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.tynamo.hibernate.services;
013
014 import ognl.Ognl;
015 import ognl.OgnlException;
016 import org.apache.commons.logging.Log;
017 import org.apache.commons.logging.LogFactory;
018 import org.apache.tapestry5.hibernate.HibernateSessionSource;
019 import org.hibernate.HibernateException;
020 import org.hibernate.cfg.Configuration;
021 import org.hibernate.mapping.*;
022 import org.hibernate.metadata.ClassMetadata;
023 import org.hibernate.metadata.CollectionMetadata;
024 import org.hibernate.type.ComponentType;
025 import org.hibernate.type.Type;
026 import org.tynamo.descriptor.*;
027 import org.tynamo.descriptor.extension.EnumReferenceDescriptor;
028 import org.tynamo.exception.MetadataNotFoundException;
029 import org.tynamo.exception.TynamoRuntimeException;
030
031 import java.beans.IntrospectionException;
032 import java.beans.Introspector;
033 import java.beans.PropertyDescriptor;
034 import java.lang.reflect.Field;
035 import java.lang.reflect.Method;
036 import java.util.ArrayList;
037 import java.util.Collection;
038 import java.util.Iterator;
039 import java.util.List;
040
041 /**
042 * This decorator will add metadata information. It will replace simple
043 * reflection based TynamoPropertyTynamoPropertyDescriptors with appropriate
044 * Hibernate descriptors <p/> Background... TynamoDescriptorService operates one
045 * ReflectorDescriptorFactory - TynamoDescriptorService iterates/scans all class
046 * types encountered - ReflectorDescriptorFactory allocates property descriptor
047 * instance for the class type - TynamoDescriptorService decorates property
048 * descriptor by calling this module HibernateDescriptorDecorator -
049 * HibernateDescriptorDecorator caches the decorated property descriptor into a
050 * decorated descriptor list - decorated descriptor list gets populated into
051 * class descriptor for class type - TynamoDescriptorService finally populates
052 * decorated class descriptor and it's aggregated list of decorated property
053 * descriptors into it's own list/cache of referenced class descriptors
054 *
055 * @see TynamoPropertyDescriptor
056 * @see ObjectReferenceDescriptor
057 * @see CollectionDescriptor
058 * @see EmbeddedDescriptor
059 */
060 public class HibernateDescriptorDecorator implements DescriptorDecorator
061 {
062 protected static final Log LOG = LogFactory.getLog(HibernateDescriptorDecorator.class);
063
064 private HibernateSessionSource hibernateSessionSource;
065
066 private DescriptorFactory descriptorFactory;
067
068 /**
069 * Columns longer than this will have their large property set to true.
070 */
071 private final int largeColumnLength;
072
073 private final boolean ignoreNonHibernateTypes;
074
075 public HibernateDescriptorDecorator(HibernateSessionSource hibernateSessionSource, DescriptorFactory descriptorFactory, int largeColumnLength, boolean ignoreNonHibernateTypes)
076 {
077 this.hibernateSessionSource = hibernateSessionSource;
078 this.descriptorFactory = descriptorFactory;
079 this.largeColumnLength = largeColumnLength;
080 this.ignoreNonHibernateTypes = ignoreNonHibernateTypes;
081 }
082
083 public TynamoClassDescriptor decorate(TynamoClassDescriptor descriptor)
084 {
085 ArrayList<TynamoPropertyDescriptor> decoratedPropertyDescriptors = new ArrayList<TynamoPropertyDescriptor>();
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 LOG.warn("MetadataNotFound! Ignoring:" + descriptor.getType().toString());
097 return descriptor;
098 } else {
099 throw new TynamoRuntimeException(e);
100 }
101 }
102
103 for (TynamoPropertyDescriptor propertyDescriptor : descriptor.getPropertyDescriptors())
104 {
105 try
106 {
107 TynamoPropertyDescriptor descriptorReference;
108
109 if (propertyDescriptor.getName().equals(getIdentifierProperty(type)))
110 {
111 descriptorReference = createIdentifierDescriptor(type, propertyDescriptor, descriptor);
112 } else if (notAHibernateProperty(classMetaData, propertyDescriptor))
113 {
114 // If this is not a hibernate property (i.e. marked
115 // Transient), it's certainly not searchable
116 // Are the any other properties like this?
117 propertyDescriptor.setSearchable(false);
118 descriptorReference = propertyDescriptor;
119 } else
120 {
121 Property mappingProperty = getMapping(type).getProperty(propertyDescriptor.getName());
122 descriptorReference = decoratePropertyDescriptor(type, mappingProperty, propertyDescriptor,
123 descriptor);
124 }
125
126 decoratedPropertyDescriptors.add(descriptorReference);
127
128 } catch (HibernateException e)
129 {
130 throw new TynamoRuntimeException(e);
131 }
132 }
133 descriptor.setPropertyDescriptors(decoratedPropertyDescriptors);
134 return descriptor;
135 }
136
137 protected TynamoPropertyDescriptor decoratePropertyDescriptor(Class type, Property mappingProperty,
138 TynamoPropertyDescriptor descriptor, TynamoClassDescriptor parentClassDescriptor)
139 {
140 if (isFormula(mappingProperty))
141 {
142 descriptor.setReadOnly(true);
143 return descriptor;
144 }
145 descriptor.setLength(findColumnLength(mappingProperty));
146 descriptor.setLarge(isLarge(mappingProperty));
147 if (!mappingProperty.isOptional())
148 {
149 descriptor.setRequired(true);
150 }
151
152 if (!mappingProperty.isInsertable() && !mappingProperty.isUpdateable())
153 {
154 descriptor.setReadOnly(true);
155 }
156
157 TynamoPropertyDescriptor descriptorReference = descriptor;
158 Type hibernateType = mappingProperty.getType();
159 if (mappingProperty.getType() instanceof ComponentType)
160 {
161 descriptorReference = buildEmbeddedDescriptor(type, mappingProperty, descriptor, parentClassDescriptor);
162 } else if (Collection.class.isAssignableFrom(descriptor.getPropertyType()))
163 {
164 descriptorReference = decorateCollectionDescriptor(type, descriptor, parentClassDescriptor);
165 } else if (hibernateType.isAssociationType())
166 {
167 descriptorReference = decorateAssociationDescriptor(type, mappingProperty, descriptor,
168 parentClassDescriptor);
169 } else if (hibernateType.getReturnedClass().isEnum())
170 {
171 descriptor.addExtension(EnumReferenceDescriptor.class.getName(), new EnumReferenceDescriptor(hibernateType
172 .getReturnedClass()));
173 }
174
175 return descriptorReference;
176 }
177
178 private EmbeddedDescriptor buildEmbeddedDescriptor(Class type, Property mappingProperty,
179 TynamoPropertyDescriptor descriptor, TynamoClassDescriptor parentClassDescriptor)
180 {
181 Component componentMapping = (Component) mappingProperty.getValue();
182 TynamoClassDescriptor baseDescriptor = descriptorFactory.buildClassDescriptor(descriptor.getPropertyType());
183 // build from base descriptor
184 EmbeddedDescriptor embeddedDescriptor = new EmbeddedDescriptor(type, baseDescriptor);
185 // and copy from property descriptor
186 embeddedDescriptor.copyFrom(descriptor);
187 ArrayList<TynamoPropertyDescriptor> decoratedProperties = new ArrayList<TynamoPropertyDescriptor>();
188 // go thru each property and decorate it with Hibernate info
189 for (TynamoPropertyDescriptor propertyDescriptor : embeddedDescriptor.getPropertyDescriptors())
190 {
191 if (notAHibernateProperty(componentMapping, propertyDescriptor))
192 {
193 decoratedProperties.add(propertyDescriptor);
194 } else
195 {
196 Property property = componentMapping.getProperty(propertyDescriptor.getName());
197 TynamoPropertyDescriptor TynamoPropertyDescriptor = decoratePropertyDescriptor(embeddedDescriptor.getBeanType(),
198 property, propertyDescriptor, parentClassDescriptor);
199 decoratedProperties.add(TynamoPropertyDescriptor);
200 }
201 }
202 embeddedDescriptor.setPropertyDescriptors(decoratedProperties);
203 return embeddedDescriptor;
204 }
205
206 /**
207 * The default way to order our property descriptors is by the order they
208 * appear in the hibernate config, with id first. Any non-mapped properties
209 * are tacked on at the end, til I think of a better way.
210 *
211 * @param propertyDescriptors
212 * @return
213 */
214 protected List sortPropertyDescriptors(Class type, List propertyDescriptors)
215 {
216 ArrayList sortedPropertyDescriptors = new ArrayList();
217
218 try
219 {
220 sortedPropertyDescriptors.add(Ognl.getValue("#this.{? identifier == true}[0]", propertyDescriptors));
221 for (Iterator iter = getMapping(type).getPropertyIterator(); iter.hasNext();)
222 {
223 Property mapping = (Property) iter.next();
224 sortedPropertyDescriptors.addAll((List) Ognl.getValue("#this.{ ? name == \"" + mapping.getName()
225 + "\"}", propertyDescriptors));
226 }
227 } catch (Exception ex)
228 {
229 throw new TynamoRuntimeException(ex);
230 }
231 return sortedPropertyDescriptors;
232 }
233
234 /**
235 * Find the Hibernate metadata for this type, traversing up the hierarchy to
236 * supertypes if necessary
237 *
238 * @param type
239 * @return
240 */
241 protected ClassMetadata findMetadata(Class type) throws MetadataNotFoundException
242 {
243 ClassMetadata metaData = hibernateSessionSource.getSessionFactory().getClassMetadata(type);
244 if (metaData != null)
245 {
246 return metaData;
247 }
248 if (!type.equals(Object.class))
249 {
250 return findMetadata(type.getSuperclass());
251 } else
252 {
253 throw new MetadataNotFoundException("Failed to find metadata.");
254 }
255 }
256
257 private boolean isFormula(Property mappingProperty)
258 {
259 for (Iterator iter = mappingProperty.getColumnIterator(); iter.hasNext();)
260 {
261 Selectable selectable = (Selectable) iter.next();
262 if (selectable.isFormula())
263 {
264 return true;
265 }
266 }
267 return false;
268 }
269
270 /**
271 * Checks to see if a property descriptor is in a component mapping
272 *
273 * @param componentMapping
274 * @param propertyDescriptor
275 * @return true if the propertyDescriptor property is in componentMapping
276 */
277 protected boolean notAHibernateProperty(Component componentMapping, TynamoPropertyDescriptor propertyDescriptor)
278 {
279 for (Iterator iter = componentMapping.getPropertyIterator(); iter.hasNext();)
280 {
281 Property property = (Property) iter.next();
282 if (property.getName().equals(propertyDescriptor.getName()))
283 {
284 return false;
285 }
286 }
287 return true;
288 }
289
290 private boolean isLarge(Property mappingProperty)
291 {
292 // Hack to avoid setting large property if length
293 // is exactly equal to Hibernate default column length
294 return findColumnLength(mappingProperty) != Column.DEFAULT_LENGTH
295 && findColumnLength(mappingProperty) > largeColumnLength;
296 }
297
298 private int findColumnLength(Property mappingProperty)
299 {
300 int length = 0;
301 for (Iterator iter = mappingProperty.getColumnIterator(); iter.hasNext();)
302 {
303 Column column = (Column) iter.next();
304 length += column.getLength();
305 }
306 return length;
307 }
308
309 /**
310 * @param classMetaData
311 * @param descriptor
312 * @return
313 */
314 protected boolean notAHibernateProperty(ClassMetadata classMetaData, TynamoPropertyDescriptor descriptor)
315 {
316 try
317 {
318 return (Boolean) Ognl.getValue("propertyNames.{ ? #this == \"" + descriptor.getName() + "\"}.size() == 0",
319 classMetaData);
320 } catch (OgnlException oe)
321 {
322 throw new TynamoRuntimeException(oe);
323 }
324 }
325
326 /**
327 * @param type
328 * @param descriptor
329 * @param parentClassDescriptor
330 * @return
331 */
332 private IdentifierDescriptor createIdentifierDescriptor(Class type, TynamoPropertyDescriptor descriptor, TynamoClassDescriptor parentClassDescriptor)
333 {
334 IdentifierDescriptor identifierDescriptor;
335 PersistentClass mapping = getMapping(type);
336
337 /**
338 * fix for TRAILS-92
339 */
340 if (mapping.getProperty(descriptor.getName()).getType() instanceof ComponentType)
341 {
342 EmbeddedDescriptor embeddedDescriptor = buildEmbeddedDescriptor(type,
343 mapping.getProperty(descriptor.getName()), descriptor, parentClassDescriptor);
344 embeddedDescriptor.setIdentifier(true);
345 identifierDescriptor = embeddedDescriptor;
346 } else
347 {
348 identifierDescriptor = new IdentifierDescriptorImpl(type, descriptor);
349 }
350
351 if (((SimpleValue) mapping.getIdentifier()).getIdentifierGeneratorStrategy().equals("assigned"))
352 {
353 identifierDescriptor.setGenerated(false);
354 }
355
356 return identifierDescriptor;
357 }
358
359 /**
360 * @param type
361 * @return
362 */
363 protected PersistentClass getMapping(Class type)
364 {
365 Configuration cfg = hibernateSessionSource.getConfiguration();
366
367 return cfg.getClassMapping(type.getName());
368 }
369
370 /**
371 * @param type
372 * @param descriptor
373 * @param parentClassDescriptor
374 */
375 private CollectionDescriptor decorateCollectionDescriptor(Class type, TynamoPropertyDescriptor descriptor,
376 TynamoClassDescriptor 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 = hibernateSessionSource.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 TynamoRuntimeException(e);
399 }
400 }
401
402 public TynamoPropertyDescriptor decorateAssociationDescriptor(Class type, Property mappingProperty,
403 TynamoPropertyDescriptor descriptor, TynamoClassDescriptor 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(TynamoClassDescriptor 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 = hibernateSessionSource.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.tynamo.descriptor.PropertyDescriptorService#getIdentifierProperty(java.lang.Class)
540 */
541 public String getIdentifierProperty(Class type)
542 {
543 try
544 {
545 return hibernateSessionSource.getSessionFactory().getClassMetadata(type).getIdentifierPropertyName();
546 } catch (HibernateException e)
547 {
548 throw new TynamoRuntimeException(e);
549 }
550 }
551 }