001: /*
002: * Copyright 2001-2007 Geert Bevin <gbevin[remove] at uwyn dot com> and
003: * JR Boyens <gnu-jrb[remove] at gmx dot net>
004: * Distributed under the terms of either:
005: * - the common development and distribution license (CDDL), v1.0; or
006: * - the GNU Lesser General Public License, v2.1 or later
007: * $Id: GenericQueryManagerRelationalUtils.java 3760 2007-05-14 13:37:33Z gbevin $
008: */
009: package com.uwyn.rife.database.querymanagers.generic;
010:
011: import com.uwyn.rife.database.querymanagers.generic.exceptions.*;
012: import java.util.*;
013:
014: import com.uwyn.rife.database.exceptions.DatabaseException;
015: import com.uwyn.rife.site.Constrained;
016: import com.uwyn.rife.site.ConstrainedProperty;
017: import com.uwyn.rife.site.ConstrainedUtils;
018: import com.uwyn.rife.tools.BeanPropertyProcessor;
019: import com.uwyn.rife.tools.BeanUtils;
020: import com.uwyn.rife.tools.ClassUtils;
021: import com.uwyn.rife.tools.JavaSpecificationUtils;
022: import com.uwyn.rife.tools.exceptions.BeanUtilsException;
023: import java.beans.PropertyDescriptor;
024: import java.lang.reflect.InvocationTargetException;
025: import java.lang.reflect.Method;
026:
027: /**
028: * Utility class to provide many-to-many and many-to-one relational
029: * capabilities to generic query manager implementations.
030: *
031: * @author Geert Bevin (gbevin[remove] at uwyn dot com)
032: * @version $Revision: 3760 $
033: * @since 1.6
034: */
035: public abstract class GenericQueryManagerRelationalUtils {
036: /**
037: * Restores a constrained many-to-one property value that is lazily loaded.
038: *
039: * @param manager the {@code GenericQueryManager} that will be used to
040: * restore the related bean instance
041: * @param constrained the constrained bean instance that contains the
042: * property whose value will be restored
043: * @param propertyName the name of the property value that will be restored
044: * @param propertyTypeClassName the class name of the property that will be
045: * restored
046: * @return the value of the property, or
047: * <p>{@code null} if the constrained property doesn't exists or if it
048: * didn't have the {@code manyToOne} constraint
049: * @since 1.6
050: */
051: public static Object restoreLazyManyToOneProperty(
052: GenericQueryManager manager, Constrained constrained,
053: String propertyName, String propertyTypeClassName) {
054: Object result = null;
055:
056: ConstrainedProperty property = constrained
057: .getConstrainedProperty(propertyName);
058:
059: // only lazily load the property value if a constrained property has been found
060: if (property != null) {
061: // only consider a constrained property with a many-to-one constraint
062: if (property.hasManyToOne()) {
063: // try to obtain the class for the property's type
064: Class return_type = null;
065: try {
066: return_type = Class.forName(propertyTypeClassName);
067: } catch (ClassNotFoundException e) {
068: throw new RuntimeException(e);
069: }
070:
071: // obtain the associated class from the many-to-one constraint
072: Class associated_class = property.getManyToOne()
073: .getAssociatedClass();
074:
075: // if the associated class wasn't specified, use the property's type
076: if (null == associated_class) {
077: associated_class = return_type;
078: } else {
079: // ensure that the specified associated class is compatible with the property's type
080: if (!return_type.isAssignableFrom(associated_class)) {
081: throw new IncompatibleManyToOneValueTypeException(
082: manager.getBaseClass(), propertyName,
083: return_type, associated_class);
084: }
085: }
086:
087: // retrieve the entity from the database for this property
088: ManyToOneDeclaration declaration = createManyToOneDeclaration(
089: manager, property, return_type);
090: if (!declaration.isBasic()) {
091: String column_name = generateManyToOneJoinColumnName(
092: property.getPropertyName(), declaration);
093: result = restoreManyToOneProperty(manager,
094: declaration.getAssociationManager(),
095: column_name, associated_class);
096: }
097: }
098: }
099:
100: return result;
101: }
102:
103: public static Object restoreManyToOneProperty(
104: GenericQueryManager manager,
105: GenericQueryManager associationManager, String columnName,
106: Class propertyType) {
107: RestoreQuery query = associationManager
108: .getRestoreQuery()
109: .fields(associationManager.getTable(), propertyType)
110: .join(manager.getTable())
111: .where(
112: associationManager.getTable()
113: + "."
114: + associationManager
115: .getIdentifierName() + " = "
116: + manager.getTable() + "." + columnName);
117: return associationManager.restoreFirst(query);
118: }
119:
120: public static ManyToOneDeclaration createManyToOneDeclaration(
121: GenericQueryManager manager, ConstrainedProperty property,
122: Class propertyType)
123: throws IncompatibleManyToOneValueTypeException {
124: ManyToOneDeclaration declaration = null;
125:
126: if (property != null && property.hasManyToOne()) {
127: ConstrainedProperty.ManyToOne many_to_one = property
128: .getManyToOne();
129:
130: declaration = new ManyToOneDeclaration().associationType(
131: many_to_one.getAssociatedClass())
132: .associationColumn(many_to_one.getColumn());
133:
134: // fall back to the associated class in case the property type wasn't provided
135: if (null == propertyType) {
136: propertyType = declaration.getAssociationType();
137: }
138:
139: // detect whether the property is a basic type, or if its type is unknown
140: // it will be treated as a primary key and not an object instance of data that's
141: // stored in the associated table
142: if (null == propertyType
143: || ClassUtils.isBasic(propertyType)) {
144: declaration.isBasic(true).associationTable(
145: many_to_one.getDerivedTable());
146: } else {
147: declaration.isBasic(false).associationTable(
148: many_to_one.getTable());
149:
150: // retrieve the class that has been associated to the property through constraints
151: Class associated_class = declaration
152: .getAssociationType();
153:
154: // if an associated class has already been specified through constraints,
155: // ensure that it's compatible with the type of the property
156: if (associated_class != null) {
157: if (!propertyType
158: .isAssignableFrom(associated_class)) {
159: throw new IncompatibleManyToOneValueTypeException(
160: manager.getBaseClass(), property
161: .getName(), propertyType,
162: associated_class);
163: }
164: }
165: // since no associated class has been specified yet, use the property type
166: else {
167: declaration.setAssociationType(propertyType);
168: }
169:
170: // create the association query manager
171: GenericQueryManager association_manager = manager
172: .createNewManager(declaration
173: .getAssociationType());
174: declaration.setAssociationManager(association_manager);
175:
176: // determine the association table
177: if (null == declaration.getAssociationTable()) {
178: declaration.setAssociationTable(association_manager
179: .getTable());
180: }
181:
182: // determine the association column name
183: if (null == declaration.getAssociationColumn()) {
184: declaration
185: .setAssociationColumn(association_manager
186: .getIdentifierName());
187: }
188: }
189: }
190:
191: return declaration;
192: }
193:
194: public static Map<String, ManyToOneDeclaration> obtainManyToOneDeclarations(
195: final GenericQueryManager manager,
196: final Constrained constrained,
197: final String fixedMainProperty,
198: final Class fixedAssocationType) {
199: final Map<String, ManyToOneDeclaration> declarations;
200: if (constrained != null
201: && constrained
202: .hasPropertyConstraint(ConstrainedProperty.MANY_TO_ONE)) {
203: declarations = new HashMap<String, ManyToOneDeclaration>();
204:
205: // collect all properties that have a many-to-one relationship
206: final List<String> property_names = new ArrayList<String>();
207: if (fixedMainProperty != null) {
208: ConstrainedProperty property = constrained
209: .getConstrainedProperty(fixedMainProperty);
210: if (property.hasManyToOne()) {
211: property_names.add(property.getPropertyName());
212: }
213: } else {
214: for (ConstrainedProperty property : (Collection<ConstrainedProperty>) constrained
215: .getConstrainedProperties()) {
216: if (property.hasManyToOne()) {
217: property_names.add(property.getPropertyName());
218: }
219: }
220: }
221:
222: // obtain the actual bean properties for the many-to-one relationships
223: if (property_names.size() > 0) {
224: String[] unresolvednamearray = new String[property_names
225: .size()];
226: property_names.toArray(unresolvednamearray);
227: try {
228: BeanUtils.processProperties(manager.getBaseClass(),
229: unresolvednamearray, null, null,
230: new BeanPropertyProcessor() {
231: public boolean gotProperty(String name,
232: PropertyDescriptor descriptor)
233: throws IllegalAccessException,
234: IllegalArgumentException,
235: InvocationTargetException {
236: ConstrainedProperty property = constrained
237: .getConstrainedProperty(name);
238: ManyToOneDeclaration declaration = createManyToOneDeclaration(
239: manager, property,
240: descriptor.getReadMethod()
241: .getReturnType());
242: if (declaration != null) {
243: if (null == fixedAssocationType
244: || fixedAssocationType == declaration
245: .getAssociationType()) {
246: declarations.put(property
247: .getPropertyName(),
248: declaration);
249:
250: if (fixedAssocationType != null) {
251: return false;
252: }
253: }
254: }
255:
256: return true;
257: }
258: });
259: } catch (BeanUtilsException e) {
260: throw new DatabaseException(e);
261: }
262: }
263: } else {
264: declarations = null;
265: }
266:
267: return declarations;
268: }
269:
270: public static Map<String, ManyToManyDeclaration> obtainManyToManyDeclarations(
271: final GenericQueryManager manager, Constrained constrained,
272: boolean includeAssociations) {
273: final Map<String, ManyToManyDeclaration> declarations;
274: if (constrained != null
275: && (constrained
276: .hasPropertyConstraint(ConstrainedProperty.MANY_TO_MANY) || includeAssociations
277: && constrained
278: .hasPropertyConstraint(ConstrainedProperty.MANY_TO_MANY_ASSOCIATION))) {
279: declarations = new HashMap<String, ManyToManyDeclaration>();
280:
281: // collect all properties that have a many-to-many relationship
282: final List<String> unresolvednamelist = new ArrayList<String>();
283: for (ConstrainedProperty property : (Collection<ConstrainedProperty>) constrained
284: .getConstrainedProperties()) {
285: if (property.hasManyToMany()) {
286: declarations.put(property.getPropertyName(),
287: new ManyToManyDeclaration()
288: .associationType(property
289: .getManyToMany()
290: .getAssociatedClass()));
291: unresolvednamelist.add(property.getPropertyName());
292: } else if (includeAssociations
293: && property.hasManyToManyAssociation()) {
294: declarations
295: .put(
296: property.getPropertyName(),
297: new ManyToManyDeclaration()
298: .reversed(true)
299: .associationType(
300: property
301: .getManyToManyAssociation()
302: .getAssociatedClass()));
303: unresolvednamelist.add(property.getPropertyName());
304: }
305: }
306:
307: // obtain the actual bean properties for the many-to-many relationships
308: if (unresolvednamelist.size() > 0) {
309: String[] unresolvednamearray = new String[unresolvednamelist
310: .size()];
311: unresolvednamelist.toArray(unresolvednamearray);
312: try {
313: BeanUtils.processProperties(manager.getBaseClass(),
314: unresolvednamearray, null, null,
315: new BeanPropertyProcessor() {
316: public boolean gotProperty(String name,
317: PropertyDescriptor descriptor)
318: throws IllegalAccessException,
319: IllegalArgumentException,
320: InvocationTargetException {
321: Method read_method = descriptor
322: .getReadMethod();
323:
324: ManyToManyDeclaration declaration = declarations
325: .get(name);
326:
327: // make sure that the many-to-many property has a supported collection type
328: Class return_type = read_method
329: .getReturnType();
330: ensureSupportedManyToManyPropertyCollectionType(
331: manager.getBaseClass(),
332: name, return_type);
333: declaration
334: .setCollectionType(return_type);
335:
336: // check if the class of the relationship has already been set, otherwise detect it from
337: // the generic information that's available in the collection
338: if (null == declaration
339: .getAssociationType()) {
340: Class associated_class = null;
341: if (JavaSpecificationUtils
342: .isAtLeastJdk15()) {
343: try {
344: Class klass = Class
345: .forName("com.uwyn.rife.database.querymanagers.generic.GenericTypeDetector");
346: Method method = klass
347: .getDeclaredMethod(
348: "detectAssociatedClass",
349: Method.class);
350: associated_class = (Class) method
351: .invoke(null,
352: read_method);
353: } catch (Exception e) {
354: throw new DatabaseException(
355: e);
356: }
357: }
358:
359: if (null == associated_class) {
360: throw new MissingManyToManyTypeInformationException(
361: manager
362: .getBaseClass(),
363: name);
364: }
365:
366: declaration
367: .setAssociationType(associated_class);
368: }
369:
370: return false;
371: }
372: });
373: } catch (BeanUtilsException e) {
374: throw new DatabaseException(e);
375: }
376: }
377: } else {
378: declarations = null;
379: }
380:
381: return declarations;
382: }
383:
384: public static Map<String, ManyToOneAssociationDeclaration> obtainManyToOneAssociationDeclarations(
385: final GenericQueryManager manager, Constrained constrained) {
386: final Map<String, ManyToOneAssociationDeclaration> declarations;
387: if (constrained != null
388: && constrained
389: .hasPropertyConstraint(ConstrainedProperty.MANY_TO_ONE_ASSOCIATION)) {
390: declarations = new HashMap<String, ManyToOneAssociationDeclaration>();
391:
392: // collect all properties that have a many-to-one association relationship
393: final List<String> unresolvednamelist = new ArrayList<String>();
394: for (ConstrainedProperty property : (Collection<ConstrainedProperty>) constrained
395: .getConstrainedProperties()) {
396: if (property.hasManyToOneAssociation()) {
397: ConstrainedProperty.ManyToOneAssociation association = property
398: .getManyToOneAssociation();
399: declarations
400: .put(
401: property.getPropertyName(),
402: new ManyToOneAssociationDeclaration()
403: .mainType(
404: association
405: .getMainClass())
406: .mainProperty(
407: association
408: .getMainProperty()));
409: unresolvednamelist.add(property.getPropertyName());
410: }
411: }
412:
413: // obtain the actual bean properties for the many-to-one association relationships
414: if (unresolvednamelist.size() > 0) {
415: String[] unresolvednamearray = new String[unresolvednamelist
416: .size()];
417: unresolvednamelist.toArray(unresolvednamearray);
418: try {
419: BeanUtils.processProperties(manager.getBaseClass(),
420: unresolvednamearray, null, null,
421: new BeanPropertyProcessor() {
422: public boolean gotProperty(String name,
423: PropertyDescriptor descriptor)
424: throws IllegalAccessException,
425: IllegalArgumentException,
426: InvocationTargetException {
427: Method read_method = descriptor
428: .getReadMethod();
429:
430: ManyToOneAssociationDeclaration declaration = declarations
431: .get(name);
432:
433: // make sure that the many-to-one association property has a supported collection type
434: Class return_type = read_method
435: .getReturnType();
436: ensureSupportedManyToOneAssociationPropertyCollectionType(
437: manager.getBaseClass(),
438: name, return_type);
439: declaration
440: .setCollectionType(return_type);
441:
442: // check if the class of the relationship has already been set, otherwise detect it from
443: // the generic information that's available in the collection
444: if (null == declaration
445: .getMainType()) {
446: Class associated_class = null;
447: if (JavaSpecificationUtils
448: .isAtLeastJdk15()) {
449: try {
450: Class klass = Class
451: .forName("com.uwyn.rife.database.querymanagers.generic.GenericTypeDetector");
452: Method method = klass
453: .getDeclaredMethod(
454: "detectAssociatedClass",
455: Method.class);
456: associated_class = (Class) method
457: .invoke(null,
458: read_method);
459: } catch (Exception e) {
460: throw new DatabaseException(
461: e);
462: }
463: }
464:
465: if (null == associated_class) {
466: throw new MissingManyToOneAssociationTypeInformationException(
467: manager
468: .getBaseClass(),
469: name);
470: }
471:
472: declaration
473: .setMainType(associated_class);
474: }
475:
476: // obtain the main declaration
477: Constrained main_constrained = ConstrainedUtils
478: .getConstrainedInstance(declaration
479: .getMainType());
480: GenericQueryManager main_manager = manager
481: .createNewManager(declaration
482: .getMainType());
483: Map<String, ManyToOneDeclaration> main_declarations = obtainManyToOneDeclarations(
484: main_manager,
485: main_constrained,
486: declaration
487: .getMainProperty(),
488: manager.getBaseClass());
489: if (null == main_declarations
490: || 0 == main_declarations
491: .size()) {
492: throw new MissingManyToOneMainPropertyException(
493: manager.getBaseClass(),
494: name, declaration
495: .getMainType());
496: } else {
497: Map.Entry<String, ManyToOneDeclaration> main_entry = main_declarations
498: .entrySet().iterator()
499: .next();
500: declaration
501: .mainProperty(
502: main_entry
503: .getKey())
504: .mainDeclaration(
505: main_entry
506: .getValue());
507: }
508:
509: return false;
510: }
511: });
512: } catch (BeanUtilsException e) {
513: throw new DatabaseException(e);
514: }
515: }
516: } else {
517: declarations = null;
518: }
519:
520: return declarations;
521: }
522:
523: public static String generateManyToManyJoinTableName(
524: ManyToManyDeclaration assocation,
525: GenericQueryManager manager1, GenericQueryManager manager2) {
526: if (assocation.isReversed()) {
527: return manager2.getTable() + "_" + manager1.getTable();
528: } else {
529: return manager1.getTable() + "_" + manager2.getTable();
530: }
531: }
532:
533: public static String generateManyToManyJoinColumnName(
534: GenericQueryManager manager) {
535: return manager.getTable() + "_" + manager.getIdentifierName();
536: }
537:
538: public static String generateManyToOneJoinColumnName(
539: String mainPropertyName, ManyToOneDeclaration declaration) {
540: return mainPropertyName + "_"
541: + declaration.getAssociationColumn();
542: }
543:
544: public static void processManyToOneJoinColumns(
545: GenericQueryManager manager,
546: ManyToOneJoinColumnProcessor processor) {
547: if (null == processor) {
548: return;
549: }
550:
551: // generate many-to-one join columns
552: Constrained constrained = ConstrainedUtils
553: .getConstrainedInstance(manager.getBaseClass());
554: if (constrained != null
555: && constrained
556: .hasPropertyConstraint(ConstrainedProperty.MANY_TO_ONE)) {
557: Map<String, ManyToOneDeclaration> manytoone_declarations = obtainManyToOneDeclarations(
558: manager, constrained, null, null);
559: if (manytoone_declarations != null) {
560: // iterate over all the many-to-one relationships that have associated classes
561: for (Map.Entry<String, ManyToOneDeclaration> entry : manytoone_declarations
562: .entrySet()) {
563: ManyToOneDeclaration declaration = entry.getValue();
564: if (!declaration.isBasic()) {
565: String column_name = generateManyToOneJoinColumnName(
566: entry.getKey(), declaration);
567: if (!processor.processJoinColumn(column_name,
568: entry.getKey(), declaration)) {
569: break;
570: }
571: }
572: }
573: }
574: }
575: }
576:
577: public static void ensureSupportedManyToManyPropertyCollectionType(
578: Class beanClass, String propertyName, Class propertyType)
579: throws UnsupportedManyToManyPropertyTypeException {
580: if (!(propertyType == Collection.class
581: || propertyType == Set.class || propertyType == List.class)) {
582: throw new UnsupportedManyToManyPropertyTypeException(
583: beanClass, propertyName, propertyType);
584: }
585: }
586:
587: public static void ensureSupportedManyToManyPropertyValueType(
588: Class beanClass, String propertyName, Object propertyValue)
589: throws UnsupportedManyToManyPropertyTypeException {
590: if (!(propertyValue instanceof Collection)) {
591: throw new UnsupportedManyToManyValueTypeException(
592: beanClass, propertyName, propertyValue);
593: }
594: }
595:
596: public static void ensureSupportedManyToOneAssociationPropertyCollectionType(
597: Class beanClass, String propertyName, Class propertyType)
598: throws UnsupportedManyToManyPropertyTypeException {
599: if (!(propertyType == Collection.class
600: || propertyType == Set.class || propertyType == List.class)) {
601: throw new UnsupportedManyToOneAssociationPropertyTypeException(
602: beanClass, propertyName, propertyType);
603: }
604: }
605:
606: public static void ensureSupportedManyToOneAssociationPropertyValueType(
607: Class beanClass, String propertyName, Object propertyValue)
608: throws UnsupportedManyToManyPropertyTypeException {
609: if (!(propertyValue instanceof Collection)) {
610: throw new UnsupportedManyToOneValueTypeException(beanClass,
611: propertyName, propertyValue);
612: }
613: }
614: }
|