2
\$\begingroup\$

Here's a class that converts Hibernate proxies to normal classes. This is useful with GWT for example, when sending objects loaded from database to the GWT client. Please review it.

package ru.minogin.core.server.hibernate;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import org.apache.log4j.Logger;
import org.hibernate.collection.PersistentBag;
import org.hibernate.collection.PersistentList;
import org.hibernate.collection.PersistentMap;
import org.hibernate.collection.PersistentSet;
import org.hibernate.collection.PersistentSortedSet;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.proxy.LazyInitializer;

import ru.minogin.core.client.bean.Bean;
import ru.minogin.core.server.reflection.ReflectionUtils;

/**
 * Thread-safety: this class in not thread-safe.
 *
 * @author Andrey Minogin
 * @version 1.0, 02/28/11
 *
 */
public class Dehibernator {
    private static final Logger logger = Logger.getLogger(Dehibernator.class);

    private IdentityHashMap<Object, Object> processed;

    @SuppressWarnings("unchecked")
    public <T> T clean(T object) {
        processed = new IdentityHashMap<Object, Object>();

        logger.debug("Cleaning: " + (object != null ? object.getClass() : null));
        return (T) doClean(object);
    }

    @SuppressWarnings("unchecked")
    private Object doClean(Object dirty) {
        logger.debug("Do clean: " + (dirty != null ? dirty.getClass() : null));

        if (dirty == null)
            return null;

        if (processed.containsKey(dirty)) {
            logger.debug("Object already cleaned, skipping.");

            return processed.get(dirty);
        }

        if (isPrimitive(dirty)) {
            logger.debug("Object is primitive, skipping.");

            return dirty;
        }

        if (dirty instanceof PersistentList) {
            logger.debug("Object is a PersistentList");

            PersistentList dirtyList = (PersistentList) dirty;
            List<Object> cleanList = new ArrayList<Object>();
            processed.put(dirtyList, cleanList);
            if (dirtyList.wasInitialized()) {
                for (Object value : dirtyList) {
                    cleanList.add(doClean(value));
                }
            }
            return cleanList;
        }

        if (dirty instanceof PersistentBag) {
            logger.debug("Object is a PersistentBag");

            PersistentBag dirtyList = (PersistentBag) dirty;
            List<Object> cleanList = new ArrayList<Object>();
            processed.put(dirtyList, cleanList);
            if (dirtyList.wasInitialized()) {
                for (Object value : dirtyList) {
                    cleanList.add(doClean(value));
                }
            }
            return cleanList;
        }

        if (dirty instanceof PersistentSortedSet) {
            logger.debug("Object is a PersistentSortedSet");

            PersistentSortedSet dirtySet = (PersistentSortedSet) dirty;
            Set<Object> cleanSet = new TreeSet<Object>();
            processed.put(dirtySet, cleanSet);
            if (dirtySet.wasInitialized()) {
                for (Object value : dirtySet) {
                    cleanSet.add(doClean(value));
                }
            }
            return cleanSet;
        }

        if (dirty instanceof PersistentSet) {
            logger.debug("Object is a PersistentSet");

            PersistentSet dirtySet = (PersistentSet) dirty;
            Set<Object> cleanSet = new HashSet<Object>();
            processed.put(dirtySet, cleanSet);
            if (dirtySet.wasInitialized()) {
                for (Object value : dirtySet) {
                    cleanSet.add(doClean(value));
                }
            }
            return cleanSet;
        }

        if (dirty instanceof PersistentMap) {
            logger.debug("Object is a PersistentMap");

            PersistentMap dirtyMap = (PersistentMap) dirty;
            Map<Object, Object> cleanMap = new LinkedHashMap<Object, Object>();
            processed.put(dirtyMap, cleanMap);
            if (dirtyMap.wasInitialized()) {
                for (Object key : dirtyMap.keySet()) {
                    Object value = dirtyMap.get(key);
                    cleanMap.put(doClean(key), doClean(value));
                }
            }
            return cleanMap;
        }

        if (dirty instanceof List) {
            logger.debug("Object is a List");

            List<Object> dirtyList = (List<Object>) dirty;
            List<Object> cleanList = new ArrayList<Object>();
            processed.put(dirtyList, cleanList);
            for (Object value : dirtyList) {
                cleanList.add(doClean(value));
            }
            return cleanList;
        }

        if (dirty instanceof LinkedHashMap) {
            logger.debug("Object is a LinkedHashMap");

            Map<Object, Object> dirtyMap = (Map<Object, Object>) dirty;
            Map<Object, Object> cleanMap = new LinkedHashMap<Object, Object>();
            processed.put(dirtyMap, cleanMap);
            for (Object key : dirtyMap.keySet()) {
                Object value = dirtyMap.get(key);
                cleanMap.put(doClean(key), doClean(value));
            }
            return cleanMap;
        }

        if (dirty instanceof HashMap) {
            logger.debug("Object is a HashMap");

            Map<Object, Object> dirtyMap = (Map<Object, Object>) dirty;
            Map<Object, Object> cleanMap = new HashMap<Object, Object>();
            processed.put(dirtyMap, cleanMap);
            for (Object key : dirtyMap.keySet()) {
                Object value = dirtyMap.get(key);
                cleanMap.put(doClean(key), doClean(value));
            }
            return cleanMap;
        }

        if (dirty instanceof LinkedHashSet<?>) {
            logger.debug("Object is a LinkedHashSet");

            Set<Object> dirtySet = (LinkedHashSet<Object>) dirty;
            Set<Object> cleanSet = new LinkedHashSet<Object>();
            processed.put(dirtySet, cleanSet);
            for (Object value : dirtySet) {
                cleanSet.add(doClean(value));
            }
            return cleanSet;
        }

        if (dirty instanceof HashSet<?>) {
            logger.debug("Object is a HashSet");

            Set<Object> dirtySet = (HashSet<Object>) dirty;
            Set<Object> cleanSet = new HashSet<Object>();
            processed.put(dirtySet, cleanSet);
            for (Object value : dirtySet) {
                cleanSet.add(doClean(value));
            }
            return cleanSet;
        }

        if (dirty instanceof TreeSet<?>) {
            logger.debug("Object is a TreeSet");

            Set<Object> dirtySet = (TreeSet<Object>) dirty;
            Set<Object> cleanSet = new TreeSet<Object>();
            processed.put(dirtySet, cleanSet);
            for (Object value : dirtySet) {
                cleanSet.add(doClean(value));
            }
            return cleanSet;
        }

        if (dirty instanceof HibernateProxy) {
            logger.debug("Object is a HibernateProxy");

            HibernateProxy proxy = (HibernateProxy) dirty;
            LazyInitializer lazyInitializer = proxy.getHibernateLazyInitializer();
            if (lazyInitializer.isUninitialized()) {
                logger.debug("It is uninitialized, skipping");

                processed.put(dirty, null);
                return null;
            }
            else {
                logger.debug("It is initialized, getting implementati");

                dirty = lazyInitializer.getImplementation();
            }
        }

        if (dirty instanceof Bean) {
            logger.debug("Object is a Bean");

            Bean bean = (Bean) dirty;
            processed.put(bean, bean);
            for (String property : bean.getPropertyNames()) {
                bean.set(property, doClean(bean.get(property)));
            }
            return bean;
        }

        processed.put(dirty, dirty);
        for (String property : ReflectionUtils.getProperties(dirty)) {
            logger.debug("Processing property " + property);

            Object value = ReflectionUtils.get(dirty, property);
            ReflectionUtils.setIfPossible(dirty, property, doClean(value));
        }
        return dirty;
    }

    private boolean isPrimitive(Object object) {
        if (object instanceof String)
            return true;

        if (object instanceof Date)
            return true;

        if (object instanceof Enum)
            return true;

        Class<? extends Object> xClass = object.getClass();
        if (xClass.isPrimitive())
            return true;

        return false;
    }
}
\$\endgroup\$
1
\$\begingroup\$

doClean() is crying out for decomposition. Compare

if (dirty instanceof PersistentList) {
    logger.debug("Object is a PersistentList");

    PersistentList dirtyList = (PersistentList) dirty;
    List<Object> cleanList = new ArrayList<Object>();
    processed.put(dirtyList, cleanList);
    if (dirtyList.wasInitialized()) {
        for (Object value : dirtyList) {
            cleanList.add(doClean(value));
        }
    }
    return cleanList;
}

to

if (dirty instanceof List) {
    logger.debug("Object is a List");

    List<Object> dirtyList = (List<Object>) dirty;
    List<Object> cleanList = new ArrayList<Object>();
    processed.put(dirtyList, cleanList);
    for (Object value : dirtyList) {
        cleanList.add(doClean(value));
    }
    return cleanList;
}

The differences are

  1. The types in the debug messages.
  2. The types in the casts.
  3. The types of the clean replacements.
  4. The check for PersistentList.wasInitialized().

Similar patterns exist for maps, sets, sorted sets, etc. and can all be refactored into new methods, each handling a different collection interface.

if (dirty instanceof List) {
    return cleanList((List)dirty);
}

...

private cleanList(List dirty) {
    logger.debug("Object is a List");
    List clean = new ArrayList();
    processed.put(dirty, clean);
    if (shouldCopyValues(dirty)) {
        for (Object value : dirty) {
            clean.add(doClean(value));
        }
    }
    return clean;
}

private shouldCopyValues(Collection dirty) {
    return (!(dirty instanceof Persistent) || ((Persistent)dirty).wasInitialized());
}

Note that there's no longer a need for a separate check in doClean() for PersistentList. One minor optimization here would be to calculate the size of the needed list ahead of time, setting it to zero if it's an uninitialized PersistentList.

For the more complicated Map and Set types, you'll need a way to map from the dirty class to the appropriate clean class. I recommend putting this logic into cleanSet() and cleanMap() instead of doClean(). You could take the same approach as you did above, using a series of instanceof tests, or you could use a map.

Actually, only the persistent varieties need to use a different class type, so you may as well just check for those three specifically and otherwise create a new instance of the dirty collection.

Note: when copying values for Maps you can use Map.entrySet() to get the key/value pairs as Map.Entry objects instead of calling Map.get() on each key. It's a minor speed improvement.

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.