KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > oracle > toplink > essentials > internal > ejb > cmp3 > metadata > accessors > EmbeddedAccessor


1 /*
2  * The contents of this file are subject to the terms
3  * of the Common Development and Distribution License
4  * (the "License"). You may not use this file except
5  * in compliance with the License.
6  *
7  * You can obtain a copy of the license at
8  * glassfish/bootstrap/legal/CDDLv1.0.txt or
9  * https://glassfish.dev.java.net/public/CDDLv1.0.html.
10  * See the License for the specific language governing
11  * permissions and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL
14  * HEADER in each file and include the License file at
15  * glassfish/bootstrap/legal/CDDLv1.0.txt. If applicable,
16  * add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your
18  * own identifying information: Portions Copyright [yyyy]
19  * [name of copyright owner]
20  */

21 // Copyright (c) 1998, 2006, Oracle. All rights reserved.
22
package oracle.toplink.essentials.internal.ejb.cmp3.metadata.accessors;
23
24 import static oracle.toplink.essentials.internal.ejb.cmp3.metadata.accessors.EmbeddedAccessor.AccessType.MIXED;
25 import static oracle.toplink.essentials.internal.ejb.cmp3.metadata.accessors.EmbeddedAccessor.AccessType.PROPERTY;
26 import static oracle.toplink.essentials.internal.ejb.cmp3.metadata.accessors.EmbeddedAccessor.AccessType.FIELD;
27 import static oracle.toplink.essentials.internal.ejb.cmp3.metadata.accessors.EmbeddedAccessor.AccessType.UNDEFINED;
28
29 import javax.persistence.*;
30
31 import oracle.toplink.essentials.internal.ejb.cmp3.metadata.accessors.objects.MetadataAccessibleObject;
32 import oracle.toplink.essentials.internal.ejb.cmp3.metadata.accessors.objects.MetadataClass;
33 import oracle.toplink.essentials.internal.ejb.cmp3.metadata.columns.MetadataColumn;
34
35 import oracle.toplink.essentials.internal.ejb.cmp3.metadata.MetadataDescriptor;
36 import oracle.toplink.essentials.internal.ejb.cmp3.metadata.MetadataHelper;
37 import oracle.toplink.essentials.internal.ejb.cmp3.xml.XMLHelper;
38 import oracle.toplink.essentials.internal.ejb.cmp3.xml.XMLConstants;
39 import oracle.toplink.essentials.internal.ejb.cmp3.xml.accessors.XMLClassAccessor;
40 import oracle.toplink.essentials.internal.helper.DatabaseField;
41
42 import oracle.toplink.essentials.mappings.AggregateObjectMapping;
43 import oracle.toplink.essentials.mappings.DatabaseMapping;
44 import oracle.toplink.essentials.mappings.OneToOneMapping;
45 import oracle.toplink.essentials.exceptions.ValidationException;
46 import org.w3c.dom.Node JavaDoc;
47
48 /**
49  * An embedded relationship accessor.
50  *
51  * @author Guy Pelletier
52  * @since TopLink EJB 3.0 Reference Implementation
53  */

54 public class EmbeddedAccessor extends BasicAccessor {
55     enum AccessType {FIELD, PROPERTY, UNDEFINED, MIXED};
56
57     protected boolean m_isEmbeddedId;
58     
59     /**
60      * INTERNAL:
61      */

62     public EmbeddedAccessor(MetadataAccessibleObject accessibleObject, ClassAccessor classAccessor, boolean isEmbeddedId) {
63         super(accessibleObject, classAccessor);
64         
65         m_isEmbeddedId = isEmbeddedId;
66     }
67     
68     /**
69      * INTERNAL:
70      */

71     public boolean isEmbedded() {
72         return true;
73     }
74     
75     /**
76      * INTERNAL:
77      */

78     public boolean isEmbeddedId() {
79         return m_isEmbeddedId;
80     }
81     
82     /**
83      * INTERNAL:
84      * Process an @Embedded or embedded element. Also called when processing
85      * an @EmbeddedId or embedded-id element.
86      */

87     public void process() {
88         // If we are an embedded id, process the embedded is metadata.
89
if (isEmbeddedId()) {
90             processEmbeddedId();
91         }
92         
93         // Tell the Embeddable class to process itself
94
MetadataDescriptor referenceDescriptor = processEmbeddableClass();
95         
96         // Store this descriptor metadata. It may be needed again later on to
97
// look up a mappedBy attribute.
98
m_descriptor.addAggregateDescriptor(referenceDescriptor);
99         
100         if (m_descriptor.hasMappingForAttributeName(getAttributeName())) {
101             // XML/Annotation merging. XML wins, ignore annotations.
102
m_logger.logWarningMessage(m_logger.IGNORE_MAPPING, m_descriptor, this);
103         } else {
104             // Create an aggregate mapping and do the rest of the work.
105
AggregateObjectMapping mapping = new AggregateObjectMapping();
106             mapping.setIsReadOnly(false);
107             mapping.setIsNullAllowed(true);
108             mapping.setReferenceClassName(getReferenceClassName());
109             mapping.setAttributeName(getAttributeName());
110         
111             // Will check for PROPERTY access
112
setAccessorMethods(mapping);
113         
114             // Process attribute overrides.
115
processAttributeOverrides(mapping);
116             
117             // Process association overrides.
118
processAssociationOverrides(mapping);
119         
120             // Add the mapping to the descriptor and we are done.
121
m_descriptor.addMapping(mapping);
122         }
123     }
124     
125     /**
126      * INTERNAL:
127      * Process an @AssociationOverride for an embedded object, that is, an
128      * aggregate object mapping in TopLink.
129      *
130      * This functionality is not supported in XML, hence why this method is
131      * defined here instead of on MetadataProcessor.
132      *
133      * Also this functionality is currently optional in the EJB 3.0 spec, but
134      * since TopLink can handle it, it is implemented and assumes the user has
135      * properly configured its use since it will fail silently.
136      */

137     protected void processAssociationOverride(AssociationOverride associationOverride, AggregateObjectMapping aggregateMapping) {
138         MetadataDescriptor aggregateDescriptor = getReferenceDescriptor();
139         
140         // AssociationOverride.name(), the name of the attribute we want to
141
// override.
142
String JavaDoc name = associationOverride.name();
143         DatabaseMapping mapping = aggregateDescriptor.getMappingForAttributeName(name);
144         
145         if (mapping == null) {
146             // WIP - For now fail silently.
147
//throw ValidationException.invalidEmbeddableAttribute(aggregateDescriptor.getJavaClass(), name, descriptor.getJavaClass(), aggregateMapping.getAttributeName());
148
}
149         
150         if (mapping.isOneToOneMapping()) {
151             int index = 0;
152             
153             for (JoinColumn joinColumn : associationOverride.joinColumns()) {
154                 // We can't change the mapping from the aggregate descriptor
155
// so we have to add field name translations. This needs to be
156
// tested since I am not entirely sure if this will acutally
157
// work.
158
// In composite primary key case, how do we association the
159
// foreign keys? Right now we assume the association overrides
160
// are specified in the same order as the original joinColumns,
161
// therefore in the same order the foreign keys were added to
162
// the mapping.
163
DatabaseField fkField = (DatabaseField) ((OneToOneMapping) mapping).getForeignKeyFields().elementAt(index++);
164                 aggregateMapping.addFieldNameTranslation(joinColumn.name(), fkField.getName());
165             }
166         } else {
167             // WIP - For now fail silently.
168
}
169     }
170     
171     /**
172      * INTERNAL:
173      * Process an @AssociationOverrides for an embedded object, that is, an
174      * aggregate object mapping in TopLink.
175      *
176      * It will also look for an @AssociationOverride.
177      */

178     protected void processAssociationOverrides(AggregateObjectMapping mapping) {
179         // Look for an @AssociationOverrides.
180
AssociationOverrides associationOverrides = getAnnotation(AssociationOverrides.class);
181         if (associationOverrides != null) {
182             for (AssociationOverride associationOverride : associationOverrides.value()) {
183                 processAssociationOverride(associationOverride, mapping);
184             }
185         }
186         
187         // Look for an @AssociationOverride.
188
AssociationOverride associationOverride = getAnnotation(AssociationOverride.class);
189         if (associationOverride != null) {
190             processAssociationOverride(associationOverride, mapping);
191         }
192     }
193     
194     /**
195      * INTERNAL:
196      * Process an @AttributeOverride or attribute-override element for an
197      * embedded object, that is, an aggregate object mapping in TopLink.
198      */

199     protected void processAttributeOverride(AggregateObjectMapping mapping, MetadataColumn column) {
200         String JavaDoc attributeName = column.getAttributeName();
201         
202         // Set the attribute name on the aggregate.
203
DatabaseMapping aggregateMapping = getReferenceDescriptor().getMappingForAttributeName(attributeName);
204         
205         if (aggregateMapping == null) {
206             m_validator.throwInvalidEmbeddableAttribute(getJavaClass(), mapping.getAttributeName(), getReferenceDescriptor().getJavaClass(), attributeName);
207         }
208         
209         // A sub-class to a mapped superclass may override an embedded attribute override.
210
DatabaseField field;
211         if (m_descriptor.hasAttributeOverrideFor(attributeName)) {
212             field = m_descriptor.getAttributeOverrideFor(attributeName).getDatabaseField();
213         } else {
214             field = column.getDatabaseField();
215         }
216         
217         mapping.addFieldNameTranslation(field.getQualifiedName(), aggregateMapping.getField().getName());
218     }
219     
220     /**
221      * INTERNAL: (Overridden in XMLEmbeddedAccessor)
222      * Process an @AttributeOverrides for an embedded object, that is, an
223      * aggregate object mapping in TopLink.
224      *
225      * It will also look for an @AttributeOverride.
226      */

227     protected void processAttributeOverrides(AggregateObjectMapping mapping) {
228         // Look for an @AttributeOverrides.
229
AttributeOverrides attributeOverrides = getAnnotation(AttributeOverrides.class);
230         
231         if (attributeOverrides != null) {
232             for (AttributeOverride attributeOverride : attributeOverrides.value()) {
233                 processAttributeOverride(mapping, new MetadataColumn(attributeOverride.column(), attributeOverride.name(), getAnnotatedElement()));
234             }
235         }
236         
237         // Look for an @AttributeOverride.
238
AttributeOverride attributeOverride = getAnnotation(AttributeOverride.class);
239         if (attributeOverride != null) {
240             processAttributeOverride(mapping, new MetadataColumn(attributeOverride.column(), attributeOverride.name(), getAnnotatedElement()));
241         }
242     }
243     
244     /**
245      * INTERNAL:
246      * This method does two things, viz:
247      * 1. Process an embeddable class, if we have not processed it yet. The reason
248      * for lazy processing of embeddable class is because of rules governing
249      * access-type of embeddable. See GlassFish issue #831 for more details.
250      *
251      * 2. If processing for an @EmbeddedId or embedded-id element, we must
252      * add the primary key field names to our processing entity.
253      *
254      * Be careful while changing order of processing.
255      */

256     protected MetadataDescriptor processEmbeddableClass() {
257         final Class JavaDoc embeddableClass = getReferenceClass();
258         MetadataDescriptor embeddableDescriptor = null;
259         try {
260             embeddableDescriptor = m_project.getDescriptor(embeddableClass);
261         } catch (ValidationException ve) {
262             // expected as we do lazy processing of embeddables.
263
}
264         if (embeddableDescriptor == null) {
265             // The embeddable class is not yet processed, so process it now.
266
embeddableDescriptor = new MetadataDescriptor(embeddableClass);
267             // adding to projects sets up appropriate persistence-unit-defaults
268
m_project.addDescriptor(embeddableDescriptor);
269             embeddableDescriptor.setIgnoreAnnotations(
270                     isMetadataComplete(embeddableDescriptor));
271             AccessType accessType =
272                     determineAccessTypeOfEmbedded(embeddableDescriptor);
273             embeddableDescriptor.setUsesPropertyAccess(
274                     accessType == PROPERTY ? true : false);
275             ClassAccessor embeddableAccessor =
276                     makeAccessorFor(embeddableDescriptor);
277             embeddableAccessor.process();
278             embeddableAccessor.setIsProcessed();
279         } else {
280             // we have already processed this embeddable class. let's
281
// validate that it is not used in entities with conflicting
282
// access type. Conflicting access-type is not allowed
283
// when there is no metadata in the embeddable class.
284
if(!isMetadataPresent(embeddableDescriptor)) {
285                 boolean embeddableUsesPropertyAccess =
286                         embeddableDescriptor.usesPropertyAccess();
287                 boolean entityUsesPropertyAccess = m_descriptor.usesPropertyAccess();
288                 if (embeddableUsesPropertyAccess != entityUsesPropertyAccess) {
289                     m_validator.throwConflictingAccessTypeInEmbeddable(
290                             embeddableClass);
291                 }
292
293             }
294         }
295         
296         // We need to set the primary keys on the owning descriptor metadata if
297
// this is an @EmbeddedId or embedded-id.
298
if (isEmbeddedId() && ! m_descriptor.ignoreIDs()) {
299             for (DatabaseMapping mapping : embeddableDescriptor.getMappings()) {
300                 DatabaseField field = (DatabaseField) mapping.getField().clone();
301                 field.setTableName(m_descriptor.getPrimaryTableName());
302                 m_descriptor.addPrimaryKeyField(field);
303             }
304         }
305         
306         return embeddableDescriptor;
307     }
308
309     /**
310      * This method is used to decide if annotations should be ignored or not for
311      * the given embeddable class.
312      * @param emDesc
313      * @return
314      */

315     private boolean isMetadataComplete(MetadataDescriptor emDesc) {
316         final Class JavaDoc emClass = emDesc.getJavaClass();
317         boolean metadataComplete =
318                 m_project.getPersistenceUnit()!= null ?
319                 m_project.getPersistenceUnit().isMetadataComplete() : false;
320         if (!metadataComplete) {
321             // check <embeddable>
322
if (m_project.hasEmbeddable(emClass)) {
323                 XMLHelper helper = m_project.getEmbeddableHelper(emClass);
324                 Node JavaDoc node = m_project.getEmbeddableNode(emClass);
325                 // check if metadata-complete at <embeddable> level
326
metadataComplete = helper.getNodeValue(
327                                 node,
328                                 XMLConstants.ATT_METADATA_COMPLETE,
329                                 false);
330             }
331         }
332         return metadataComplete;
333     }
334
335     /**
336      * This method is used to decide if a class metadata or not.
337      * @param desc
338      * @return
339      */

340     private boolean isMetadataPresent(MetadataDescriptor desc) {
341         AccessType annotAccessType = computeAccessTypeFromAnnotation(desc);
342         AccessType xmlAccessType = computeAccessTypeFromXML(desc);
343         return annotAccessType!=UNDEFINED || xmlAccessType!=UNDEFINED;
344
345     }
346     /**
347      * This method computes access-type based on placement of annotations.
348      * @param desc descriptor representing the class whose access-type is being
349      * computed.
350      * @return AccessType of the class.
351      */

352     private AccessType computeAccessTypeFromAnnotation(MetadataDescriptor desc) {
353         final Class JavaDoc javaClass = desc.getJavaClass();
354         boolean fieldAccess = MetadataHelper.havePersistenceAnnotationsDefined(
355                 MetadataHelper.getFields(javaClass));
356         boolean propAccess = MetadataHelper.havePersistenceAnnotationsDefined(
357                 MetadataHelper.getMethods(javaClass));
358         AccessType accessType = UNDEFINED;
359         if (fieldAccess && propAccess) {
360             accessType = MIXED;
361         } else if(fieldAccess) {
362             accessType = FIELD;
363         } else if(propAccess) {
364             accessType = PROPERTY;
365         }
366         return accessType;
367     }
368
369     /**
370      * This method computes access-type based on metadata specified in mapping
371      * XML file. If there is a default value for access is specified in
372      * <persistence-unit-defaults> or <entity-mappings> and either there is
373      * no <embeddable> for the given class or there is <access> specified
374      * in the <embeddable>, the default value is used.
375      * @param desc descriptor representing the class whose access-type is being
376      * computed.
377      * @return AccessType of the class.
378      */

379     private AccessType computeAccessTypeFromXML(MetadataDescriptor desc) {
380         // step #1: what's PU level default
381
String JavaDoc access = m_project.getPersistenceUnit() != null ?
382                 m_project.getPersistenceUnit().getAccess():null;
383         // step #2: what's specified in <embeddable> element
384
final Class JavaDoc javaClass = desc.getJavaClass();
385         if (m_project.hasEmbeddable(javaClass)) {
386             XMLHelper helper = m_project.getEmbeddableHelper(javaClass);
387             Node JavaDoc node = m_project.getEmbeddableNode(javaClass);
388             // override pu-level value by <embeddable> level if any
389
// Note, we pass the puLevel value as the default value
390
access = helper.getNodeValue(
391                             node,
392                             XMLConstants.ATT_ACCESS,
393                             access);
394         }
395         AccessType accessType = UNDEFINED;
396         if(access!=null && access.length()!=0) {
397             accessType = AccessType.valueOf(access);
398         }
399         return accessType;
400     }
401     /**
402      * This method is responsible for determining the access-type for an
403      * Embeddable class represented by emDescr that is passed to
404      * this method. This method should *not* be called more than once as this is
405      * quite expensive. Now the rules:
406      *
407      * Rule 1: In the *absence* of metadata in embeddable class, access-type of
408      * an embeddable is determined by the access-type of the enclosing entity.
409      *
410      * Rule 2: In the presence of metadata in embeddable class, access-type of
411      * an embeddable is determined using that metadata. This allows sharing
412      * of the embeddable in entities with conflicting access-types.
413      *
414      * Rule 3: It is an error to use a *metadata-less* embeddable class in
415      * entities with conflicting access-types as that might result in
416      * different database mapping for the same embeddable class.
417      *
418      * Rule 4: It is an error if metadata-complete == false, and
419      * metadata is present *both* in annotations and XML, and
420      * access-type as determined by each of them is *not* same.
421      *
422      * Rule 5: It is an error if *both* fields and properties of an embeddable
423      * class are annotated and metadata-complete == false.
424      *
425      * @param emDesc descriptor for the Embeddable class
426      */

427     private AccessType determineAccessTypeOfEmbedded(
428             MetadataDescriptor emDesc) {
429         final AccessType entityAccessType = m_descriptor.usesPropertyAccess() ?
430                             PROPERTY : FIELD;
431         AccessType accessType = UNDEFINED;
432         final boolean metadataComplete =emDesc.ignoreAnnotations();
433         final AccessType accessTypeUsingAnnotation =
434                 computeAccessTypeFromAnnotation(emDesc);
435         final AccessType accessTypeUsingXML =
436                 computeAccessTypeFromXML(emDesc);
437         if (metadataComplete) {
438             // metadata-complete is true, then use XML access-type if defined,
439
// else use enclosing entity's access-type.
440
accessType = accessTypeUsingXML != UNDEFINED ?
441                     accessTypeUsingXML : entityAccessType;
442         } else {// metadata-complete is false
443
if (accessTypeUsingAnnotation == UNDEFINED
444                     && accessTypeUsingAnnotation == UNDEFINED) {
445                 // metadata is absent, so we use enclosing entity's access-type
446
accessType = entityAccessType;
447             } else if (accessTypeUsingXML == UNDEFINED
448                     && accessTypeUsingAnnotation != UNDEFINED) {
449                 // annotation is present in embeddable class
450
accessType = accessTypeUsingAnnotation;
451                 if (accessType == MIXED) {
452                     m_validator.throwBothFieldsAndPropertiesAnnotatedException(emDesc.getJavaClass());
453                 }
454             } else if (accessTypeUsingAnnotation == UNDEFINED
455                     && accessTypeUsingXML != UNDEFINED) {
456                 // access is defined using XML for embeddable class
457
accessType = accessTypeUsingXML;
458             } else if (accessTypeUsingAnnotation == accessTypeUsingXML) {
459                 // annotation is present as well as access is defined using XML
460
// and they are same. So use it.
461
accessType = accessTypeUsingAnnotation;
462             } else {
463                 // annotation is present as well as access is defined using XML
464
// and they are different. So report an exception.
465
m_validator.throwIncorrectOverridingOfAccessType(
466                         emDesc.getJavaClass(),
467                         accessTypeUsingXML.toString(),
468                         accessTypeUsingAnnotation.toString());
469             }
470         }
471         // we have taken every precaution to make sure that either an embeddable
472
// has a well defined access-type or a suitable ValidationException
473
// is thrown.
474
assert(accessType != UNDEFINED && accessType != MIXED);
475         return accessType;
476     }
477
478     /**
479      * This method is responsible for configuring the right MetadataAccessor
480      * object for processing of the Embeddable class represented by the
481      * embeddableDescriptor that is passed in. This methood associates the
482      * accessor with the descriptor as well.
483      *
484      * @param embeddableDescriptor descriptor for the Embeddable
485      * @return a MetadataAccessor that will be used to read metadata for this class
486      * @throws ValidationException if the embeddable class is neither
487      * annotated as Embeddable nor specified as embeddable in XML.
488      */

489     private ClassAccessor makeAccessorFor(
490             MetadataDescriptor embeddableDescriptor) {
491         ClassAccessor embeddableAccessor;
492         final Class JavaDoc embeddableClass = embeddableDescriptor.getJavaClass();
493         if (m_project.hasEmbeddable(embeddableClass)) {
494             Node JavaDoc node = m_project.getEmbeddableNode(embeddableClass);
495             XMLHelper helper = m_project.getEmbeddableHelper(embeddableClass);
496             embeddableAccessor = new XMLClassAccessor(
497                     new MetadataClass(embeddableClass),
498                     node,
499                     helper,
500                     m_processor,
501                     embeddableDescriptor);
502         } else if (MetadataHelper.isAnnotationPresent(
503                 Embeddable.class, embeddableClass, embeddableDescriptor)) {
504             embeddableAccessor = new ClassAccessor(
505                     new MetadataClass(embeddableClass),
506                     m_processor,
507                     embeddableDescriptor);
508         } else {
509             m_validator.throwInvalidEmbeddedAttribute(
510                     m_descriptor.getDescriptor().getJavaClass(),
511                     m_accessibleObject.getName(),
512                     embeddableClass);
513             // to keep compiler happy
514
throw new RuntimeException JavaDoc("Will never reach here"); // NOI18N
515
}
516         // associate the accessor to the descriptor (as per Javadoc)
517
embeddableDescriptor.setClassAccessor(embeddableAccessor);
518         return embeddableAccessor;
519     }
520
521     /**
522      * INTERNAL:
523      * Process an EmbeddedId or embedded-id element. After processing the
524      * Embeddable class we must add the primary key field names to our
525      * processing entity.
526      */

527     public void processEmbeddedId() {
528         if (m_descriptor.ignoreIDs()) {
529             // XML/Annotation merging. XML wins, ignore annotations.
530
m_logger.logWarningMessage(m_logger.IGNORE_EMBEDDED_ID, this);
531         } else {
532             // Check if we already processed an EmbeddedId for this entity.
533
if (m_descriptor.hasEmbeddedIdAttribute()) {
534                 m_validator.throwMultipleEmbeddedIdsFound(getJavaClass(), getAttributeName(), m_descriptor.getEmbeddedIdAttributeName());
535             }
536             
537             // Check if we already processed an Id or IdClass.
538
if (m_descriptor.hasPrimaryKeyFields()) {
539                 m_validator.throwEmbeddedIdAndIdFound(getJavaClass(), getAttributeName(), m_descriptor.getIdAttributeName());
540             }
541             
542             // Set the PK class.
543
m_descriptor.setPKClass(getReferenceClass());
544             
545             // Store the embeddedId attribute name.
546
m_descriptor.setEmbeddedIdAttributeName(getAttributeName());
547         }
548     }
549 }
550
Popular Tags