com.javaetmoi.core.persistence.hibernate.LazyLoadingUtil.java Source code

Java tutorial

Introduction

Here is the source code for com.javaetmoi.core.persistence.hibernate.LazyLoadingUtil.java

Source

/**
 * Copyright 2012 the original author or authors.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */
package com.javaetmoi.core.persistence.hibernate;

import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.hibernate.EntityMode;
import org.hibernate.Hibernate;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.collection.PersistentCollection;
import org.hibernate.collection.PersistentMap;
import org.hibernate.impl.AbstractSessionImpl;
import org.hibernate.metadata.ClassMetadata;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.type.ComponentType;
import org.hibernate.type.Type;

/**
 * Set of helper methods that fetch a complete entity graph.
 * 
 * <p>
 * Provides a lazy way to resolve all Hibernate proxies.
 * </p>
 * 
 * @author Antoine Rey
 */
public class LazyLoadingUtil {

    /**
     * No-arg constructor
     */
    private LazyLoadingUtil() {
        // Private visibility because utility class
    }

    /**
     * Populate a lazy-initialized object graph by recursivity.
     * 
     * <p>
     * This method deeply navigates into a graph of entities in order to resolve uninitialized
     * Hibernate proxies.<br>
     * The goal is to avoid any {@link lazyinitializationexception} once entities are detached from
     * the Hibernate session.<br>
     * May attention: this method has to be called from an open persistent context / Hibernate
     * session.
     * </p>
     * 
     * @param currentSession
     *            Hibernate session still open
     * @param entities
     *            a {@link Collection} of Hibernate entities to load
     * @return the {@link Collection} of Hibernate entities fully loaded. Similar to the entities
     *         input parameter. Usefull when calling this method in a return statement.
     * 
     */
    public static <E> Collection<E> deepHydrate(final Session currentSession, Collection<E> entities) {
        for (Object entity : entities) {
            deepInflateEntity(currentSession, entity, new HashSet<String>());
        }
        return entities;
    }

    /**
     * Populate a lazy-initialized object graph by recursivity.
     * 
     * <p>
     * This method deeply navigates into a graph of entities in order to resolve uninitialized
     * Hibernate proxies.<br>
     * The goal is to avoid any {@link lazyinitializationexception} once entities are detached from
     * the Hibernate session.<br>
     * May attention: this method has to be called from an open persistent context / Hibernate
     * session.
     * </p>
     * 
     * @param currentSession
     *            Hibernate session still open
     * @param entity
     *            a single Hibernate entity or a simple java class referencing entities
     * @return the Hibernate entity fully loaded. Similar to the entity input parameter. Usefull
     *         when calling this method in a return statement.
     * 
     */
    public static <E> E deepHydrate(final Session currentSession, E entity) {
        deepInflateEntity(currentSession, entity, new HashSet<String>());
        return entity;
    }

    @SuppressWarnings("unchecked")
    private static void deepInflateEntity(final Session currentSession, Object entity, Set<String> recursiveGuard)
            throws HibernateException {
        if (entity == null) {
            return;
        }

        Class<? extends Object> persistentClass = entity.getClass();
        if (entity instanceof HibernateProxy) {
            persistentClass = ((HibernateProxy) entity).getHibernateLazyInitializer().getPersistentClass();
        }
        ClassMetadata classMetadata = currentSession.getSessionFactory().getClassMetadata(persistentClass);
        if (classMetadata == null) {
            return;
        }
        Serializable identifier = classMetadata.getIdentifier(entity, (AbstractSessionImpl) currentSession);
        String key = persistentClass.getName() + "|" + identifier;

        if (recursiveGuard.contains(key)) {
            return;
        }
        recursiveGuard.add(key);

        if (!Hibernate.isInitialized(entity)) {
            Hibernate.initialize(entity);
        }

        for (int i = 0, n = classMetadata.getPropertyNames().length; i < n; i++) {
            String propertyName = classMetadata.getPropertyNames()[i];
            Type propertyType = classMetadata.getPropertyType(propertyName);
            Object propertyValue = null;
            if (entity instanceof javassist.util.proxy.ProxyObject) {
                // For javassist proxy, the classMetadata.getPropertyValue(..) method return en
                // emppty collection. So we have to call the property's getter in order to call the
                // JavassistLazyInitializer.invoke(..) method that will initialize the collection by
                // loading it from the database.
                propertyValue = callCollectionGetter(entity, propertyName);
            } else {
                propertyValue = classMetadata.getPropertyValue(entity, propertyName, EntityMode.POJO);
            }
            deepInflateProperty(propertyValue, propertyType, currentSession, recursiveGuard);
        }
    }

    @SuppressWarnings({ "rawtypes" })
    private static void deepInflateProperty(Object propertyValue, Type propertyType, Session currentSession,
            Set<String> recursiveGuard) {
        if (propertyValue == null) {
            return; // null guard
        }

        if (propertyType.isEntityType()) {
            deepInflateEntity(currentSession, propertyValue, recursiveGuard);
        } else if (propertyType.isCollectionType()) {
            // Handle PersistentBag, PersistentList and PersistentIndentifierBag
            if (propertyValue instanceof List) {
                deepInflateCollection(currentSession, recursiveGuard, (List) propertyValue);
            } else if (propertyValue instanceof Map) {
                deepInflateMap(currentSession, recursiveGuard, (Map) propertyValue);
            } else if (propertyValue instanceof Set) {
                deepInflateCollection(currentSession, recursiveGuard, (Set) propertyValue);
            } else {
                throw new UnsupportedOperationException(
                        "Unsupported collection type: " + propertyValue.getClass().getSimpleName());
            }
        } else if (propertyType.isComponentType()) {
            if (propertyType instanceof ComponentType) {
                // i.e. @Embeddable annotation (see
                // https://github.com/arey/hibernate-hydrate/issues/1)
                deepInflateComponent(currentSession, propertyValue, (ComponentType) propertyType, recursiveGuard);
            }
        }
    }

    private static void deepInflateComponent(Session currentSession, Object componentValue,
            ComponentType componentType, Set<String> recursiveGuard) {
        // No public API to access to the component Hibernate metamodel => force to use
        // introspection instead
        String[] propertyNames = ReflectionUtil.getValue("propertyNames", componentType);
        Type[] propertyTypes = ReflectionUtil.getValue("propertyTypes", componentType);

        for (int i = 0; i < propertyNames.length; i++) {
            String propertyName = propertyNames[i];
            Type propertyType = propertyTypes[i];
            Object propertyValue = ReflectionUtil.getValue(propertyName, componentValue);
            deepInflateProperty(propertyValue, propertyType, currentSession, recursiveGuard);
        }

    }

    private static void deepInflateMap(Session currentSession, Set<String> recursiveGuard,
            @SuppressWarnings("rawtypes") Map map) {

        if (map instanceof PersistentMap) {
            if (!((PersistentMap) map).wasInitialized()) {
                Hibernate.initialize(map);
            }
        }

        if (map.size() > 0) {
            // First map keys
            @SuppressWarnings("rawtypes")
            Set keySet = map.keySet();
            for (Object key : keySet) {
                deepInflateEntity(currentSession, key, recursiveGuard);
            }
            // Then map values
            deepInflateCollection(currentSession, recursiveGuard, map.values());
        }
    }

    /**
     * 
     * 
     * @param sessionFactory
     * @param recursiveGuard
     * @param collection
     */
    private static void deepInflateCollection(Session currentSession, Set<String> recursiveGuard,
            @SuppressWarnings("rawtypes") Collection collection) {
        if (collection != null && collection.size() > 0) {
            ComponentType collectionType = null;
            if (collection instanceof PersistentCollection
                    && !((PersistentCollection) collection).isUnreferenced()) {
                String role = ((PersistentCollection) collection).getRole();
                Type type = currentSession.getSessionFactory().getCollectionMetadata(role).getElementType();
                if (type instanceof ComponentType) {
                    // ManyToMany relationship with @Embeddable annotation (see
                    // https://github.com/arey/hibernate-hydrate/issues/3)
                    collectionType = (ComponentType) type;
                }
            }
            for (Object item : collection) {
                if (item == null) {
                    continue;
                } else if (collectionType != null) {
                    deepInflateComponent(currentSession, item, collectionType, recursiveGuard);
                } else {
                    deepInflateEntity(currentSession, item, recursiveGuard);
                }
            }
        }
    }

    /**
     * Calls the getter of a collection property in order to resolve the javassist lazy proxy
     * object.
     * 
     * @param entity
     *            target object
     * @param propertyName
     *            name of the collection property (ie. clients)
     * @return the collection
     */
    protected static Object callCollectionGetter(Object entity, String propertyName) {
        try {
            Method getter = entity.getClass().getMethod(getterFromCollection(propertyName));
            getter.setAccessible(true);
            return getter.invoke(entity);
        } catch (NoSuchMethodException e) {
            // Wrap checked exception to the runtime unchecked exception
            return new RuntimeException(e);
        } catch (IllegalAccessException e) {
            return new RuntimeException(e);
        } catch (InvocationTargetException e) {
            return new RuntimeException(e);
        }
    }

    /**
     * Generate the getter name of a collection property.
     * 
     * @param propertyName
     *            name of the collection property (ie. clients)
     * @return name of the corresponding getter (ie. getClients)
     */
    protected static String getterFromCollection(String propertyName) {
        return new StringBuilder(propertyName.length() + 3).append("get")
                .append(Character.toTitleCase(propertyName.charAt(0))).append(propertyName.substring(1)).toString();
    }
}