Archive

Archive for September, 2009

JBoss classloading demystified!

September 24, 2009 Leave a comment

When things get to go wrong couple of days before production, you tend to understand how JBoss classloading works.
This is with JBoss 4.2.2GA

You have two EARs, a.ear and b.ear. Both have c.jar in their libs.
Case 1: Both EARs dont do a scoped classloading

    What happens at server startup:

Both a.ear and b.ear use the same UnifiedLoadedRepository3 instance.
a.ear gets loaded first.
a.ear puts c.jar into the repository.
Now a.ear/b.ear and c.jar are called as UCL’s (Unified class loaders). All these UCLs are listed in the order of the deployment.
If C.class is in c.jar, then when asked for C.class, it is picked up from c.jar of a.ear. ie JBoss goes thru the list of the UCLs with the ULR. And the it finds C.class @ c.jar of a.ear first, and it returns the same.

When you hot undeploy a.ear, then any code calling b.ear gets C.class from b.ear. The only difference is that c.jar is out of the ULR. You get a “Not loaded in repository cache” message in the JMX window.

Now do a hot deploy of a.ear, this time you get java.lang.VerifyError for some class in c.jar.
When you redeploy the common jars, you need to redeploy everything.

Case 2: b.ear has the isolation part and also java2ParentDelegation to true

    What happens at server startup:

Calling b.ear will fail due to Classcastexception on the shared classes.
Why? The scoped application will use the already loaded class from the parent repository’s class cache.

We will go over these scenarios:
1. Deploy A, Deploy B, startserver, undeploy A, Access B
2. Deploy A, Deploy B, startserver, Access App A , undeploy A, Access App B
3. Deploy A, Deploy B, startserver, Access App A , undeploy B, Access App A
4. Deploy A, Deploy B, startserver, Access App A , Access App B, undeploy A, Access App B

Case 1: Deploy A, Deploy B, startserver, undeploy A, Access B

  • A.ear and B.ear deployed.
    displayClassInfo:

    com.noelios.restlet.ext.servlet.ServerServlet Information
    Repository cache version:
    com.noelios.restlet.ext.servlet.ServerServlet(11c5b4).ClassLoader=org.jboss.mx.loading.UnifiedClassLoader3@5a84be{ url=file:abc/deploy/tmp7314601894329309101A-0.0.1-SNAPSHOT.ear ,addedOrder=47}
    ..org.jboss.mx.loading.UnifiedClassLoader3@5a84be{ url=file:abc/deploy/tmp7314601894329309101A-0.0.1-SNAPSHOT.ear ,addedOrder=47}
    ….file:abc/deploy/tmp7314601894329309101A-0.0.1-SNAPSHOT.ear
    ….file:abc/deploy/tmp7314601894329309101A-0.0.1-SNAPSHOT.ear-contents/lib/com.noelios.restlet-1.1.1.jar
    ….file:abc/deploy/tmp7314601894329309101A-0.0.1-SNAPSHOT.ear-contents/lib/com.noelios.restlet.ext.servlet-1.1.1.jar
    ..org.jboss.system.server.NoAnnotationURLClassLoader@fcfa52
    ..sun.misc.Launcher$AppClassLoader@7172ea
    ….file:/Volumes/Mustang/branch/JBoss_POS_20081204/jboss-4.2.2.GA-pos/bin/run.jar
    ..sun.misc.Launcher$ExtClassLoader@b169f8
    ….file:/Library/Java/Extensions/jspComm.jar
    ++++CodeSource: (file:abc/deploy/tmp7314601894329309101A-0.0.1-SNAPSHOT.ear-contents/lib/com.noelios.restlet.ext.servlet-1.1.1.jar )
    Implemented Interfaces:
    ### Instance0 found in UCL: org.jboss.mx.loading.UnifiedClassLoader3@5a84be{ url=file:abc/deploy/tmp7314601894329309101A-0.0.1-SNAPSHOT.ear ,addedOrder=47}
    ### Instance1 found in UCL: org.jboss.mx.loading.UnifiedClassLoader3@9c5cf5{ url=file:abc/deploy/tmp6517201656953000171B-0.0.1-SNAPSHOT.ear ,addedOrder=48}

    ->undeploy A

    com.noelios.restlet.ext.servlet.ServerServlet Information
    Not loaded in repository cache
    ### Instance0 found in UCL: org.jboss.mx.loading.UnifiedClassLoader3@151ce0{ url=file:abc/deploy/tmp3581146229639141667B-0.0.1-SNAPSHOT.ear ,addedOrder=48}

    ->Access AppB via

    2009-09-26 19:46:28,567 ERROR [http-127.0.0.1-8080-1 – [ServerServlet]] – Allocate exception for servlet ServerServlet
    java.lang.NullPointerException
    at org.jboss.mx.loading.RepositoryClassLoader.findClass(RepositoryClassLoader.java:630)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:316)
    at org.jboss.mx.loading.RepositoryClassLoader.loadClassImpl(RepositoryClassLoader.java:474)
    at org.jboss.mx.loading.RepositoryClassLoader.loadClass(RepositoryClassLoader.java:415)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:251)
    at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:374)

    What happened here?? JBoss maps up A.ear to be the classloader for ServerServlet. JBoss hasnt loaded the ServerServlet yet. When B.ear wants ServerServlet class, JBoss checks its UCL’s and sees that A.ear can load it. Since we undeloyed A.ear, we get this nullptrexception

  • Case 2: Deploy A, Deploy B, startserver,Access App A , undeploy A, Access App B

  • A.ear and B.ear deployed.
    displayClassInfo:

    com.noelios.restlet.ext.servlet.ServerServlet Information
    Repository cache version:
    com.noelios.restlet.ext.servlet.ServerServlet(11c5b4).ClassLoader=org.jboss.mx.loading.UnifiedClassLoader3@5a84be{ url=file:abc/deploy/tmp7314601894329309101A-0.0.1-SNAPSHOT.ear ,addedOrder=47}
    ..org.jboss.mx.loading.UnifiedClassLoader3@5a84be{ url=file:abc/deploy/tmp7314601894329309101A-0.0.1-SNAPSHOT.ear ,addedOrder=47}
    ….file:abc/deploy/tmp7314601894329309101A-0.0.1-SNAPSHOT.ear
    ….file:abc/deploy/tmp7314601894329309101A-0.0.1-SNAPSHOT.ear-contents/lib/com.noelios.restlet-1.1.1.jar
    ….file:abc/deploy/tmp7314601894329309101A-0.0.1-SNAPSHOT.ear-contents/lib/com.noelios.restlet.ext.servlet-1.1.1.jar
    ..org.jboss.system.server.NoAnnotationURLClassLoader@fcfa52
    ..sun.misc.Launcher$AppClassLoader@7172ea
    ….file:/Volumes/Mustang/branch/JBoss_POS_20081204/jboss-4.2.2.GA-pos/bin/run.jar
    ..sun.misc.Launcher$ExtClassLoader@b169f8
    ….file:/Library/Java/Extensions/jspComm.jar
    ++++CodeSource: (file:abc/deploy/tmp7314601894329309101A-0.0.1-SNAPSHOT.ear-contents/lib/com.noelios.restlet.ext.servlet-1.1.1.jar )
    Implemented Interfaces:
    ### Instance0 found in UCL: org.jboss.mx.loading.UnifiedClassLoader3@5a84be{ url=file:abc/deploy/tmp7314601894329309101A-0.0.1-SNAPSHOT.ear ,addedOrder=47}
    ### Instance1 found in UCL: org.jboss.mx.loading.UnifiedClassLoader3@9c5cf5{ url=file:abc/deploy/tmp6517201656953000171B-0.0.1-SNAPSHOT.ear ,addedOrder=48}

    ->Access AppA via

    ->undeploy A

    com.noelios.restlet.ext.servlet.ServerServlet Information
    Not loaded in repository cache
    ### Instance0 found in UCL: org.jboss.mx.loading.UnifiedClassLoader3@151ce0{ url=file:abc/deploy/tmp3581146229639141667B-0.0.1-SNAPSHOT.ear ,addedOrder=48}

    ->Access AppB via

    java.lang.ClassCastException: com.apple.ist.retail.pos.b.restlet2.BApplication
    com.noelios.restlet.ext.servlet.ServerServlet.createApplication(ServerServlet.java:282)
    com.noelios.restlet.ext.servlet.ServerServlet.getApplication(ServerServlet.java:703)
    com.noelios.restlet.ext.servlet.ServerServlet.init(ServerServlet.java:820)
    com.apple.ist.retail.pos.b.servlet2.BServlet.init(BServlet.java:24)
    javax.servlet.GenericServlet.init(GenericServlet.java:212)

    What happened here? B.ear was linked up with the ServerServlet from A.ear. When A.ear was undeployed, the ServerServlet got loaded from B.ear and thus this ClassCastException

  • Case 3: Deploy A, Deploy B, startserver,Access App A , undeploy B, Access App A

  • A.ear and B.ear deployed.
    displayClassInfo:

    com.noelios.restlet.ext.servlet.ServerServlet Information
    Repository cache version:
    com.noelios.restlet.ext.servlet.ServerServlet(11c5b4).ClassLoader=org.jboss.mx.loading.UnifiedClassLoader3@5a84be{ url=file:abc/deploy/tmp7314601894329309101A-0.0.1-SNAPSHOT.ear ,addedOrder=47}
    ..org.jboss.mx.loading.UnifiedClassLoader3@5a84be{ url=file:abc/deploy/tmp7314601894329309101A-0.0.1-SNAPSHOT.ear ,addedOrder=47}
    ….file:abc/deploy/tmp7314601894329309101A-0.0.1-SNAPSHOT.ear
    ….file:abc/deploy/tmp7314601894329309101A-0.0.1-SNAPSHOT.ear-contents/lib/com.noelios.restlet-1.1.1.jar
    ….file:abc/deploy/tmp7314601894329309101A-0.0.1-SNAPSHOT.ear-contents/lib/com.noelios.restlet.ext.servlet-1.1.1.jar
    ..org.jboss.system.server.NoAnnotationURLClassLoader@fcfa52
    ..sun.misc.Launcher$AppClassLoader@7172ea
    ….file:/Volumes/Mustang/branch/JBoss_POS_20081204/jboss-4.2.2.GA-pos/bin/run.jar
    ..sun.misc.Launcher$ExtClassLoader@b169f8
    ….file:/Library/Java/Extensions/jspComm.jar
    ++++CodeSource: (file:abc/deploy/tmp7314601894329309101A-0.0.1-SNAPSHOT.ear-contents/lib/com.noelios.restlet.ext.servlet-1.1.1.jar )
    Implemented Interfaces:
    ### Instance0 found in UCL: org.jboss.mx.loading.UnifiedClassLoader3@5a84be{ url=file:abc/deploy/tmp7314601894329309101A-0.0.1-SNAPSHOT.ear ,addedOrder=47}
    ### Instance1 found in UCL: org.jboss.mx.loading.UnifiedClassLoader3@9c5cf5{ url=file:abc/deploy/tmp6517201656953000171B-0.0.1-SNAPSHOT.ear ,addedOrder=48}

    ->Access AppA via

    ->undeploy B
    displayClassInfo:

    com.noelios.restlet.ext.servlet.ServerServlet Information
    Repository cache version:
    com.noelios.restlet.ext.servlet.ServerServlet(11c5b4).ClassLoader=org.jboss.mx.loading.UnifiedClassLoader3@5a84be{ url=file:abc/deploy/tmp7314601894329309101A-0.0.1-SNAPSHOT.ear ,addedOrder=47}
    ..org.jboss.mx.loading.UnifiedClassLoader3@5a84be{ url=file:abc/deploy/tmp7314601894329309101A-0.0.1-SNAPSHOT.ear ,addedOrder=47}
    ….file:abc/deploy/tmp7314601894329309101A-0.0.1-SNAPSHOT.ear
    ….file:abc/deploy/tmp7314601894329309101A-0.0.1-SNAPSHOT.ear-contents/lib/com.noelios.restlet-1.1.1.jar
    ….file:abc/deploy/tmp7314601894329309101A-0.0.1-SNAPSHOT.ear-contents/lib/com.noelios.restlet.ext.servlet-1.1.1.jar
    ..org.jboss.system.server.NoAnnotationURLClassLoader@fcfa52
    ..sun.misc.Launcher$AppClassLoader@7172ea
    ….file:/Volumes/Mustang/branch/JBoss_POS_20081204/jboss-4.2.2.GA-pos/bin/run.jar
    ..sun.misc.Launcher$ExtClassLoader@b169f8
    ….file:/Library/Java/Extensions/jspComm.jar
    ++++CodeSource: (file:abc/deploy/tmp7314601894329309101A-0.0.1-SNAPSHOT.ear-contents/lib/com.noelios.restlet.ext.servlet-1.1.1.jar )
    Implemented Interfaces:
    ### Instance0 found in UCL: org.jboss.mx.loading.UnifiedClassLoader3@5a84be{ url=file:abc/deploy/tmp7314601894329309101A-0.0.1-SNAPSHOT.ear ,addedOrder=47}

    ->Access AppA : No issues

  • hibernate caching

    September 14, 2009 Leave a comment

    Original Post
    # load will load the object from the cache if it exists and the database otherwise. If it loads the object from the database, it populates the cache.
    # createQuery with setCacheable=false. This will get every object and populate the cache. If an object was already in the cache, it will use that. So, if you specify “from foo” with an empty cache, and there are 10 foo objects, it will first grab all 10 objects from the database, and populate the cache with them. In the meantime, if foo#2 is changed in the database, but not the cache, “from foo” will get the cached version. If foo#2 is eliminated from the cache, “from foo” will get 1 and 3-10 from the cache, but will get foo#2 from the database and repopulate the cache.
    # createQuery with setCacheable=true. This is basically the same as createQuery with setCacheable=false, with a few differences. First, an extra entry will be made into the query cache. Second, if you issued a query with “from foo”, and then manually removed foo#2 from the cache, and then changed foo#2, and then redid the query, “from foo”, would it still hold the old foo#2? That is what I expected, but no! It uses the query results from “from foo”, but, those only have the keys. A lookup is performed on each key. Since the key does not exist anymore, it goes back to the database and repopulates the cache.
    # If one issues a createQuery with “FROM foo WHERE name=?”, and the name changes, there will be 2 queries in the query cache, and both will point to the same id. The query cache with the original name is not automatically notified. This means, I think, one must manually do this and organize one’s code well, or understand that the query cache should only be used for an acceptable time period whereby it can be slightly incorrect. For example, a report that has to be up to date for the last 10 minutes. Or, one can live with only having the objects cached but not the query cache itself…it is hinted at that caching with the query cache is a bit rare.
    # createSQLQuery with a partial list of mapped columns fails (as it should), so that means that if you use custom sql to populate something with addEntity, it will always be fully cached.
    # createQuery with a left join fetch adds everything to the cache. This means that explict set caching will probably not be necessary, which is good. Eager fetching with the left join is naturally synchronized with what goes into the cache.
    # When using createSQLQuery, one must use an explicit .addScalar or .addEntity if setCacheable is true, or hibernate gets confused on casting.
    # To set an expiration time on custom sql which has no class mapping, use setCacheRegion on createSQLQuery, and then set that in the eviction policy.
    # If one uses the eviction policy code, the /default must be set

    Categories: Tech stuff

    Hibernate issues

    September 13, 2009 Leave a comment

    Ignorance from myside and lack of meaningful exceptions thrown by hibernate took me a day to a simple save.
    I have been trying to deploy a hibernate application in JBOSS and I have run into enough issues to write a book.

    Tried doing the SessionFactory JNDI thing..didnt work out. Went back to hibernate.cfg.xml
    Configurations needed

    Issue 1: No solution still
    Related issue

    2009-09-13 00:37:22,837 WARN [http-17.223.23.143-8080-1 – WrappedConnection] – Closing a result set you left open! Please close it yourself.
    java.lang.Throwable: STACKTRACE
    at org.jboss.resource.adapter.jdbc.WrappedStatement.registerResultSet(WrappedStatement.java:617)
    at org.jboss.resource.adapter.jdbc.WrappedStatement.getGeneratedKeys(WrappedStatement.java:533)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:585)
    at org.hibernate.util.GetGeneratedKeysHelper.getGeneratedKey(GetGeneratedKeysHelper.java:69)
    at org.hibernate.id.IdentityGenerator$GetGeneratedKeysDelegate.executeAndExtract(IdentityGenerator.java:74)
    at org.hibernate.id.insert.AbstractReturningDelegate.performInsert(AbstractReturningDelegate.java:33)
    at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2158)
    at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2638)
    at org.hibernate.action.EntityIdentityInsertAction.execute(EntityIdentityInsertAction.java:48)
    at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:250)
    at org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:298)
    at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:181)
    at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:107)
    at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:187)
    at org.hibernate.event.def.DefaultSaveEventListener.saveWithGeneratedOrRequestedId(DefaultSaveEventListener.java:33)
    at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:172)
    at org.hibernate.event.def.DefaultSaveEventListener.performSaveOrUpdate(DefaultSaveEventListener.java:27)
    at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:70)
    at org.hibernate.impl.SessionImpl.fireSave(SessionImpl.java:535)
    at org.hibernate.impl.SessionImpl.save(SessionImpl.java:523)
    at org.hibernate.impl.SessionImpl.save(SessionImpl.java:519)

    Issue 2: ehcache
    Solution

    Caused by: java.lang.NullPointerException
    at org.jboss.mx.loading.RepositoryClassLoader.findClass(RepositoryClassLoader.java:630)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:316)
    at org.jboss.mx.loading.RepositoryClassLoader.loadClassImpl(RepositoryClassLoader.java:474)
    at org.jboss.mx.loading.RepositoryClassLoader.loadClass(RepositoryClassLoader.java:415)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:251)
    at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:374)
    at net.sf.ehcache.CacheManager.detectAndFixDiskStorePathConflict(CacheManager.java:270)
    at net.sf.ehcache.CacheManager.configure(CacheManager.java:252)
    at net.sf.ehcache.CacheManager.init(CacheManager.java:196)
    at net.sf.ehcache.CacheManager.(CacheManager.java:157)
    at org.hibernate.cache.EhCacheProvider.start(EhCacheProvider.java:127)
    at org.hibernate.impl.SessionFactoryImpl.(SessionFactoryImpl.java:183)
    – at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1294)

    Categories: Tech stuff

    web.xml, CONFIDENTIAL, redirectPort,HTTP, HTTPS, 302

    September 12, 2009 Leave a comment

    Copied stuff:

    That’s expected:

    – The page submits to http://server:port/page2.jsp

    – The server issues a 302 redirect to https://server:port/page2.jsp – before examining any page content to find out there’s a POST. As your requirement is for confidentiality, this is correct – the server *must not* require any of the content to be sent before making the decision to redirect, or confidentiality could be broken.

    – The browser acts on the redirect and issues a GET for the redirected page, hence without the POST data.

    > Without the constraint everything works,
    > when changing action to https://server:port/page2.jsp it works too.

    Yes, as the intermediate redirect will be missing.

    > I’m wondering whether this could be a tomcat bug?

    No, it’s a feature of HTTP. Change your form action (or set the entire site to be SSL).

    Categories: Tech stuff

    SQL JOINS

    September 12, 2009 Leave a comment

    INNER JOIN:

    SELECT Persons.LastName, Persons.FirstName, Orders.OrderNo
    FROM Persons
    INNER JOIN Orders
    ON Persons.P_Id=Orders.P_Id
    ORDER BY Persons.LastName

    It will return only those rows in person which have mapping in orders.

    LEFT OUTER JOIN/LEFT JOIN:

    SELECT Persons.LastName, Persons.FirstName, Orders.OrderNo
    FROM Persons
    LEFT JOIN Orders
    ON Persons.P_Id=Orders.P_Id
    ORDER BY Persons.LastName

    It will return all the rows in person with mapping from orders(if available)

    INNER JOIN: Is the N+1 issue I guess. Where each row from table A is searched for in table B and if matched is shown.

    Categories: Tech stuff

    Hibernate Statistics JMX

    September 12, 2009 Leave a comment

    Couple of articles abt Hibernate statistics:
    Hibernate Concurrency bugs
    Nitty gritties of the Statistics MBean

    To enable Statistics Mbean via the jboss-service.xml
    <mbean code="org.hibernate.jmx.StatisticsService"
    name="xyz:service=HibernateStatistics">
    <attribute name="SessionFactoryJNDIName">java:/xyz/SessionFactory</attribute>
    <attribute name="StatisticsEnabled">true</attribute>
    <depends>jboss:service=Naming</depends>
    </mbean>

    Categories: Tech stuff

    Explain plan for Oracle

    September 10, 2009 Leave a comment


    explain plan set statement_id = 'name' for select....

    select * from table(dbms_xplan.display);

    Categories: Tech stuff

    Unix shell scripts

    September 10, 2009 Leave a comment

    I suck at shell scripting. So I write to remember.

    #!/bin/sh
    LOG_DIR=.
    fileCount=`ls -latr ${LOG_DIR}/prod* | wc -l`
    #If no files found then exit.
    if [ ${fileCount} -eq 0 ]
    then
    echo "what the F man!"
    exit
    fi

    LASTHOUR=`expr \`date +%s\` - 3600`
    scanStartDate=`date -r $LASTHOUR '+%Y-%m-%d %H:'`
    echo "Current time: $(date)"
    fileList=`ls -latr ${LOG_DIR}/prod* | tail -3 | awk '{print $9}'`

    #Send mail
    if [ ${a} -eq ${b} ]
    then
    echo -e "xyz\neer" | mail -s "STATS - $(hostname) - ${scanStartDate}" xyz@gmail.com
    else
    exit
    fi

    Categories: Tech stuff

    Threadpool and apache mina

    September 2, 2009 Leave a comment

    Another link: Mina and threadpools

    Found this info at : Link

    The processorCount parameter specifies the number of SocketIoProcessors
    the SocketAcceptor should use. Each SocketIoProcessor owns a
    java.nio.channels.Selector. When the SocketAcceptor accepts a new
    connection it will add the new connection to the next SocketIoProcessor
    in a round-robin fashion. The default is 1 which is fine if you’re
    running on single CPU hardware.

    The Executor specified in the SocketAcceptor constructor will be used by
    the SocketAcceptor to run its internal Worker thread which listens for
    new connections. If you don’t need 100% control over the threads created
    in your application you won’t have to bother about this.

    If you specify the ThreadModel.MANUAL thread model MINA won’t create an
    ExecutorFilter for your sessions’ filter chains. If you use MANUAL and
    you don’t add an ExecutorFilter yourself all processing will take place
    in a single SocketIoProcessor Worker thread (or processorCount threads
    if processorCount > 1).

    If you want to add your own ExecutorFilter explicitly (like in c) you
    should specify ThreadModel.MANUAL otherwise you will end up with two
    ExecutorFilters.

    A ThreadModel can be specified for all ports your SocketAcceptor is
    listening on by using acceptor.getDefaultConfig().setThreadModel(…) or
    on a port by port basis by specifying a SocketAcceptorConfig when
    calling acceptor.bind(…).

    More readings:
    ThreadPool in Mina with concurrent from Java5

    Categories: Tech stuff