001: package com.bm.introspectors;
002:
003: import java.lang.annotation.Annotation;
004: import java.lang.reflect.Field;
005: import java.util.Set;
006: import java.util.Map.Entry;
007:
008: import javax.persistence.DiscriminatorColumn;
009: import javax.persistence.DiscriminatorType;
010: import javax.persistence.DiscriminatorValue;
011: import javax.persistence.EmbeddedId;
012: import javax.persistence.Entity;
013: import javax.persistence.IdClass;
014: import javax.persistence.Inheritance;
015: import javax.persistence.InheritanceType;
016: import javax.persistence.Table;
017:
018: import org.apache.log4j.Logger;
019:
020: import com.bm.introspectors.relations.EntityReleationInfo;
021: import com.bm.introspectors.relations.GlobalPrimaryKeyStore;
022: import com.bm.introspectors.relations.ManyToOneReleation;
023: import com.bm.utils.AccessType;
024: import com.bm.utils.AccessTypeFinder;
025: import com.bm.utils.IdClassInstanceGen;
026:
027: /**
028: * This class inspects all relevant fields of an entity bean and holds the
029: * information.
030: *
031: * @author Daniel Wiese
032: * @author Istvan Devai
033: * @param <T> -
034: * the type of the class to inspect
035: * @since 07.10.2005
036: */
037: public class EntityBeanIntrospector<T> extends
038: AbstractPersistentClassIntrospector<T> implements
039: Introspector<T> {
040:
041: static final Logger log = Logger
042: .getLogger(EntityBeanIntrospector.class);
043:
044: /** true if the class has an composed pk field. * */
045: private boolean hasPKClass = false;
046:
047: /** the introspector for the embedded class. * */
048: private EmbeddedClassIntrospector<Object> embeddedPKClass = null;
049:
050: /** the table name. * */
051: private String tableName;
052:
053: /** the table name. * */
054: private String schemaName;
055:
056: private boolean hasSchema = false;
057:
058: private Class<?> idClass = null;
059:
060: private final Class<T> toInspect;
061:
062: /** the inheritance type used by the entity class (null when no inheritance used) */
063: private InheritanceType inheritanceStrategy;
064:
065: /** the name of the discriminator (if a single-table inheritance strategy is used) */
066: private String discriminatorName;
067:
068: /** the value of the discriminator for the introspected class (if a single-table inheritance strategy is used) */
069: private String discriminatorValue;
070:
071: /** the type of the discriminator (if a single-table inheritance strategy is used) */
072: private Class discriminatorType;
073:
074: /**
075: * Constructor with the class to inspect.
076: *
077: * @param toInspect -
078: * the class to inspect
079: */
080: public EntityBeanIntrospector(Class<T> toInspect) {
081:
082: this .toInspect = toInspect;
083: Annotation[] classAnnotations = toInspect.getAnnotations();
084: boolean isSessionBean = false;
085: boolean isTableNameSpecified = false;
086: boolean isAccessTypeField = false;
087: Entity entityAnnotation = null;
088:
089: // iterate over the annotations
090: for (Annotation a : classAnnotations) {
091: if (a instanceof Entity) {
092: log.debug("The class to introspect "
093: + toInspect.getCanonicalName()
094: + " is an Entity-Bean");
095: isSessionBean = true;
096: if (AccessTypeFinder.findAccessType(toInspect).equals(
097: AccessType.FIELD)) {
098: isAccessTypeField = true;
099: }
100:
101: entityAnnotation = (Entity) a;
102:
103: } else if (a instanceof Table) {
104: Table table = (Table) a;
105: this .tableName = table.name();
106: this .hasSchema = !table.schema().equals("");
107: this .schemaName = table.schema();
108: isTableNameSpecified = true;
109: } else if (a instanceof IdClass) {
110: this .idClass = ((IdClass) a).value();
111: }
112: }
113:
114: // check for mandatory conditions
115: if (!isSessionBean) {
116: throw new RuntimeException("The class "
117: + toInspect.getSimpleName()
118: + " is not a entity bean");
119: }
120:
121: if (!isTableNameSpecified) {
122: this .tableName = this .generateDefautTableName(toInspect,
123: entityAnnotation);
124: log
125: .debug("The class "
126: + toInspect.getSimpleName()
127: + " doas not specify a table name! Uning default Name: "
128: + this .tableName);
129:
130: }
131:
132: if (isAccessTypeField) {
133: this .processAccessTypeField(toInspect);
134: } else {
135: this .processAccessTypeProperty(toInspect);
136: }
137:
138: // Process Entity inheritance annotations (if any)
139: processInheritance(toInspect);
140:
141: postProcessRelationProperties();
142: }
143:
144: /**
145: * Overide the abstract implementation of this method, to handle with
146: * embedded classes
147: *
148: * @author Daniel Wiese
149: * @since 15.10.2005
150: * @see com.bm.introspectors.AbstractPersistentClassIntrospector#processAccessTypeField(java.lang.Class)
151: */
152: @Override
153: protected void processAccessTypeField(Class<T> toInspect) {
154: // class the super method
155: super .processAccessTypeField(toInspect);
156: // extract meta information
157: Field[] fields = toInspect.getDeclaredFields();
158: for (Field aktField : fields) {
159: // don't introspect fields generated by Hibernate
160: Annotation[] fieldAnnotations = aktField.getAnnotations();
161:
162: // look into the annotations
163: for (Annotation a : fieldAnnotations) {
164: // set the embedded class, if any
165: if (a instanceof EmbeddedId) {
166: this .embeddedPKClass = new EmbeddedClassIntrospector<Object>(
167: new Property(aktField));
168: this .hasPKClass = true;
169:
170: // set the akt field information
171: final Property aktProperty = new Property(aktField);
172: if (this .getPresistentFieldInfo(aktProperty) != null) {
173: final PersistentPropertyInfo fi = this
174: .getPresistentFieldInfo(aktProperty);
175: fi.setEmbeddedClass(true);
176: fi.setNullable(false);
177: }
178:
179: // set the akt pk information> Ebedded classes are not
180: // generated
181: PrimaryKeyInfo info = new PrimaryKeyInfo(
182: ((EmbeddedId) a));
183: this .extractGenerator(fieldAnnotations, info);
184: this .pkFieldInfo.put(aktProperty, info);
185: }
186: }
187: }
188: }
189:
190: /**
191: * Returns the pk to delete one entity bean.
192: *
193: * @param entityBean -
194: * the entity bean instance
195: * @return - return the pk or the pk class
196: */
197: public Object getPrimaryKey(T entityBean) {
198: try {
199: if (this .hasEmbeddedPKClass()) {
200: // return the embedded class instance
201: return this .getField(entityBean, this .embeddedPKClass
202: .getAttibuteName());
203: } else if (this .getPkFields().size() == 1) {
204: // return the single element
205: Property toRead = this .getPkFields().iterator().next();
206: return this .getField(entityBean, toRead);
207: } else if (this .getPkFields().size() > 0 && hasIdClass()) {
208: IdClassInstanceGen idClassInstanceGen = new IdClassInstanceGen(
209: this .getPkFields(), this .idClass, entityBean);
210: return idClassInstanceGen.getIDClassIntance();
211:
212: } else {
213: throw new RuntimeException(
214: "Multiple PK fields detected, use EmbeddedPKClass or IDClass");
215: }
216: } catch (IllegalAccessException e) {
217: log.error(e);
218: throw new RuntimeException(e);
219: }
220: }
221:
222: /**
223: * Returns the tableName.
224: *
225: * @return Returns the tableName.
226: */
227: public String getTableName() {
228: return tableName;
229: }
230:
231: /**
232: * Returns the tableName.
233: *
234: * @return Returns the tableName.
235: */
236: public String getShemaName() {
237: return this .schemaName;
238: }
239:
240: /**
241: * Returns if a chema name is persent.
242: *
243: * @return a chema name is persent.
244: */
245: public boolean hasSchema() {
246: return this .hasSchema;
247: }
248:
249: /**
250: * Returns the embeddedPKClass.
251: *
252: * @return Returns the embeddedPKClass.
253: */
254: public EmbeddedClassIntrospector<Object> getEmbeddedPKClass() {
255: return embeddedPKClass;
256: }
257:
258: /**
259: * Returns the hasPKClass.
260: *
261: * @return Returns the hasPKClass.
262: */
263: public boolean hasEmbeddedPKClass() {
264: return hasPKClass;
265: }
266:
267: /**
268: * Returns the hasPKClass.
269: *
270: * @return Returns the hasPKClass.
271: */
272: public boolean hasIdClass() {
273: return idClass != null;
274: }
275:
276: /**
277: * If no table name is specifed hgenrate a JSR 220 table name form class
278: *
279: * @param clazz -
280: * the clss name
281: * @return - the JSR 220 default table name
282: */
283: private String generateDefautTableName(Class clazz,
284: Entity entityAnnotation) {
285:
286: if (entityAnnotation != null
287: && !entityAnnotation.name().equals("")) {
288: return entityAnnotation.name().toUpperCase();
289: }
290:
291: String back = clazz.getName();
292: if (back.lastIndexOf(".") > 0
293: && back.lastIndexOf(".") + 1 < back.length()) {
294: back = back.substring(back.lastIndexOf(".") + 1, back
295: .length());
296: return back.toUpperCase();
297: } else {
298: return back.toUpperCase();
299: }
300: }
301:
302: /**
303: * Returns the name of the class to inspect.
304: */
305: public String getPersistentClassName() {
306: return this .toInspect.getName();
307:
308: }
309:
310: /**
311: * Returns the logger for this class.
312: * @return
313: */
314: protected Logger getLogger() {
315: return log;
316: }
317:
318: /**
319: * Perform post processing on relation properties. Has to be done after the
320: * EntityBeanIntrospectors have processed all properties, to avoid cyclic
321: * dependencies.
322: */
323: private void postProcessRelationProperties() {
324:
325: for (Entry<Property, PersistentPropertyInfo> entry : fieldInfo
326: .entrySet()) {
327: if (entry.getValue().isReleation()) {
328: EntityReleationInfo relation = entry.getValue()
329: .getEntityReleationInfo();
330: // TODO (Pd): see if we can generalize this, to other relation types
331: if (relation instanceof ManyToOneReleation) {
332: Class targetClass = ((ManyToOneReleation) relation)
333: .getTargetClass();
334: Set<Property> keyProps = GlobalPrimaryKeyStore
335: .getStore().getPrimaryKeyInfo(targetClass);
336: ((ManyToOneReleation) relation)
337: .setTargetKeyProperty(keyProps);
338: // Check that database name is set (it's explicitly unset while processing the
339: // ManyToOne annotation, to be able to recognize the case when it's not
340: // specified by a Column annotation)
341: if (entry.getValue().getDbName() == null) {
342: // Currently, only single key columns are supported
343: String keyName = keyProps.iterator().next()
344: .getName();
345: String dbName = entry.getKey().getName() + "_"
346: + keyName;
347: entry.getValue().setDbName(dbName);
348: log
349: .debug("No db name set for relation; using default "
350: + entry.getValue().getDbName());
351: }
352: }
353: }
354: }
355: }
356:
357: /**
358: * Determines whether the inspected entity class uses entity inheritance and of what type.
359: * Also determines discriminator attributes for the single-table inheritance strategy.
360: * @param toInspect the class that is inspected
361: */
362: private void processInheritance(Class<T> toInspect) {
363: // Find the root of the entity class hierarchy
364: Class<? super T> baseClass = toInspect;
365: while (baseClass.getSuperclass().getAnnotation(Entity.class) != null) {
366: baseClass = baseClass.getSuperclass();
367: }
368: // If root is same as class to inspect, there is no inheritance.
369: if (!baseClass.equals(toInspect)) {
370: Inheritance inheritanceAnnotation = baseClass
371: .getAnnotation(Inheritance.class);
372: inheritanceStrategy = inheritanceAnnotation != null ? inheritanceAnnotation
373: .strategy()
374: : null;
375: if (inheritanceStrategy == null) {
376: log.debug("strategy is null -> taking default");
377: inheritanceStrategy = InheritanceType.SINGLE_TABLE;
378: } else {
379: log.debug("strategy is: " + inheritanceStrategy);
380: }
381:
382: if (inheritanceStrategy
383: .equals(InheritanceType.SINGLE_TABLE)) {
384: // Table is specified as annotation on root entity
385: Table tableAnnotation = baseClass
386: .getAnnotation(Table.class);
387: if (tableAnnotation != null) {
388: this .tableName = tableAnnotation.name();
389: } else {
390: this .tableName = generateDefautTableName(baseClass,
391: baseClass.getAnnotation(Entity.class));
392: }
393: // Determine what discriminator to use
394: DiscriminatorColumn discriminatorColumn = baseClass
395: .getAnnotation(DiscriminatorColumn.class);
396: if (discriminatorColumn != null) {
397: this .discriminatorName = discriminatorColumn.name();
398: switch (discriminatorColumn.discriminatorType()) {
399: case INTEGER:
400: discriminatorType = Integer.class;
401: break;
402: case STRING:
403: discriminatorType = String.class;
404: break;
405: case CHAR:
406: discriminatorType = Character.class;
407: break;
408: }
409: //this.discriminatorProperty = new DiscriminatorProperty(toInspect, discriminatorName, type);
410: } else {
411: // Use defaults:
412: this .discriminatorName = "DTYPE";
413: this .discriminatorType = String.class;
414: }
415: // Find discriminator value
416: DiscriminatorValue discriminatorValueAnnotation = toInspect
417: .getAnnotation(DiscriminatorValue.class);
418: if (discriminatorValueAnnotation != null) {
419: this .discriminatorValue = discriminatorValueAnnotation
420: .value();
421: }
422: } else {
423: log.debug("Inheritance strategy " + inheritanceStrategy
424: + " not (yet) supported.");
425: }
426: }
427: }
428:
429: /**
430: * @return true when entity uses single-table inheritance strategy
431: */
432: public boolean usesSingleTableInheritance() {
433: return inheritanceStrategy != null
434: && inheritanceStrategy
435: .equals(InheritanceType.SINGLE_TABLE);
436: }
437:
438: /**
439: * @return name of discriminator (column), or null if not used.
440: */
441: public String getDiscriminatorName() {
442: return discriminatorName;
443: }
444:
445: /**
446: * @return discriminator value used for the class that is inspected by this object,
447: * or null if not used.
448: */
449: public String getDiscriminatorValue() {
450: return discriminatorValue;
451: }
452:
453: /**
454: * Returns discriminator type used for the class that is inspected by this object,
455: * or null if not used.
456: * @return one of Integer.class, String.class or Character.class
457: */
458: public Class getDiscriminatorType() {
459: return discriminatorType;
460: }
461: }
|