package org.tynamo.hibernate.decorators; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import org.apache.tapestry5.func.F; import org.apache.tapestry5.func.Predicate; import org.apache.tapestry5.hibernate.HibernateSessionSource; import org.apache.tapestry5.ioc.annotations.Inject; import org.apache.tapestry5.ioc.annotations.Symbol; import org.hibernate.HibernateException; import org.hibernate.cfg.Configuration; import org.hibernate.mapping.Column; import org.hibernate.mapping.Component; import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Property; import org.hibernate.mapping.Selectable; import org.hibernate.mapping.SimpleValue; import org.hibernate.metadata.ClassMetadata; import org.hibernate.metadata.CollectionMetadata; import org.hibernate.type.CollectionType; import org.hibernate.type.ComponentType; import org.hibernate.type.Type; import org.slf4j.Logger; import org.tynamo.descriptor.CollectionDescriptor; import org.tynamo.descriptor.EmbeddedDescriptor; import org.tynamo.descriptor.IdentifierDescriptor; import org.tynamo.descriptor.IdentifierDescriptorImpl; import org.tynamo.descriptor.ObjectReferenceDescriptor; import org.tynamo.descriptor.TynamoClassDescriptor; import org.tynamo.descriptor.TynamoPropertyDescriptor; import org.tynamo.descriptor.decorators.DescriptorDecorator; import org.tynamo.descriptor.extension.EnumReferenceDescriptor; import org.tynamo.descriptor.factories.DescriptorFactory; import org.tynamo.exception.MetadataNotFoundException; import org.tynamo.exception.TynamoRuntimeException; import org.tynamo.hibernate.TynamoHibernateSymbols; /** * This decorator will add metadata information. It will replace simple * reflection based TynamoPropertyTynamoPropertyDescriptors with appropriate * Hibernate descriptors <p/> Background... TynamoDescriptorService operates one * ReflectorDescriptorFactory - TynamoDescriptorService iterates/scans all class * types encountered - ReflectorDescriptorFactory allocates property descriptor * instance for the class type - TynamoDescriptorService decorates property * descriptor by calling this module HibernateDescriptorDecorator - * HibernateDescriptorDecorator caches the decorated property descriptor into a * decorated descriptor list - decorated descriptor list gets populated into * class descriptor for class type - TynamoDescriptorService finally populates * decorated class descriptor and it's aggregated list of decorated property * descriptors into it's own list/cache of referenced class descriptors * * @see TynamoPropertyDescriptor * @see ObjectReferenceDescriptor * @see CollectionDescriptor * @see EmbeddedDescriptor */ public class HibernateDescriptorDecorator implements DescriptorDecorator { private Logger logger; private HibernateSessionSource hibernateSessionSource; private DescriptorFactory descriptorFactory; /** * Columns longer than this will have their large property set to true. */ private final int largeColumnLength; private final boolean ignoreNonHibernateTypes; public HibernateDescriptorDecorator(HibernateSessionSource hibernateSessionSource, DescriptorFactory descriptorFactory, @Inject @Symbol(TynamoHibernateSymbols.LARGE_COLUMN_LENGTH) int largeColumnLength, @Inject @Symbol(TynamoHibernateSymbols.IGNORE_NON_HIBERNATE_TYPES) boolean ignoreNonHibernateTypes, Logger logger) { this.hibernateSessionSource = hibernateSessionSource; this.descriptorFactory = descriptorFactory; this.largeColumnLength = largeColumnLength; this.ignoreNonHibernateTypes = ignoreNonHibernateTypes; this.logger = logger; } public TynamoClassDescriptor decorate(TynamoClassDescriptor descriptor) { java.util.List<TynamoPropertyDescriptor> decoratedPropertyDescriptors = new ArrayList<TynamoPropertyDescriptor>(); Class beanType = descriptor.getBeanType(); ClassMetadata classMetaData = null; try { classMetaData = findMetadata(beanType); } catch (MetadataNotFoundException e) { if (ignoreNonHibernateTypes) { logger.warn("MetadataNotFound! could not decorate: " + descriptor.getBeanType().getSimpleName()); descriptor.setNonVisual(true); return descriptor; } else { throw new TynamoRuntimeException(e); } } for (TynamoPropertyDescriptor propertyDescriptor : descriptor.getPropertyDescriptors()) { try { TynamoPropertyDescriptor descriptorReference; if (propertyDescriptor.getName().equals(getIdentifierProperty(beanType))) { descriptorReference = createIdentifierDescriptor(beanType, propertyDescriptor); } else if (notAHibernateProperty(classMetaData, propertyDescriptor)) { // If this is not a hibernate property (i.e. marked // Transient), it's certainly not searchable // Are the any other properties like this? propertyDescriptor.setSearchable(false); descriptorReference = propertyDescriptor; } else { Property mappingProperty = getMapping(beanType).getProperty(propertyDescriptor.getName()); descriptorReference = decoratePropertyDescriptor(beanType, mappingProperty, propertyDescriptor); } decoratedPropertyDescriptors.add(descriptorReference); } catch (HibernateException e) { throw new TynamoRuntimeException(e); } } descriptor.setPropertyDescriptors(decoratedPropertyDescriptors); return descriptor; } protected TynamoPropertyDescriptor decoratePropertyDescriptor(Class beanType, Property mappingProperty, TynamoPropertyDescriptor descriptor) { if (isFormula(mappingProperty)) { descriptor.setReadOnly(true); return descriptor; } descriptor.setLength(findColumnLength(mappingProperty)); descriptor.setLarge(isLarge(mappingProperty)); if (!mappingProperty.isOptional()) { descriptor.setRequired(true); } if (!mappingProperty.isInsertable() && !mappingProperty.isUpdateable()) { descriptor.setReadOnly(true); } TynamoPropertyDescriptor descriptorReference = descriptor; Type hibernateType = mappingProperty.getType(); if (mappingProperty.getType() instanceof ComponentType) { descriptorReference = buildEmbeddedDescriptor(beanType, mappingProperty, descriptor); } else if (mappingProperty.getType() instanceof CollectionType) { descriptorReference = decorateCollectionDescriptor(beanType, descriptor); } else if (hibernateType.isAssociationType()) { descriptorReference = decorateAssociationDescriptor(beanType, mappingProperty, descriptor); } else if (hibernateType.getReturnedClass().isEnum()) { descriptor.addExtension(EnumReferenceDescriptor.class.getName(), new EnumReferenceDescriptor(hibernateType.getReturnedClass())); } return descriptorReference; } private EmbeddedDescriptor buildEmbeddedDescriptor(Class beanType, Property mappingProperty, TynamoPropertyDescriptor descriptor) { Component componentMapping = (Component) mappingProperty.getValue(); TynamoClassDescriptor embeddedClassDescriptor = descriptorFactory.buildClassDescriptor(descriptor.getPropertyType()); java.util.List<TynamoPropertyDescriptor> decoratedProperties = new ArrayList<TynamoPropertyDescriptor>(); // go thru each property and decorate it with Hibernate info for (TynamoPropertyDescriptor propertyDescriptor : embeddedClassDescriptor.getPropertyDescriptors()) { if (notAHibernateProperty(componentMapping, propertyDescriptor)) { decoratedProperties.add(propertyDescriptor); } else { Property property = componentMapping.getProperty(propertyDescriptor.getName()); TynamoPropertyDescriptor tynamopropertydescriptor = decoratePropertyDescriptor(embeddedClassDescriptor.getBeanType(), property, propertyDescriptor); decoratedProperties.add(tynamopropertydescriptor); } } embeddedClassDescriptor.setPropertyDescriptors(decoratedProperties); return new EmbeddedDescriptor(beanType, descriptor, embeddedClassDescriptor); } /** * Find the Hibernate metadata for this type, traversing up the hierarchy to * supertypes if necessary * * @param type * @return */ protected ClassMetadata findMetadata(Class type) throws MetadataNotFoundException { ClassMetadata metaData = hibernateSessionSource.getSessionFactory().getClassMetadata(type); if (metaData != null) { return metaData; } if (!type.equals(Object.class)) { return findMetadata(type.getSuperclass()); } else { throw new MetadataNotFoundException("Failed to find metadata."); } } private boolean isFormula(Property mappingProperty) { for (Iterator iter = mappingProperty.getColumnIterator(); iter.hasNext(); ) { Selectable selectable = (Selectable) iter.next(); if (selectable.isFormula()) { return true; } } return false; } /** * Checks to see if a property descriptor is in a component mapping * * @param componentMapping * @param propertyDescriptor * @return true if the propertyDescriptor property is in componentMapping */ protected boolean notAHibernateProperty(Component componentMapping, TynamoPropertyDescriptor propertyDescriptor) { for (Iterator iter = componentMapping.getPropertyIterator(); iter.hasNext(); ) { Property property = (Property) iter.next(); if (property.getName().equals(propertyDescriptor.getName())) { return false; } } return true; } private boolean isLarge(Property mappingProperty) { // Hack to avoid setting large property if length // is exactly equal to Hibernate default column length return findColumnLength(mappingProperty) != Column.DEFAULT_LENGTH && findColumnLength(mappingProperty) > largeColumnLength; } private int findColumnLength(Property mappingProperty) { int length = 0; for (Iterator iter = mappingProperty.getColumnIterator(); iter.hasNext(); ) { Column column = (Column) iter.next(); length += column.getLength(); } return length; } /** * @param classMetaData * @param descriptor * @return */ protected boolean notAHibernateProperty(ClassMetadata classMetaData, final TynamoPropertyDescriptor descriptor) { return F.flow(classMetaData.getPropertyNames()).filter(new Predicate<String>() { public boolean accept(String propertyName) { return descriptor.getName().equals(propertyName); } }).isEmpty(); } /** * @param beanType * @param descriptor * @return */ private IdentifierDescriptor createIdentifierDescriptor(Class beanType, TynamoPropertyDescriptor descriptor) { IdentifierDescriptor identifierDescriptor; PersistentClass mapping = getMapping(beanType); /** * fix for TRAILS-92 */ if (mapping.getProperty(descriptor.getName()).getType() instanceof ComponentType) { EmbeddedDescriptor embeddedDescriptor = buildEmbeddedDescriptor(beanType, mapping.getProperty(descriptor.getName()), descriptor); embeddedDescriptor.setIdentifier(true); identifierDescriptor = embeddedDescriptor; } else { identifierDescriptor = new IdentifierDescriptorImpl(beanType, descriptor); } if (((SimpleValue) mapping.getIdentifier()).getIdentifierGeneratorStrategy().equals("assigned")) { identifierDescriptor.setGenerated(false); } return identifierDescriptor; } /** * @param type * @return */ protected PersistentClass getMapping(Class type) { Configuration cfg = hibernateSessionSource.getConfiguration(); return cfg.getClassMapping(type.getName()); } /** * @param beanType * @param descriptor */ private CollectionDescriptor decorateCollectionDescriptor(Class beanType, TynamoPropertyDescriptor descriptor) { try { CollectionDescriptor collectionDescriptor = new CollectionDescriptor(beanType, descriptor); org.hibernate.mapping.Collection collectionMapping = findCollectionMapping(beanType, descriptor.getName()); // It is a child relationship if it has delete-orphan specified in // the mapping collectionDescriptor.setChildRelationship(collectionMapping.hasOrphanDelete()); CollectionMetadata collectionMetaData = hibernateSessionSource.getSessionFactory().getCollectionMetadata( collectionMapping.getRole()); collectionDescriptor.setElementType(collectionMetaData.getElementType().getReturnedClass()); collectionDescriptor.setOneToMany(collectionMapping.isOneToMany()); decorateOneToManyCollection(beanType, collectionDescriptor, collectionMapping); return collectionDescriptor; } catch (HibernateException e) { throw new TynamoRuntimeException(e); } } public TynamoPropertyDescriptor decorateAssociationDescriptor(final Class beanType, final Property mappingProperty, final TynamoPropertyDescriptor descriptor) { Type hibernateType = mappingProperty.getType(); return new ObjectReferenceDescriptor(beanType, descriptor, hibernateType.getReturnedClass()); } /** * I couldn't find a way to get the "mappedBy" value from the collection * metadata, so I'm getting it from the OneToMany annotation. */ private void decorateOneToManyCollection(final Class beanType, final CollectionDescriptor descriptor, org.hibernate.mapping.Collection mapping) { if (descriptor.isOneToMany() && mapping.isInverse()) { try { Field propertyField = beanType.getDeclaredField(descriptor.getName()); PropertyDescriptor[] propertyDescriptors = Introspector.getBeanInfo(beanType).getPropertyDescriptors(); PropertyDescriptor beanPropDescriptor = F.flow(propertyDescriptors).filter(new Predicate<PropertyDescriptor>() { public boolean accept(PropertyDescriptor propertyDescriptor) { return propertyDescriptor.getName().equals(descriptor.getName()); } }).first(); Method readMethod = beanPropDescriptor.getReadMethod(); String mappedBy = ""; if (readMethod.isAnnotationPresent(javax.persistence.OneToMany.class)) { mappedBy = readMethod.getAnnotation(javax.persistence.OneToMany.class).mappedBy(); } else if (propertyField.isAnnotationPresent(javax.persistence.OneToMany.class)) { mappedBy = propertyField.getAnnotation(javax.persistence.OneToMany.class).mappedBy(); } if (!"".equals(mappedBy)) { descriptor.setInverseProperty(mappedBy); } } catch (SecurityException e) { logger.warn("Couldn't decorate collection: " + beanType.getSimpleName() + "." + descriptor.getName(), e); } catch (NoSuchFieldException e) { logger.warn("Couldn't decorate collection: " + beanType.getSimpleName() + "." + descriptor.getName(), e); } catch (IntrospectionException e) { logger.warn("Couldn't decorate collection: " + beanType.getSimpleName() + "." + descriptor.getName(), e); } } } protected org.hibernate.mapping.Collection findCollectionMapping(Class type, String name) { String roleName = type.getName() + "." + name; org.hibernate.mapping.Collection collectionMapping = hibernateSessionSource.getConfiguration() .getCollectionMapping(roleName); if (collectionMapping != null) { return collectionMapping; } else if (!type.equals(Object.class)) { return findCollectionMapping(type.getSuperclass(), name); } else { throw new MetadataNotFoundException("Metadata not found."); } } /* * (non-Javadoc) * * @see org.tynamo.descriptor.PropertyDescriptorService#getIdentifierProperty(java.lang.Class) */ public String getIdentifierProperty(Class type) { try { return hibernateSessionSource.getSessionFactory().getClassMetadata(type).getIdentifierPropertyName(); } catch (HibernateException e) { throw new TynamoRuntimeException(e); } } }