1 2 Previous Next 18 Replies Latest reply on Nov 12, 2008 11:41 AM by juandbotiva Go to original post
      • 15. Re: Detach entities - Obtaining a clear pojo
        alrubinger

        Some really nice stuff in there, James. I like how you've removed the need for a base class. I wonder how far we can take this now...

        The latest revision of our class is now in package org.hibernate.collection to take advantage of the internal members of other classes in this package - I see some benefits to that design choice. However, there's a method in there requiring @Entity to be imported, and now we've made the EJB3 JARs a dependency on the classpath. I don't think we can do that. :) Hibernate isn't always (and most times isn't) being used strictly within EJB3. I'd like to come up with something that doesn't involve us crossing that boundary.

        Which got me to thinking.

        Why, if we're using EJB3 to manage our POJO Entities, it is acceptable to return an object with Hibernate classes to the client in the first place? JBoss itself is requiring EJB3 clients to know Hibernate's involved?! Shouldn't the container be removing all of that stuff for us once the object leaves the session/transaction? Then Hibernate would be completely transparent as the storage mechanism for CMP, and the application wouldn't need to know anything about it.

        Is it possible for the container to call some form of the utilities we've been talking about once a transaction is committed? It's smart enough to flush all necessary updates via the EntityManager.

        Would this break some rule? Once the transaction is done and the EntityManager flushed, session closed - remove all Hibernate Impls from the (now detached) entity. Same case for is the entity crosses a (network | JVM) boundary, leaving the session entirely. Would this prevent a reattach?

        Seems like we want something pretty simple:

        1) Remote client requests object.
        2) EJB3 looks it up, does appropriate (configured) cascades for CMRs, returns it.
        3) Client can access all members of the object returned.

        Not too much to ask, is it?

        S,
        ALR

        • 16. Re: Detach entities - Obtaining a clear pojo

           

          Which got me to thinking. Why, if we're using EJB3 to manage our POJO Entities, it is acceptable to return an object with Hibernate classes to the client in the first place? JBoss itself is requiring EJB3 clients to know Hibernate's involved?! Shouldn't the container be removing all of that stuff for us once the object leaves the session/transaction? Then Hibernate would be completely transparent as the storage mechanism for CMP, and the application wouldn't need to know anything about it.


          I agree that was one of my main points - Is a Pojo really a Pojo if its depends on specific 3rd party classes? Not in my book its not.


          I've removed the @Entity requirement as we don't actually care if the object is an entity or not, its only the presence of PersistentCollection members that we must check for, and we can only do that by checking each field or each object.
          I've kept a check to eleminate classes that are definitely not going to contain PersistentCollection fields for performance reasons.

          This new implementation allows us to recurse any object graph looking for PersistentCollection fields that might be hidden within it, as long as they are accessible via a get* method.

          package org.hibernate.collection;
          
          import java.lang.reflect.InvocationTargetException;
          import java.util.ArrayList;
          import java.util.Collection;
          import java.util.Map;
          
          import org.apache.commons.beanutils.PropertyUtils;
          import org.hibernate.LazyInitializationException;
          
          /**
           * Some utils methods to get a complete clean POJO from an entity bean with hibernate specific
           * fields stripped out. Note this class is not meant as a way of avoid LazyInitializationException's.
           * Its purpose to strip out data before sending the object to a remote client so that does not have
           * knowledge of hibernate classes.
           * @author ALR 5/9/2006
           * @author modified by James Adams 6/9/2006, 7/9/2006
           *
           */
          public class HibernateCleaner {
          
           /**
           * Traverse through the object removing all fields of type org.hibernate.collection.PersistentCollection.
           * by following all bean style get* methods.
           * *Note* the object cannot be reattached after calling this method.
           *
           * @param obj
           * @return obj with no PersistentCollection objects within it.
           */
           public static void clean(Object obj){
           try {
           removePersistenceContext(obj, new ArrayList<Integer>());
           } catch (IllegalAccessException e) {
           throw new RuntimeException(e);
           } catch (InvocationTargetException e) {
           throw new RuntimeException(e);
           } catch (NoSuchMethodException e) {
           throw new RuntimeException(e);
           }
           }
          
           /**
           * Checks if the object MIGHT contain PersistentCollection fields by checking if its not a primative, or if its in package
           * java.* or javax.*. **NOTE** - This relies on there not being an entity within those packages.
           * A more conservative aproach is to only check if its a primative.
           * @param obj
           * @author James Adams
           */
           public static boolean couldObjectContainPersistentCollections(Object obj){
          
           if(obj.getClass().isPrimitive()){ return false; }
          
           //TODO - This is not full proof and relys on people not using package with names below. More conservative approach
           //is to remove this
           if(obj.getClass().getName().startsWith("java.") || obj.getClass().getName().startsWith("javax.")){
           return false;
           }
          
           return true; //If we got here it might be a hiberate persisted object
           }
          
           /**
           * If the specified object's identity hash code is not in the specified
           * collection of visited hash codes, removes of all data binding the object
           * (and its members) to a specific persistence context, leaving intact only
           * model-centric data
           *
           * @param visitedObjectHashCodes
           * @param obj
           * @author ALR
           * @author James Adams, modified 6/9/2006,7/9/2006
           * @throws NoSuchMethodException
           * @throws InvocationTargetException
           * @throws IllegalAccessException
           */
           private static Object removePersistenceContext(Object obj, Collection<Integer> visitedObjectHashCodes)
           throws IllegalAccessException, InvocationTargetException, NoSuchMethodException{
           if(obj==null){
           return null;
           }
          
           if(visitedObjectHashCodes.contains(System.identityHashCode(obj))){
           return obj;
           }
          
           //Add the object's hash to the Collection of visited hash codes
           visitedObjectHashCodes.add(System.identityHashCode(obj));
          
           try{
           // If runtime type of persistentCollection is PersistentCollection we get the inner collection
           //from within the hibernate proxy
           if ((obj instanceof PersistentCollection)){
           obj = getInnerCollection((PersistentCollection)obj);
           }
           }catch(LazyInitializationException e){
           return null;
           }
          
           if(obj==null){
           return null;
           }
          
           if(obj instanceof Collection){
           for(Object o : (Collection)obj){
           //recurse into each element of the collection. We don't need to set the value
           //of the object in the Collection as the reference will be adjusted, doing so within this
           //iterable loop would result in a ConcurrentModificationException anyway.
           removePersistenceContext(o, visitedObjectHashCodes);
           //TODO - Does Hibernate support Collections of primatives? As this will not work when the
           //Collection is of types that Java does not pass by reference; primatives, String, Integer,
           //Float etc. In this case we need to create a new Collection and populate it.
           }
           }else if(!couldObjectContainPersistentCollections(obj) ){ //we don't need to investigate all fields if its not an entity
           return obj;
           }else{
           Map allMembers = PropertyUtils.describe(obj);
           for(Object member : allMembers.entrySet()){
           Map.Entry m = (Map.Entry)member;
           try {
           try{
           PropertyUtils.setProperty(obj, m.getKey().toString(), removePersistenceContext(m.getValue(), visitedObjectHashCodes));
           }catch (LazyInitializationException e){
           PropertyUtils.setProperty(obj, m.getKey().toString(), null);
           }
           } catch (IllegalAccessException e) {
           throw new RuntimeException(e);
           } catch (InvocationTargetException e) {
           throw new RuntimeException(e);
           } catch (NoSuchMethodException e) {
           }
           }
           }
           return obj;
           }
          
           /**
           * Because this class is in package org.hibernate.collection this method
           * allows us to access the inner protected fields of the hibernate collections
           * @param obj
           * @return The inner collection object of the PersistentCollection parameter
           * @author James Adams
           */
           protected static Object getInnerCollection(PersistentCollection obj){
           if(obj instanceof PersistentBag){
           return ((PersistentBag)obj).bag;
           }else if(obj instanceof PersistentList){
           return ((PersistentList)obj).list;
           }else if(obj instanceof PersistentSet){
           return ((PersistentSet)obj).set;
           }else if(obj instanceof PersistentMap){
           return ((PersistentMap)obj).map;
           }else if(obj instanceof PersistentSortedMap){
           return ((PersistentSortedMap)obj).map;
           }else if(obj instanceof PersistentSortedSet){
           return ((PersistentSortedSet)obj).set;
           }else{
           return null;
           }
           }
          
          }
          


          • 17. Re: Detach entities - Obtaining a clear pojo

            Since the challenge still exists, here is an even further improved version.
            The behaviour is the same, simply some performance improvements were added.

            package org.hibernate.collection;
            
            import java.lang.reflect.InvocationTargetException;
            import java.util.Collection;
            import java.util.HashSet;
            import java.util.Map;
            
            import org.apache.commons.beanutils.PropertyUtils;
            import org.hibernate.LazyInitializationException;
            
            /**
             * Some utility methods to get a complete clean POJO from an entity bean with hibernate
             * specific fields stripped out. Note this class is not meant as a way of avoid
             * LazyInitializationException's.
             * Its purpose to strip out data before sending the object to a remote client so that
             * does not have knowledge of hibernate classes.
             * @author ALR 5/9/2006
             * @author modified by James Adams 6/9/2006, 7/9/2006
             * @author me 7/6/2008
             */
            public final class HibernateCleaner
            {
             /**
             * Traverse through the object removing all fields of type
             * org.hibernate.collection.PersistentCollection. by following all bean style get* methods.
             * <p>
             * <b>Note</b> the object cannot be reattached after calling this method.
             *
             * @param obj the object to clean
             */
             public static void clean(final Object obj)
             {
             try
             {
             removePersistenceContext(obj, new HashSet<Integer>());
             }
             catch (final IllegalAccessException e)
             {
             throw new RuntimeException(e);
             }
             catch (final InvocationTargetException e)
             {
             throw new RuntimeException(e);
             }
             catch (final NoSuchMethodException e)
             {
             throw new RuntimeException(e);
             }
             }
            
             /**
             * Checks if the object MIGHT contain PersistentCollection fields by checking if its not a
             * primitive, or if its in package java.* or javax.*.
             *
             * <p>
             * <b>NOTE</b> This relies on there not being an entity within those packages.
             * A more conservative approach is to only check if its a primitive.
             * @param obj the object to check
             * @author James Adams
             */
             public static boolean couldObjectContainPersistentCollections(final Object obj)
             {
             if (obj == null) return false;
            
             final Class clazz = obj.getClass();
            
             if (clazz.isPrimitive()) return false;
            
             final String className = clazz.getName();
            
             //TODO - This is not fool proof and relies on people not using package with names below.
             //More conservative approach is to remove this
             if (className.startsWith("java.") || className.startsWith("javax.")) return false;
            
             return true; //If we got here it might be a hibernate persisted object
             }
            
             /**
             * Because this class is in package org.hibernate.collection this method
             * allows us to access the inner protected fields of the hibernate collections
             * @param obj
             * @return The inner collection object of the PersistentCollection parameter
             * @author James Adams
             */
             private static Object getInnerCollection(final PersistentCollection obj)
             {
             if (obj instanceof PersistentBag)
             return ((PersistentBag) obj).bag;
             else if (obj instanceof PersistentList)
             return ((PersistentList) obj).list;
             else if (obj instanceof PersistentSet)
             return ((PersistentSet) obj).set;
             else if (obj instanceof PersistentMap)
             return ((PersistentMap) obj).map;
             else if (obj instanceof PersistentSortedMap)
             return ((PersistentSortedMap) obj).map;
             else if (obj instanceof PersistentSortedSet)
             return ((PersistentSortedSet) obj).set;
             else
             return null;
             }
            
             /**
             * If the specified object's identity hash code is not in the specified
             * collection of visited hash codes, removes of all data binding the object
             * (and its members) to a specific persistence context, leaving intact only
             * model-centric data
             *
             * @param visitedObjectHashCodes
             * @param obj
             * @author ALR
             * @author James Adams, modified 6/9/2006,7/9/2006
             * @throws NoSuchMethodException
             * @throws InvocationTargetException
             * @throws IllegalAccessException
             */
             private static Object removePersistenceContext(Object obj, //
             final Collection<Integer> visitedObjectHashCodes) //
             throws IllegalAccessException, InvocationTargetException, NoSuchMethodException
             {
             if (obj == null) return null;
            
             final Integer hashCode = System.identityHashCode(obj);
            
             if (visitedObjectHashCodes.contains(hashCode)) return obj;
            
             //Add the object's hash to the Collection of visited hash codes
             visitedObjectHashCodes.add(hashCode);
            
             try
             {
             // If runtime type of persistentCollection is PersistentCollection we get the
             // inner collection from within the hibernate proxy
             if (obj instanceof PersistentCollection)
             {
             obj = getInnerCollection((PersistentCollection) obj);
             }
             }
             catch (final LazyInitializationException e)
             {
             return null;
             }
            
             if (obj == null) return null;
            
             if (obj instanceof Collection)
             {
             for (final Object o : (Collection< ? >) obj)
             {
             //recurse into each element of the collection. We don't need to set the value of
             //the object in the Collection as the reference will be adjusted, doing so within
             //this iterable loop would result in a ConcurrentModificationException anyway.
             removePersistenceContext(o, visitedObjectHashCodes);
             //TODO - Does Hibernate support Collections of primitives? As this will not work
             // when the Collection is of types that Java does not pass by reference;
             // primitives, String, Integer, Float etc.
             // In this case we need to create a new Collection and populate it.
             }
             }
             else if (!couldObjectContainPersistentCollections(obj))
             return obj;
             else
             {
             @SuppressWarnings("unchecked")
             final Map<String, Object> allMembers = PropertyUtils.describe(obj);
             for (final Map.Entry<String, Object> member : allMembers.entrySet())
             {
             final Object propertyValue = member.getValue();
             try
             {
             final Object cleanedValue = removePersistenceContext(propertyValue, //
             visitedObjectHashCodes);
            
             // avoid costly calls via reflection if the value has not been modified
             if (cleanedValue != propertyValue)
             {
             PropertyUtils.setProperty(obj, member.getKey(), cleanedValue);
             }
             }
             catch (final LazyInitializationException e)
             {
             PropertyUtils.setProperty(obj, member.getKey(), null);
             }
             }
             }
             return obj;
             }
            
             private HibernateCleaner()
             {
             // nothing
             }
            }


            • 18. Re: Detach entities - Obtaining a clear pojo
              juandbotiva

              Hi!

              I'm getting an java.lang.IllegalAccessError when trying to access the fields in the getInnerCollection method. Is there a workaround for this ?

              java.lang.IllegalAccessError: tried to access field org.hibernate.collection.PersistentBag.bag from class org.hibernate.collection.HibernateCleaner


              Thanks in advance.

              Regards.

              1 2 Previous Next