package org.sigmah.server.servlet.exporter.models; /* * #%L * Sigmah * %% * Copyright (C) 2010 - 2016 URD * %% * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program. If not, see * <http://www.gnu.org/licenses/gpl-3.0.html>. * #L% */ import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.hibernate.Hibernate; import org.hibernate.HibernateException; import org.hibernate.collection.internal.PersistentBag; import org.hibernate.collection.internal.PersistentList; import org.hibernate.collection.internal.PersistentSet; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.proxy.HibernateProxy; import org.sigmah.server.computation.ServerComputations; import org.sigmah.server.domain.OrgUnitModel; import org.sigmah.server.domain.ProjectModel; import org.sigmah.server.domain.element.ComputationElement; import org.sigmah.shared.computation.Computations; import org.sigmah.shared.dto.element.FlexibleElementDTO; /** * Creates plain objects from Hibernate proxies. * * @author Raphaƫl Calabro (rcalabro@ideia.fr) V1.3 * @author Mehdi Benabdeslam (mehdi.benabdeslam@netapsys.fr) V2.0 */ public class Realizer { private final static Log LOG = LogFactory.getLog(Realizer.class); private final static String ORGANIZATION_FIELD = "organization"; private final static String FORMULA_FIELD = "rule"; private Realizer() { } /** * Creates a new instance of <code>object</code> with <code>ArrayList</code>s instead of <code>PersistentBag</code>s * and <code>HashSet</code> instead of <code>PersistentSet</code>s. <br> * <b>Note:</b> do not use this without testing its compatibility with your objects. * * @param <T> * Type of the object to copy. * @param object * An hibernate proxy. * @param ignores * Array of classes to ignore. * @return A copy of the given object without any proxy instance. */ public static <T> T realize(T object, Class<?>... ignores) { return realize(object, Collections.singleton(ORGANIZATION_FIELD), ignores); } public static <T> T realize(T object, Set<String> ignoredFields, Class<?>... ignores) { return realize(object, new HashMap<>(), ignoredFields, new HashSet<>(Arrays.asList(ignores)), object); } /** * Creates a new object and recursively fills its fields. * * @param <T> * Type of the object to copy. * @param object * Object to copy. * @param alreadyRealizedObjects * Set of already copied objects. * @param ignores * Set of classes to ignore. * @param parent * Parent object. * @return A copy of the given object without any proxy instance. */ @SuppressWarnings("unchecked") private static <T> T realize(T object, Map<Object, Object> alreadyRealizedObjects, Set<String> ignoredFields, Set<Class<?>> ignores, Object parent) { T result = null; if (object != null) { // If the given object has already been instantiated, no need to instantiate it again if (alreadyRealizedObjects.containsKey(object)) { return (T) alreadyRealizedObjects.get(object); } // Extracting the class of the current object final Class<T> clazz = object instanceof HibernateProxy ? ((HibernateProxy)object).getHibernateLazyInitializer().getPersistentClass() : (Class<T>) object.getClass(); if (ignores.contains(clazz) || clazz.getName().startsWith("java.") || clazz.isEnum()) { LOG.trace("\t\tUsing the given value for " + clazz); return object; } LOG.trace("Realizing " + clazz + "..."); try { final Constructor<T> emptyConstructor = clazz.getConstructor(); // Creating a new instance of the current object // REM: this will crash if the object doesn't have an empty constructor final T instance = emptyConstructor.newInstance(); alreadyRealizedObjects.put(object, instance); final List<Field> fields = getFieldsOfClass(clazz); for (final Field field : fields) { final Object destinationValue = getFieldValue(field, clazz, object, alreadyRealizedObjects, ignoredFields, ignores, parent); if (destinationValue != null) { setFieldValue(field, clazz, instance, destinationValue); } } result = instance; } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | HibernateException e) { LOG.debug("An error occured while realizing " + object, e); } } return result; } /** * Sets the given <code>field</code> with the given <code>value</code> in * the object <code>instance</code>. * * @param <T> * Type of the object to set. * @param field * Field to set. * @param clazz * Class of the object to set. * @param instance * Object to set. * @param value * Value to set. * @throws IllegalArgumentException * @throws InvocationTargetException * @throws IllegalAccessException * @throws SecurityException */ private static <T> void setFieldValue(final Field field, final Class<T> clazz, final T instance, final Object value) throws IllegalArgumentException, InvocationTargetException, IllegalAccessException, SecurityException { // Setting the field of the new object final Method setterMethod = getSetterMethod(field, clazz); if(setterMethod != null) { setterMethod.invoke(instance, value); } else { field.setAccessible(true); // Force the accessibility of the current field field.set(instance, value); } } /** * Retrieve the value of the given <code>field</code> for the given * <code>source</code> object. * * @param <T> * Type of the source object. * @param field * Field to extract. * @param clazz * Class of the source object. * @param source * Object to read from. * @param alreadyRealizedObjects * Set of already handled objects (to avoid infinite recursion). * @param ignores * Set of classes to ignore. * @param parent * Parent object. * @return The value of the given field for the given object. * @throws HibernateException * @throws SecurityException * @throws IllegalAccessException * @throws InvocationTargetException * @throws IllegalArgumentException */ private static <T> Object getFieldValue(final Field field, final Class<T> clazz, final T source, final Map<Object, Object> alreadyRealizedObjects, final Set<String> ignoredFields, final Set<Class<?>> ignores, final Object parent) throws HibernateException, SecurityException, IllegalAccessException, InvocationTargetException, IllegalArgumentException { // Avoid trying to modify static fields. // Organization should not be exported. if (Modifier.isStatic(field.getModifiers()) || (!ignores.contains(clazz) && ignoredFields.contains(field.getName()))) { return null; } LOG.trace("\tfield " + field.getName()); final Method getterMethod = getGetterMethod(field, clazz); final Object sourceValue = getterMethod.invoke(source); final Object destinationValue; final Class<?> sourceValueClass = getterMethod.getReturnType(); if (sourceValue instanceof PersistentCollection || sourceValue instanceof HibernateProxy) { Hibernate.initialize(sourceValue); } if (sourceValue == null || sourceValueClass == null) { destinationValue = null; } else if (sourceValue instanceof PersistentBag || sourceValue instanceof PersistentList) { // Turning persistent bags into array lists final ArrayList<Object> list = new ArrayList<Object>(); for (Object value : (PersistentBag) sourceValue) { list.add(realize(value, alreadyRealizedObjects, ignoredFields, ignores, parent)); } destinationValue = list; } else if (sourceValue instanceof PersistentSet) { // Turning persistent sets into hash sets final HashSet<Object> set = new HashSet<>(); for (Object value : (PersistentSet) sourceValue) { set.add(realize(value, alreadyRealizedObjects, ignoredFields, ignores, parent)); } destinationValue = set; } else if (source instanceof ComputationElement && field.getName().equals(FORMULA_FIELD)) { final Collection<FlexibleElementDTO> elements; if (parent instanceof ProjectModel) { elements = ServerComputations.getAllElementsFromModel((ProjectModel) parent); } else if (parent instanceof OrgUnitModel) { elements = ServerComputations.getAllElementsFromModel((OrgUnitModel) parent); } else { elements = Collections.<FlexibleElementDTO>emptyList(); } destinationValue = Computations.formatRuleForEdition(((ComputationElement) source).getRule(), elements); } else { destinationValue = realize(sourceValue, alreadyRealizedObjects, ignoredFields, ignores, parent); } return destinationValue; } /** * Find all the declared fields of the given class and its super classes * for Sigmah objects. * * @param clazz * Class to read. * @return A list of every declared fields (also contains static fields). * @throws SecurityException If one of the fields is protected. */ private static List<Field> getFieldsOfClass(final Class<?> clazz) throws SecurityException { // Accessing fields from the given object. final ArrayList<Field> fields = new ArrayList<>(); fields.addAll(Arrays.asList(clazz.getDeclaredFields())); // Accessing fields from the super classes. Class<?> superClass = clazz.getSuperclass(); while (superClass.getPackage().getName().startsWith("org.sigmah")) { fields.addAll(Arrays.asList(superClass.getDeclaredFields())); superClass = superClass.getSuperclass(); } return fields; } /** * Find the getter method for the given <code>field</code> in the given * <code>class</code>. * <p> * The name of the getter method is assumed to starts by <code>get</code> or * by <code>is</code>. * * @param field * Field to search. * @param clazz * Class to use. * @return The getter field or <code>null</code> if not found. */ private static Method getGetterMethod(Field field, Class<?> clazz) { final String getAccessor = "get" + field.getName().toLowerCase(); for(final Method method : clazz.getMethods()) { if(getAccessor.equals(method.getName().toLowerCase()) && method.getParameterTypes().length == 0) { return method; } } final String isAccessor = "is" + field.getName().toLowerCase(); for(final Method method : clazz.getMethods()) { if(isAccessor.equals(method.getName().toLowerCase()) && method.getParameterTypes().length == 0) { return method; } } return null; } /** * Find the setter method for the given <code>field</code> in the given * <code>class</code>. * <p> * The name of the setter method is assumed to starts by <code>set</code>. * * @param field * Field to search. * @param clazz * Class to use. * @return The setter field or <code>null</code> if not found. */ private static Method getSetterMethod(Field field, Class<?> clazz) { final String getAccessor = "set" + field.getName().toLowerCase(); for(final Method method : clazz.getMethods()) { if(getAccessor.equals(method.getName().toLowerCase()) && method.getParameterTypes().length == 1) { return method; } } return null; } }