001    /*
002     * Copyright 2004 Chris Nelson
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
007     * Unless required by applicable law or agreed to in writing,
008     * software distributed under the License is distributed on an "AS IS" BASIS,
009     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
010     * See the License for the specific language governing permissions and limitations under the License.
011     */
012    package org.trails.hibernate;
013    
014    import java.beans.IntrospectionException;
015    import java.beans.Introspector;
016    import java.beans.PropertyDescriptor;
017    import java.lang.reflect.Field;
018    import java.lang.reflect.Method;
019    import java.util.ArrayList;
020    import java.util.Collection;
021    import java.util.HashMap;
022    import java.util.Iterator;
023    import java.util.List;
024    
025    import ognl.Ognl;
026    import ognl.OgnlException;
027    
028    import org.apache.commons.logging.Log;
029    import org.apache.commons.logging.LogFactory;
030    import org.hibernate.HibernateException;
031    import org.hibernate.SessionFactory;
032    import org.hibernate.cfg.Configuration;
033    import org.hibernate.mapping.Column;
034    import org.hibernate.mapping.Component;
035    import org.hibernate.mapping.PersistentClass;
036    import org.hibernate.mapping.Property;
037    import org.hibernate.mapping.Selectable;
038    import org.hibernate.mapping.SimpleValue;
039    import org.hibernate.metadata.ClassMetadata;
040    import org.hibernate.metadata.CollectionMetadata;
041    import org.hibernate.type.ComponentType;
042    import org.hibernate.type.Type;
043    import org.springframework.orm.hibernate3.LocalSessionFactoryBean;
044    import org.trails.exception.TrailsRuntimeException;
045    import org.trails.descriptor.extension.EnumReferenceDescriptor;
046    import org.trails.descriptor.*;
047    
048    /**
049     * This decorator will add metadata information. It will replace simple
050     * reflection based TrailsPropertyIPropertyDescriptors with appropriate
051     * Hibernate descriptors <p/> Background... TrailsDescriptorService operates one
052     * ReflectorDescriptorFactory - TrailsDescriptorService iterates/scans all class
053     * types encountered - ReflectorDescriptorFactory allocates property descriptor
054     * instance for the class type - TrailsDescriptorService decorates property
055     * descriptor by calling this module HibernateDescriptorDecorator -
056     * HibernateDescriptorDecorator caches the decorated property descriptor into a
057     * decorated descriptor list - decorated descriptor list gets populated into
058     * class descriptor for class type - TrailsDescriptorService finally populates
059     * decorated class descriptor and it's aggregated list of decorated property
060     * descriptors into it's own list/cache of referenced class descriptors
061     * 
062     * @see TrailsPropertyDescriptor
063     * @see ObjectReferenceDescriptor
064     * @see CollectionDescriptor
065     * @see EmbeddedDescriptor
066     */
067    public class HibernateDescriptorDecorator implements DescriptorDecorator
068    {
069            protected static final Log LOG = LogFactory.getLog(HibernateDescriptorDecorator.class);
070    
071            private LocalSessionFactoryBean localSessionFactoryBean;
072    
073            private List types;
074    
075            private DescriptorFactory descriptorFactory;
076    
077            private HashMap<Class, IClassDescriptor> descriptors = new HashMap<Class, IClassDescriptor>();
078    
079            private int largeColumnLength = 100;
080    
081            private boolean ignoreNonHibernateTypes = false;
082    
083            public IClassDescriptor decorate(IClassDescriptor descriptor)
084            {
085                    ArrayList<IPropertyDescriptor> decoratedPropertyDescriptors = new ArrayList<IPropertyDescriptor>();
086    
087                    Class type = descriptor.getType();
088                    ClassMetadata classMetaData = null;
089    
090                    try
091                    {
092                            classMetaData = findMetadata(type);
093                    } catch (MetadataNotFoundException e)
094                    {
095                            if (ignoreNonHibernateTypes) {
096                                    return descriptor;
097                            } else {
098                                    throw new TrailsRuntimeException(e);
099                            }
100                    }
101    
102                    for (IPropertyDescriptor propertyDescriptor : descriptor.getPropertyDescriptors())
103                    {
104                            try
105                            {
106                                    IPropertyDescriptor descriptorReference;
107    
108                                    if (propertyDescriptor.getName().equals(getIdentifierProperty(type)))
109                                    {
110                                            descriptorReference = createIdentifierDescriptor(type, propertyDescriptor, descriptor);
111                                    } else if (notAHibernateProperty(classMetaData, propertyDescriptor))
112                                    {
113                                            // If this is not a hibernate property (i.e. marked
114                                            // Transient), it's certainly not searchable
115                                            // Are the any other properties like this?
116                                            propertyDescriptor.setSearchable(false);
117                                            descriptorReference = propertyDescriptor;
118                                    } else
119                                    {
120                                            Property mappingProperty = getMapping(type).getProperty(propertyDescriptor.getName());
121                                            descriptorReference = decoratePropertyDescriptor(type, mappingProperty, propertyDescriptor,
122                                                            descriptor);
123                                    }
124    
125                                    decoratedPropertyDescriptors.add(descriptorReference);
126    
127                            } catch (HibernateException e)
128                            {
129                                    throw new TrailsRuntimeException(e);
130                            }
131                    }
132                    descriptor.setPropertyDescriptors(decoratedPropertyDescriptors);
133                    return descriptor;
134            }
135    
136            protected IPropertyDescriptor decoratePropertyDescriptor(Class type, Property mappingProperty,
137                            IPropertyDescriptor descriptor, IClassDescriptor parentClassDescriptor)
138            {
139                    if (isFormula(mappingProperty))
140                    {
141                            descriptor.setReadOnly(true);
142                            return descriptor;
143                    }
144                    descriptor.setLength(findColumnLength(mappingProperty));
145                    descriptor.setLarge(isLarge(mappingProperty));
146                    if (!mappingProperty.isOptional())
147                    {
148                            descriptor.setRequired(true);
149                    }
150    
151                    if (!mappingProperty.isInsertable() && !mappingProperty.isUpdateable())
152                    {
153                            descriptor.setReadOnly(true);
154                    }
155    
156                    IPropertyDescriptor descriptorReference = descriptor;
157                    Type hibernateType = mappingProperty.getType();
158                    if (mappingProperty.getType() instanceof ComponentType)
159                    {
160                            EmbeddedDescriptor embeddedDescriptor = buildEmbeddedDescriptor(type, mappingProperty, descriptor,
161                                            parentClassDescriptor);
162                            descriptorReference = embeddedDescriptor;
163                    } else if (Collection.class.isAssignableFrom(descriptor.getPropertyType()))
164                    {
165                            descriptorReference = decorateCollectionDescriptor(type, descriptor, parentClassDescriptor);
166                    } else if (hibernateType.isAssociationType())
167                    {
168                            descriptorReference = decorateAssociationDescriptor(type, mappingProperty, descriptor,
169                                            parentClassDescriptor);
170                    } else if (hibernateType.getReturnedClass().isEnum())
171                    {
172                            descriptor.addExtension(EnumReferenceDescriptor.class.getName(), new EnumReferenceDescriptor(hibernateType
173                                            .getReturnedClass()));
174                    }
175    
176                    return descriptorReference;
177            }
178    
179            private EmbeddedDescriptor buildEmbeddedDescriptor(Class type, Property mappingProperty,
180                            IPropertyDescriptor descriptor, IClassDescriptor parentClassDescriptor)
181            {
182                    Component componentMapping = (Component) mappingProperty.getValue();
183                    IClassDescriptor baseDescriptor = getDescriptorFactory().buildClassDescriptor(descriptor.getPropertyType());
184                    // build from base descriptor
185                    EmbeddedDescriptor embeddedDescriptor = new EmbeddedDescriptor(type, baseDescriptor);
186                    // and copy from property descriptor
187                    embeddedDescriptor.copyFrom(descriptor);
188                    ArrayList<IPropertyDescriptor> decoratedProperties = new ArrayList<IPropertyDescriptor>();
189                    // go thru each property and decorate it with Hibernate info
190                    for (IPropertyDescriptor propertyDescriptor : embeddedDescriptor.getPropertyDescriptors())
191                    {
192                            if (notAHibernateProperty(componentMapping, propertyDescriptor))
193                            {
194                                    decoratedProperties.add(propertyDescriptor);
195                            } else
196                            {
197                                    Property property = componentMapping.getProperty(propertyDescriptor.getName());
198                                    IPropertyDescriptor iPropertyDescriptor = decoratePropertyDescriptor(embeddedDescriptor.getBeanType(),
199                                                    property, propertyDescriptor, parentClassDescriptor);
200                                    decoratedProperties.add(iPropertyDescriptor);
201                            }
202                    }
203                    embeddedDescriptor.setPropertyDescriptors(decoratedProperties);
204                    return embeddedDescriptor;
205            }
206    
207            /**
208             * The default way to order our property descriptors is by the order they
209             * appear in the hibernate config, with id first. Any non-mapped properties
210             * are tacked on at the end, til I think of a better way.
211             * 
212             * @param propertyDescriptors
213             * @return
214             */
215            protected List sortPropertyDescriptors(Class type, List propertyDescriptors)
216            {
217                    ArrayList sortedPropertyDescriptors = new ArrayList();
218    
219                    try
220                    {
221                            sortedPropertyDescriptors.add(Ognl.getValue("#this.{? identifier == true}[0]", propertyDescriptors));
222                            for (Iterator iter = getMapping(type).getPropertyIterator(); iter.hasNext();)
223                            {
224                                    Property mapping = (Property) iter.next();
225                                    sortedPropertyDescriptors.addAll((List) Ognl.getValue("#this.{ ? name == \"" + mapping.getName()
226                                                    + "\"}", propertyDescriptors));
227                            }
228                    } catch (Exception ex)
229                    {
230                            throw new TrailsRuntimeException(ex);
231                    }
232                    return sortedPropertyDescriptors;
233            }
234    
235            /**
236             * Find the Hibernate metadata for this type, traversing up the hierarchy to
237             * supertypes if necessary
238             * 
239             * @param type
240             * @return
241             */
242            protected ClassMetadata findMetadata(Class type) throws MetadataNotFoundException
243            {
244                    ClassMetadata metaData = getSessionFactory().getClassMetadata(type);
245                    if (metaData != null)
246                    {
247                            return metaData;
248                    }
249                    if (!type.equals(Object.class))
250                    {
251                            return findMetadata(type.getSuperclass());
252                    } else
253                    {
254                            throw new MetadataNotFoundException("Failed to find metadata.");
255                    }
256            }
257    
258            private boolean isFormula(Property mappingProperty)
259            {
260                    for (Iterator iter = mappingProperty.getColumnIterator(); iter.hasNext();)
261                    {
262                            Selectable selectable = (Selectable) iter.next();
263                            if (selectable.isFormula())
264                            {
265                                    return true;
266                            }
267                    }
268                    return false;
269            }
270    
271            /**
272             * Checks to see if a property descriptor is in a component mapping
273             * 
274             * @param componentMapping
275             * @param propertyDescriptor
276             * @return true if the propertyDescriptor property is in componentMapping
277             */
278            protected boolean notAHibernateProperty(Component componentMapping, IPropertyDescriptor propertyDescriptor)
279            {
280                    for (Iterator iter = componentMapping.getPropertyIterator(); iter.hasNext();)
281                    {
282                            Property property = (Property) iter.next();
283                            if (property.getName().equals(propertyDescriptor.getName()))
284                            {
285                                    return false;
286                            }
287                    }
288                    return true;
289            }
290    
291            private boolean isLarge(Property mappingProperty)
292            {
293                    // Hack to avoid setting large property if length
294                    // is exactly equal to Hibernate default column length
295                    return findColumnLength(mappingProperty) != Column.DEFAULT_LENGTH
296                                    && findColumnLength(mappingProperty) > getLargeColumnLength();
297            }
298    
299            private int findColumnLength(Property mappingProperty)
300            {
301                    int length = 0;
302                    for (Iterator iter = mappingProperty.getColumnIterator(); iter.hasNext();)
303                    {
304                            Column column = (Column) iter.next();
305                            length += column.getLength();
306                    }
307                    return length;
308            }
309    
310            /**
311             * @param classMetaData
312             * @param type
313             * @return
314             */
315            protected boolean notAHibernateProperty(ClassMetadata classMetaData, IPropertyDescriptor descriptor)
316            {
317                    try
318                    {
319                            return (Boolean) Ognl.getValue("propertyNames.{ ? #this == \"" + descriptor.getName() + "\"}.size() == 0",
320                                            classMetaData);
321                    } catch (OgnlException oe)
322                    {
323                            throw new TrailsRuntimeException(oe);
324                    }
325            }
326    
327            /**
328             * @param type
329             * @param descriptor
330             * @param parentClassDescriptor
331             * @return
332             */
333            private IIdentifierDescriptor createIdentifierDescriptor(Class type, IPropertyDescriptor descriptor, IClassDescriptor parentClassDescriptor)
334            {
335                    IIdentifierDescriptor identifierDescriptor;
336                    PersistentClass mapping = getMapping(type);
337    
338                    /**
339                     * fix for TRAILS-92
340                     */
341                    if (mapping.getProperty(descriptor.getName()).getType() instanceof ComponentType)
342                    {
343                            EmbeddedDescriptor embeddedDescriptor = buildEmbeddedDescriptor(type,
344                                            mapping.getProperty(descriptor.getName()), descriptor, parentClassDescriptor);
345                            embeddedDescriptor.setIdentifier(true);
346                            identifierDescriptor = embeddedDescriptor;
347                    } else
348                    {
349                            identifierDescriptor = new IdentifierDescriptor(type, descriptor);
350                    }
351    
352                    if (((SimpleValue) mapping.getIdentifier()).getIdentifierGeneratorStrategy().equals("assigned"))
353                    {
354                            identifierDescriptor.setGenerated(false);
355                    }
356    
357                    return identifierDescriptor;
358            }
359    
360            /**
361             * @param type
362             * @return
363             */
364            protected PersistentClass getMapping(Class type)
365            {
366                    Configuration cfg = getLocalSessionFactoryBean().getConfiguration();
367    
368                    return cfg.getClassMapping(type.getName());
369            }
370    
371            /**
372             * @param type
373             * @param newDescriptor
374             */
375            private CollectionDescriptor decorateCollectionDescriptor(Class type, IPropertyDescriptor descriptor,
376                            IClassDescriptor parentClassDescriptor)
377            {
378                    try
379                    {
380                            CollectionDescriptor collectionDescriptor = new CollectionDescriptor(type, descriptor);
381                            org.hibernate.mapping.Collection collectionMapping = findCollectionMapping(type, descriptor.getName());
382                            // It is a child relationship if it has delete-orphan specified in
383                            // the mapping
384                            collectionDescriptor.setChildRelationship(collectionMapping.hasOrphanDelete());
385                            CollectionMetadata collectionMetaData = getSessionFactory().getCollectionMetadata(
386                                            collectionMapping.getRole());
387    
388                            collectionDescriptor.setElementType(collectionMetaData.getElementType().getReturnedClass());
389    
390                            collectionDescriptor.setOneToMany(collectionMapping.isOneToMany());
391    
392                            decorateOneToManyCollection(parentClassDescriptor, collectionDescriptor, collectionMapping);
393    
394                            return collectionDescriptor;
395    
396                    } catch (HibernateException e)
397                    {
398                            throw new TrailsRuntimeException(e);
399                    }
400            }
401    
402            public IPropertyDescriptor decorateAssociationDescriptor(Class type, Property mappingProperty,
403                            IPropertyDescriptor descriptor, IClassDescriptor parentClassDescriptor)
404            {
405                    Type hibernateType = mappingProperty.getType();
406                    Class parentClassType = parentClassDescriptor.getType();
407                    ObjectReferenceDescriptor descriptorReference = new ObjectReferenceDescriptor(type, descriptor, hibernateType
408                                    .getReturnedClass());
409    
410                    try
411                    {
412                            Field propertyField = parentClassType.getDeclaredField(descriptor.getName());
413                            PropertyDescriptor beanPropDescriptor = (PropertyDescriptor) Ognl.getValue(
414                                            "propertyDescriptors.{? name == '" + descriptor.getName() + "'}[0]", Introspector
415                                                            .getBeanInfo(parentClassType));
416                            Method readMethod = beanPropDescriptor.getReadMethod();
417    
418                            // Start by checking for and retrieving mappedBy attribute inside
419                            // the annotation
420                            String inverseProperty = "";
421                            if (readMethod.isAnnotationPresent(javax.persistence.OneToOne.class))
422                            {
423                                    inverseProperty = readMethod.getAnnotation(javax.persistence.OneToOne.class).mappedBy();
424                            } else if (propertyField.isAnnotationPresent(javax.persistence.OneToOne.class))
425                            {
426                                    inverseProperty = propertyField.getAnnotation(javax.persistence.OneToOne.class).mappedBy();
427                            } else
428                            {
429                                    // If there is none then just return the
430                                    // ObjectReferenceDescriptor
431                                    return descriptorReference;
432                            }
433    
434                            if ("".equals(inverseProperty))
435                            {
436                                    // http://forums.hibernate.org/viewtopic.php?t=974287&sid=12d018b08dffe07e263652190cfc4e60
437                                    // Caution... this does not support multiple
438                                    // class references across the OneToOne relationship
439                                    Class returnType = readMethod.getReturnType();
440                                    for (int i = 0; i < returnType.getDeclaredMethods().length; i++)
441                                    {
442                                            if (returnType.getDeclaredMethods()[i].getReturnType().equals(propertyField.getDeclaringClass()))
443                                            {
444                                                    Method theProperty = returnType.getDeclaredMethods()[i];
445                                                    /* strips preceding 'get' */
446                                                    inverseProperty = theProperty.getName().substring(3).toLowerCase();
447                                                    break;
448                                            }
449                                    }
450                            }
451    
452                    } catch (SecurityException e)
453                    {
454                            LOG.error(e.getMessage());
455                    } catch (NoSuchFieldException e)
456                    {
457                            LOG.error(e.getMessage());
458                    } catch (OgnlException e)
459                    {
460                            LOG.error(e.getMessage());
461                    } catch (IntrospectionException e)
462                    {
463                            LOG.error(e.getMessage());
464                    }
465                    return descriptorReference;
466            }
467    
468            /**
469             * I couldn't find a way to get the "mappedBy" value from the collection
470             * metadata, so I'm getting it from the OneToMany annotation.
471             */
472            private void decorateOneToManyCollection(IClassDescriptor parentClassDescriptor,
473                            CollectionDescriptor collectionDescriptor, org.hibernate.mapping.Collection collectionMapping)
474            {
475                    Class type = parentClassDescriptor.getType();
476                    if (collectionDescriptor.isOneToMany() && collectionMapping.isInverse())
477                    {
478                            try
479                            {
480    
481                                    Field propertyField = type.getDeclaredField(collectionDescriptor.getName());
482                                    PropertyDescriptor beanPropDescriptor = (PropertyDescriptor) Ognl.getValue(
483                                                    "propertyDescriptors.{? name == '" + collectionDescriptor.getName() + "'}[0]", Introspector
484                                                                    .getBeanInfo(type));
485                                    Method readMethod = beanPropDescriptor.getReadMethod();
486                                    String mappedBy = "";
487                                    if (readMethod.isAnnotationPresent(javax.persistence.OneToMany.class))
488                                    {
489                                            mappedBy = readMethod.getAnnotation(javax.persistence.OneToMany.class).mappedBy();
490                                    } else if (propertyField.isAnnotationPresent(javax.persistence.OneToMany.class))
491                                    {
492                                            mappedBy = propertyField.getAnnotation(javax.persistence.OneToMany.class).mappedBy();
493                                    }
494    
495                                    if (!"".equals(mappedBy))
496                                    {
497                                            collectionDescriptor.setInverseProperty(mappedBy);
498                                    }
499    
500                                    parentClassDescriptor.setHasCyclicRelationships(true);
501    
502                            } catch (SecurityException e)
503                            {
504                                    LOG.error(e.getMessage());
505                            } catch (NoSuchFieldException e)
506                            {
507                                    LOG.error(e.getMessage());
508                            } catch (OgnlException e)
509                            {
510                                    LOG.error(e.getMessage());
511                            } catch (IntrospectionException e)
512                            {
513                                    LOG.error(e.getMessage());
514                            }
515                    }
516            }
517    
518            protected org.hibernate.mapping.Collection findCollectionMapping(Class type, String name)
519            {
520                    String roleName = type.getName() + "." + name;
521                    org.hibernate.mapping.Collection collectionMapping = getLocalSessionFactoryBean().getConfiguration()
522                                    .getCollectionMapping(roleName);
523                    if (collectionMapping != null)
524                    {
525                            return collectionMapping;
526                    } else if (!type.equals(Object.class))
527                    {
528                            return findCollectionMapping(type.getSuperclass(), name);
529                    } else
530                    {
531                            throw new MetadataNotFoundException("Metadata not found.");
532                    }
533    
534            }
535    
536            /*
537             * (non-Javadoc)
538             * 
539             * @see org.trails.descriptor.PropertyDescriptorService#getIdentifierProperty(java.lang.Class)
540             */
541            public String getIdentifierProperty(Class type)
542            {
543                    try
544                    {
545                            return getSessionFactory().getClassMetadata(type).getIdentifierPropertyName();
546                    } catch (HibernateException e)
547                    {
548                            throw new TrailsRuntimeException(e);
549                    }
550            }
551    
552            /**
553             * @return Returns the sessionFactory.
554             */
555            public SessionFactory getSessionFactory()
556            {
557                    return (SessionFactory) getLocalSessionFactoryBean().getObject();
558            }
559    
560            public IClassDescriptor getClassDescriptor(Class type)
561            {
562                    return descriptors.get(type);
563            }
564    
565            /**
566             * @return Returns the localSessionFactoryBean.
567             */
568            public LocalSessionFactoryBean getLocalSessionFactoryBean()
569            {
570                    return localSessionFactoryBean;
571            }
572    
573            /**
574             * @param localSessionFactoryBean
575             *            The localSessionFactoryBean to set.
576             */
577            public void setLocalSessionFactoryBean(LocalSessionFactoryBean localSessionFactoryBean)
578            {
579                    this.localSessionFactoryBean = localSessionFactoryBean;
580            }
581    
582            /*
583             * (non-Javadoc)
584             * 
585             * @see org.trails.descriptor.TrailsDescriptorService#getAllDescriptors()
586             */
587            public List<IClassDescriptor> getAllDescriptors()
588            {
589                    return new ArrayList<IClassDescriptor>(descriptors.values());
590            }
591    
592            public List getTypes()
593            {
594                    return types;
595            }
596    
597            public void setTypes(List types)
598            {
599                    this.types = types;
600            }
601    
602            public int getLargeColumnLength()
603            {
604                    return largeColumnLength;
605            }
606    
607            /**
608             * Columns longer than this will have their large property set to true.
609             * 
610             * @param largeColumnLength
611             */
612            public void setLargeColumnLength(int largeColumnLength)
613            {
614                    this.largeColumnLength = largeColumnLength;
615            }
616    
617            public DescriptorFactory getDescriptorFactory()
618            {
619                    return descriptorFactory;
620            }
621    
622            public void setDescriptorFactory(DescriptorFactory descriptorFactory)
623            {
624                    this.descriptorFactory = descriptorFactory;
625            }
626    
627            public void setIgnoreNonHibernateTypes(boolean ignoreNonHibernateTypes)
628            {
629                    this.ignoreNonHibernateTypes = ignoreNonHibernateTypes;
630            }
631    }