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 }