/* This file is part of Cyclos (www.cyclos.org). A project of the Social Trade Organisation (www.socialtrade.org). Cyclos 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 2 of the License, or (at your option) any later version. Cyclos 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 Cyclos; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package nl.strohalm.cyclos.utils; import java.beans.PropertyDescriptor; import java.lang.reflect.Array; import java.lang.reflect.Method; 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 java.util.SortedMap; import java.util.TreeMap; import java.util.TreeSet; import javassist.util.proxy.MethodHandler; import javassist.util.proxy.ProxyFactory; import javassist.util.proxy.ProxyObject; import nl.strohalm.cyclos.entities.Entity; import nl.strohalm.cyclos.entities.EntityReference; import org.apache.commons.beanutils.PropertyUtils; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang.StringUtils; import org.hibernate.proxy.HibernateProxy; /** * Helper class for entities * @author luis */ public class EntityHelper { /** * A method handler for entity references * @author luis */ private static final class InstanceMethodHandler implements MethodHandler { private final Class<? extends Entity> entityType; private final Long id; private InstanceMethodHandler(final Class<? extends Entity> entityType, final Long id) { this.entityType = entityType; this.id = id; } @Override public Object invoke(final Object object, final Method thisMethod, final Method proceed, final Object[] args) throws Throwable { if (isExpectedMethod("toString", 0, thisMethod, args)) { return entityType.getSimpleName() + "#" + id; } else { return proceed.invoke(object, args); } } private boolean isExpectedMethod(final String expectedMethod, final int expectedArgs, final Method currentMethod, final Object[] currentArgs) { return expectedMethod.equals(currentMethod.getName()) && expectedArgs == (currentArgs == null ? 0 : currentArgs.length); } } private static Long[] EMPTY_ARRAY = new Long[0]; private static Map<Class<? extends Entity>, SortedMap<String, PropertyDescriptor>> cachedPropertiesByClass = new HashMap<Class<? extends Entity>, SortedMap<String, PropertyDescriptor>>(); private static Map<Class<? extends Entity>, Class<? extends Entity>> cachedEntityTypes = new HashMap<Class<? extends Entity>, Class<? extends Entity>>(); /** * Returns the real class of the given entity. If it is a proxy, return the entity class, not the proxy class */ @SuppressWarnings("unchecked") public static Class<? extends Entity> getRealClass(final Entity entity) { final Class<? extends Entity> type = entity.getClass(); if ((entity instanceof EntityReference) || (entity instanceof HibernateProxy)) { return (Class<? extends Entity>) type.getSuperclass(); } return type; } /** * Returns the real root class of the given entity. If it is a proxy, return the entity class, not the proxy class. For example, if trying * getRootRealClass(MemberAccountProxy), the result will be Account. */ @SuppressWarnings("unchecked") public static Class<? extends Entity> getRealRootClass(final Entity entity) { Class<? extends Entity> type = getRealClass(entity); while (!type.getSuperclass().equals(Entity.class)) { type = (Class<? extends Entity>) type.getSuperclass(); } return type; } /** * Returns true if the specified id contains a possible entity identifier. A possible entity identifier is considered valid if it is not null and * positive. */ public static boolean isValidId(final Long id) { return id != null && id > 0; } /** * Returns true if the given string value contains a valid identifier */ public static boolean isValidId(final String value) { try { final long id = Long.parseLong(value); return isValidId(id); } catch (final NumberFormatException e) { return false; } } /** * Parses a list of ids from a given string */ public static Set<Long> parseIds(final String string) { final Set<Long> ids = new HashSet<Long>(); final String[] parts = StringUtils.trimToEmpty(string).split(","); for (String part : parts) { part = part.trim(); if (part.isEmpty()) { continue; } long id; try { id = Long.parseLong(part); if (id <= 0) { throw new Exception(); } } catch (final Exception e) { throw new IllegalStateException("Invalid id value:" + part); } ids.add(id); } return ids; } /** * Returns a Map with basic properties for the given entity */ public static Map<String, PropertyDescriptor> propertyDescriptorsFor(final Entity entity) { final Class<? extends Entity> clazz = getRealClass(entity); SortedMap<String, PropertyDescriptor> properties = cachedPropertiesByClass.get(clazz); if (properties == null) { properties = new TreeMap<String, PropertyDescriptor>(); final PropertyDescriptor[] propertyDescriptors = PropertyUtils.getPropertyDescriptors(clazz); for (final PropertyDescriptor descriptor : propertyDescriptors) { final String name = descriptor.getName(); boolean ok = name.equals("id"); if (!ok) { final Method readMethod = descriptor.getReadMethod(); if (readMethod != null) { final Class<?> declaringClass = readMethod.getDeclaringClass(); ok = !declaringClass.equals(Entity.class) && !declaringClass.equals(CustomFieldsContainer.class); } } if (ok) { properties.put(name, descriptor); } } properties = Collections.unmodifiableSortedMap(properties); cachedPropertiesByClass.put(clazz, properties); } return properties; } /** * Returns a collection with basic property names for the given entity */ public static Collection<String> propertyNamesFor(final Entity entity) { return propertyDescriptorsFor(entity).keySet(); } /** * Returns a reference to the given entity type and the give id */ @SuppressWarnings("unchecked") public static <E extends Entity> E reference(final Class<E> entityType, final Long id) { if (id == null || id.longValue() <= 0L) { return null; } final Class<? extends Entity> proxyClass = resolveEntityClass(entityType); final E proxy = (E) ClassHelper.instantiate(proxyClass); ((ProxyObject) proxy).setHandler(new InstanceMethodHandler(entityType, id)); proxy.setId(id); return proxy; } /** * Returns a reference array to the given entity type and the give ids */ @SuppressWarnings("unchecked") public static <E extends Entity> E[] references(final Class<E> entityType, final List<Long> ids) { if (ids == null || ids.size() == 0) { return (E[]) Array.newInstance(entityType, 0); } final E[] entities = (E[]) Array.newInstance(entityType, ids.size()); for (int i = 0; i < ids.size(); i++) { final Long id = ids.get(i); entities[i] = reference(entityType, id); } return entities; } /** * @return return an ordered set of EntityVO */ public static Set<EntityVO> toEntityVO(final Collection<? extends Entity> entities) { if (CollectionUtils.isEmpty(entities)) { return Collections.emptySet(); } final TreeSet<EntityVO> orderedSet = new TreeSet<EntityVO>(); for (final Entity entity : entities) { orderedSet.add(entity.readOnlyView()); } return orderedSet; } /** * Converts entities into an array of identifiers */ public static Long[] toIds(final Collection<? extends Entity> entities) { if (entities == null || entities.isEmpty()) { return EMPTY_ARRAY; } final Long[] ids = new Long[entities.size()]; int i = 0; for (final Entity entity : entities) { ids[i++] = entity.getId(); } return ids; } /** * Converts entities into an array of identifiers */ public static Long[] toIds(final Entity... entities) { if (entities == null || entities.length == 0) { return EMPTY_ARRAY; } return toIds(Arrays.asList(entities)); } public static Long[] toIds(final String... idsAsString) { if (idsAsString == null || idsAsString.length == 0) { return EMPTY_ARRAY; } final Long[] ids = new Long[idsAsString.length]; int i = 0; for (final String idAsString : idsAsString) { ids[i++] = Long.valueOf(idAsString); } return ids; } /** * converts a Collection of Entities into an ArrayList of their id's. * @param entities. The input list of Entities. Can be null or empty. * @return An ArrayList of the ids. Returns an empty list if the input was null or an empty list. */ public static Collection<Long> toIdsAsList(final Collection<? extends Entity> entities) { if (entities == null || entities.isEmpty()) { return Collections.emptyList(); } final List<Long> ids = new ArrayList<Long>(entities.size()); for (final Entity entity : entities) { ids.add(entity.getId()); } return ids; } /** * Converts entities into a JSON representation of ids */ public static String toIdsAsString(final Collection<? extends Entity> entities) { return '[' + StringUtils.join(toIds(entities), ',') + ']'; } /** * Converts entities into a JSON representation of ids */ public static String toIdsAsString(final Entity... entities) { return '[' + StringUtils.join(toIds(entities), ',') + ']'; } @SuppressWarnings("unchecked") private synchronized static Class<? extends Entity> resolveEntityClass(final Class<? extends Entity> entityType) { Class<? extends Entity> proxyType = cachedEntityTypes.get(entityType); if (proxyType == null) { final ProxyFactory factory = new ProxyFactory(); factory.setInterfaces(new Class[] { EntityReference.class }); factory.setSuperclass(entityType); proxyType = factory.createClass(); cachedEntityTypes.put(entityType, proxyType); } return proxyType; } }