001: /**********************************************************************
002: Copyright (c) 2004 Erik Bengtson and others. All rights reserved.
003: Licensed under the Apache License, Version 2.0 (the "License");
004: you may not use this file except in compliance with the License.
005: You may obtain a copy of the License at
006:
007: http://www.apache.org/licenses/LICENSE-2.0
008:
009: Unless required by applicable law or agreed to in writing, software
010: distributed under the License is distributed on an "AS IS" BASIS,
011: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012: See the License for the specific language governing permissions and
013: limitations under the License.
014:
015:
016: Contributors:
017: 2004 Andy Jefferson - toString(), MetaData, javadocs
018: 2004 Andy Jefferson - nullIndicatorColumn/Value, ownerField
019: ...
020: **********************************************************************/package org.jpox.metadata;
021:
022: import java.lang.reflect.Field;
023: import java.lang.reflect.Method;
024: import java.lang.reflect.Modifier;
025: import java.util.ArrayList;
026: import java.util.Collections;
027: import java.util.Iterator;
028: import java.util.List;
029:
030: import org.jpox.ClassLoaderResolver;
031: import org.jpox.exceptions.ClassNotResolvedException;
032: import org.jpox.util.ClassUtils;
033: import org.jpox.util.JPOXLogger;
034: import org.jpox.util.StringUtils;
035:
036: /**
037: * This element specifies the mapping for an embedded type. It contains multiple field elements,
038: * one for each field in the type.
039: * <P>
040: * The <B>null-indicator-column</B> optionally identifies the name of the column used to indicate
041: * whether the embedded instance is null. By default, if the value of this column is null, then the
042: * embedded instance is null. This column might be mapped to a field of the embedded instance but
043: * might be a synthetic column for the sole purpose of indicating a null reference.
044: * The <B>null-indicator-value</B> specifies the value to indicate that the embedded instance is null.
045: * This is only used for non-nullable columns.
046: * If <B>null-indicator-column</B> is omitted, then the embedded instance is assumed always to exist.
047: *
048: * @since 1.1
049: * @version $Revision: 1.42 $
050: */
051: public class EmbeddedMetaData extends MetaData {
052: /** Fields/properties of the embedded object. */
053: protected final List members = new ArrayList();
054:
055: /** Name of the owner field/property in the embedded object. */
056: protected final String ownerMember;
057:
058: /** Name of a column used for determining if the embedded object is null */
059: protected final String nullIndicatorColumn;
060:
061: /** Value in the null column indicating that the embedded object is null */
062: protected final String nullIndicatorValue;
063:
064: // -------------------------------------------------------------------------
065: // Fields below here are not represented in the output MetaData. They are
066: // for use internally in the operation of the JDO system. The majority are
067: // for convenience to save iterating through the fields since the fields
068: // are fixed once initialised.
069:
070: protected AbstractMemberMetaData fieldMetaData[];
071:
072: /**
073: * Constructor to create a copy of the passed metadata using the provided parent.
074: * @param parent The parent
075: * @param embmd The metadata to copy
076: */
077: public EmbeddedMetaData(MetaData parent, EmbeddedMetaData embmd) {
078: super (parent);
079: this .ownerMember = embmd.ownerMember;
080: this .nullIndicatorColumn = embmd.nullIndicatorColumn;
081: this .nullIndicatorValue = embmd.nullIndicatorValue;
082: for (int i = 0; i < embmd.members.size(); i++) {
083: if (embmd.members.get(i) instanceof FieldMetaData) {
084: addMember(new FieldMetaData(this ,
085: (AbstractMemberMetaData) embmd.members.get(i)));
086: } else {
087: addMember(new PropertyMetaData(this ,
088: (PropertyMetaData) embmd.members.get(i)));
089: }
090: }
091: }
092:
093: /**
094: * Constructor.
095: * @param parent Owning metadata
096: * @param ownerField The owner field
097: * @param nullColumn Column to use for determining null embedded object
098: * @param nullValue Value of column to use for determining null embedded object
099: */
100: public EmbeddedMetaData(MetaData parent, String ownerField,
101: String nullColumn, String nullValue) {
102: super (parent);
103: this .ownerMember = (StringUtils.isWhitespace(ownerField) ? null
104: : ownerField);
105: nullIndicatorColumn = (StringUtils.isWhitespace(nullColumn) ? null
106: : nullColumn);
107: nullIndicatorValue = (StringUtils.isWhitespace(nullValue) ? null
108: : nullValue);
109: }
110:
111: /**
112: * Method to populate the embedded MetaData.
113: * This performs checks on the validity of the field types for embedding.
114: * @param clr The class loader to use where necessary
115: * @param primary the primary ClassLoader to use (or null)
116: */
117: public void populate(ClassLoaderResolver clr, ClassLoader primary) {
118: // Find the class that the embedded fields apply to
119: MetaData md = getParent();
120: AbstractMemberMetaData apmd = null; // Field that has <embedded>
121: AbstractClassMetaData embCmd = null; // The embedded class
122: String embeddedType = null; // Name of the embedded type
123: if (md instanceof AbstractMemberMetaData) {
124: // PC embedded in PC object
125: apmd = (AbstractMemberMetaData) md;
126: embeddedType = apmd.getTypeName();
127: MetaDataManager mmgr = apmd.getAbstractClassMetaData()
128: .getMetaDataManager();
129: embCmd = mmgr.getMetaDataForClassInternal(apmd.getType(),
130: clr);
131: if (embCmd == null && apmd.getFieldTypes() != null
132: && apmd.getFieldTypes().length == 1) {
133: // The specified field is not embeddable, nor is it persistent-interface, so try field-type for embedding
134: embCmd = mmgr.getMetaDataForClassInternal(clr
135: .classForName(apmd.getFieldTypes()[0]), clr);
136: }
137: if (embCmd == null) {
138: JPOXLogger.METADATA.error(LOCALISER.msg("044121", apmd
139: .getFullFieldName(), apmd.getTypeName()));
140: throw new InvalidMetaDataException(LOCALISER, "044121",
141: apmd.getFullFieldName(), apmd.getTypeName());
142: }
143: } else if (md instanceof ElementMetaData) {
144: // PC element embedded in collection
145: ElementMetaData elemmd = (ElementMetaData) md;
146: apmd = (AbstractMemberMetaData) elemmd.getParent();
147: embeddedType = apmd.getCollection().getElementType();
148: try {
149: Class cls = clr.classForName(embeddedType, primary);
150: embCmd = apmd.getAbstractClassMetaData()
151: .getMetaDataManager()
152: .getMetaDataForClassInternal(cls, clr);
153: } catch (ClassNotResolvedException cnre) {
154: // Should be caught by populating the Collection
155: }
156: if (embCmd == null) {
157: JPOXLogger.METADATA.error(LOCALISER.msg("044122", apmd
158: .getFullFieldName(), apmd.getCollection()
159: .getElementType()));
160: throw new InvalidMetaDataException(LOCALISER, "044122",
161: apmd.getFullFieldName(), apmd.getTypeName());
162: }
163: } else if (md instanceof KeyMetaData) {
164: // PC key embedded in Map
165: KeyMetaData keymd = (KeyMetaData) md;
166: apmd = (AbstractMemberMetaData) keymd.getParent();
167: embeddedType = apmd.getMap().getKeyType();
168: try {
169: Class cls = clr.classForName(embeddedType, primary);
170: embCmd = apmd.getAbstractClassMetaData()
171: .getMetaDataManager()
172: .getMetaDataForClassInternal(cls, clr);
173: } catch (ClassNotResolvedException cnre) {
174: // Should be caught by populating the Map
175: }
176: if (embCmd == null) {
177: JPOXLogger.METADATA.error(LOCALISER
178: .msg("044123", apmd.getFullFieldName(), apmd
179: .getMap().getKeyType()));
180: throw new InvalidMetaDataException(LOCALISER, "044123",
181: apmd.getFullFieldName(), embeddedType);
182: }
183: } else if (md instanceof ValueMetaData) {
184: // PC value embedded in Map
185: ValueMetaData valuemd = (ValueMetaData) md;
186: apmd = (AbstractMemberMetaData) valuemd.getParent();
187: embeddedType = apmd.getMap().getValueType();
188: try {
189: Class cls = clr.classForName(embeddedType, primary);
190: embCmd = apmd.getAbstractClassMetaData()
191: .getMetaDataManager()
192: .getMetaDataForClassInternal(cls, clr);
193: } catch (ClassNotResolvedException cnre) {
194: // Should be caught by populating the Map
195: }
196: if (embCmd == null) {
197: JPOXLogger.METADATA.error(LOCALISER.msg("044124", apmd
198: .getFullFieldName(), apmd.getMap()
199: .getValueType()));
200: throw new InvalidMetaDataException(LOCALISER, "044124",
201: apmd.getFullFieldName(), embeddedType);
202: }
203: }
204:
205: // Check that all "members" are of the correct type for the embedded object
206: Iterator memberIter = members.iterator();
207: while (memberIter.hasNext()) {
208: Object fld = memberIter.next();
209: // TODO Should allow PropertyMetaData here I think
210: if (embCmd instanceof InterfaceMetaData
211: && fld instanceof FieldMetaData) {
212: // Cannot have a field within a persistent interface
213: throw new InvalidMetaDataException(LOCALISER, "044129",
214: apmd.getFullFieldName(), embCmd
215: .getFullClassName(),
216: ((AbstractMemberMetaData) fld).getName());
217: }
218: }
219:
220: // Add fields for the class that aren't in the <embedded> block using Reflection.
221: // NOTE 1 : We ignore fields in superclasses
222: // NOTE 2 : We ignore "enhanced" fields (added by the JDO enhancer)
223: // NOTE 3 : We ignore inner class fields (containing "$")
224: // NOTE 4 : We sort the fields into ascending alphabetical order
225: Class embeddedClass = null;
226: Collections.sort(members);
227: try {
228: // Load the embedded class
229: embeddedClass = clr.classForName(embeddedType, primary);
230:
231: // TODO Cater for properties in the populating class when the user defines using setters
232:
233: // Get all (reflected) fields in the populating class
234: Field[] cls_fields = embeddedClass.getDeclaredFields();
235: for (int i = 0; i < cls_fields.length; i++) {
236: // Limit to fields in this class, that aren't enhanced fields
237: // that aren't inner class fields, and that aren't static
238: if (cls_fields[i].getDeclaringClass().getName().equals(
239: embeddedType)
240: && !cls_fields[i].getName().startsWith("jdo")
241: && !ClassUtils.isInnerClass(cls_fields[i]
242: .getName())
243: && !Modifier.isStatic(cls_fields[i]
244: .getModifiers())) {
245: // Find if there is a AbstractMemberMetaData for this field.
246: // This is possible as AbstractMemberMetaData implements Comparable
247: if (Collections.binarySearch(members, cls_fields[i]
248: .getName()) < 0) {
249: // Add a default FieldMetaData for this field.
250: JPOXLogger.METADATA.debug(LOCALISER.msg(
251: "044125", cls_fields[i].getName(),
252: embeddedType, apmd.getClassName(true)));
253: FieldMetaData omittedFmd = new FieldMetaData(
254: this , cls_fields[i].getName());
255: members.add(omittedFmd);
256: Collections.sort(members);
257: }
258: }
259: }
260: } catch (Exception e) {
261: JPOXLogger.METADATA.error(e.getMessage(), e);
262: throw new RuntimeException(e.getMessage());
263: }
264:
265: // add properties of interface, only if interface
266: if (embCmd instanceof InterfaceMetaData) {
267: try {
268: // Get all (reflected) fields in the populating class
269: Method[] clsMethods = embeddedClass
270: .getDeclaredMethods();
271: for (int i = 0; i < clsMethods.length; i++) {
272: // Limit to methods in this class, that aren't enhanced fields
273: // that aren't inner class fields, and that aren't static
274: if (clsMethods[i].getDeclaringClass().getName()
275: .equals(embeddedType)
276: && (clsMethods[i].getName().startsWith(
277: "get") || clsMethods[i].getName()
278: .startsWith("is"))
279: && !ClassUtils.isInnerClass(clsMethods[i]
280: .getName())) {
281: String fieldName = ClassUtils
282: .getFieldNameForJavaBeanGetter(clsMethods[i]
283: .getName());
284: // Find if there is a PropertyMetaData for this field.
285: // This is possible as PropertyMetaData implements Comparable
286: if (Collections
287: .binarySearch(members, fieldName) < 0) {
288: // Add a default PropertyMetaData for this field.
289: JPOXLogger.METADATA.debug(LOCALISER.msg(
290: "044060", fieldName, apmd
291: .getClassName(true)));
292: PropertyMetaData fmd = new PropertyMetaData(
293: this , fieldName);
294: members.add(fmd);
295: Collections.sort(members);
296: }
297: }
298: }
299: } catch (Exception e) {
300: JPOXLogger.METADATA.error(e.getMessage(), e);
301: throw new RuntimeException(e.getMessage());
302: }
303: }
304: Collections.sort(members);
305:
306: memberIter = members.iterator();
307: while (memberIter.hasNext()) {
308: AbstractMemberMetaData fieldFmd = (AbstractMemberMetaData) memberIter
309: .next();
310: if (fieldFmd instanceof FieldMetaData) {
311: Field cls_field = null;
312: try {
313: cls_field = embeddedClass.getDeclaredField(fieldFmd
314: .getName());
315: } catch (Exception e) {
316: // MetaData field doesn't exist in the class!
317: throw new InvalidMetaDataException(LOCALISER,
318: "044071", embeddedClass.getName(), fieldFmd
319: .getFullFieldName());
320: }
321: fieldFmd.populate(clr, cls_field, null, primary);
322: } else {
323: Method cls_method = null;
324: try {
325: try {
326: cls_method = embeddedClass
327: .getDeclaredMethod(ClassUtils
328: .getJavaBeanGetterName(fieldFmd
329: .getName(), true), null);
330: } catch (Exception e) {
331: cls_method = embeddedClass.getDeclaredMethod(
332: ClassUtils.getJavaBeanGetterName(
333: fieldFmd.getName(), false),
334: null);
335: }
336: } catch (Exception e) {
337: // MetaData field doesn't exist in the class!
338: throw new InvalidMetaDataException(LOCALISER,
339: "044071", embeddedClass.getName(), fieldFmd
340: .getFullFieldName());
341: }
342: fieldFmd.populate(clr, null, cls_method, primary);
343: }
344: }
345: }
346:
347: /**
348: * Method to initialise the object, creating all internal convenience
349: * arrays.
350: */
351: public void initialise() {
352: fieldMetaData = new AbstractMemberMetaData[members.size()];
353: for (int i = 0; i < fieldMetaData.length; i++) {
354: fieldMetaData[i] = (AbstractMemberMetaData) members.get(i);
355: fieldMetaData[i].initialise();
356: }
357:
358: setInitialised();
359: }
360:
361: // ----------------------------- Accessors ---------------------------------
362:
363: /**
364: * Accessor for fieldMetaData
365: * @return Returns the fieldMetaData.
366: */
367: public final AbstractMemberMetaData[] getFieldMetaData() {
368: return fieldMetaData;
369: }
370:
371: /**
372: * Accessor for the owner field/property to contain the FK back to the owner
373: * @return The owner field/property
374: */
375: public final String getOwnerMember() {
376: return ownerMember;
377: }
378:
379: /**
380: * Accessor for the column to check for null values.
381: * @return The column name
382: */
383: public final String getNullIndicatorColumn() {
384: return nullIndicatorColumn;
385: }
386:
387: /**
388: * Accessor for the value of the null indicator column when null
389: * @return The value
390: */
391: public final String getNullIndicatorValue() {
392: return nullIndicatorValue;
393: }
394:
395: // ----------------------------- Mutators ----------------------------------
396:
397: /**
398: * Method to add a field/property to the embedded definition.
399: * Rejects the addition of duplicate named fields/properties.
400: * @param mmd Meta-Data for the field/property.
401: */
402: public void addMember(AbstractMemberMetaData mmd) {
403: if (mmd == null) {
404: return;
405: }
406:
407: if (isInitialised()) {
408: throw new RuntimeException(LOCALISER.msg("044108", mmd
409: .getName(), mmd.getAbstractClassMetaData()
410: .getFullClassName()));
411: }
412: Iterator iter = members.iterator();
413: while (iter.hasNext()) {
414: AbstractMemberMetaData md = (AbstractMemberMetaData) iter
415: .next();
416: if (mmd.getName().equals(md.getName())) {
417: throw new RuntimeException(LOCALISER.msg("044112", mmd
418: .getName(), mmd.getAbstractClassMetaData()
419: .getFullClassName()));
420: }
421: }
422: members.add(mmd);
423: }
424:
425: // ------------------------------- Utilities -------------------------------
426:
427: /**
428: * Returns a string representation of the object using a prefix
429: * This can be used as part of a facility to output a MetaData file.
430: * @param prefix prefix string
431: * @param indent indent string
432: * @return a string representation of the object.
433: */
434: public String toString(String prefix, String indent) {
435: // Field needs outputting so generate metadata
436: StringBuffer sb = new StringBuffer();
437: sb.append(prefix).append("<embedded");
438: if (ownerMember != null) {
439: sb.append(" owner-field=\"" + ownerMember + "\"");
440: }
441: if (nullIndicatorColumn != null) {
442: sb.append(" null-indicator-column=\"" + nullIndicatorColumn
443: + "\"");
444: }
445: if (nullIndicatorValue != null) {
446: sb.append(" null-indicator-value=\"" + nullIndicatorValue
447: + "\"");
448: }
449: sb.append(">\n");
450:
451: // Add fields
452: for (int i = 0; i < members.size(); i++) {
453: AbstractMemberMetaData f = (AbstractMemberMetaData) members
454: .get(i);
455: sb.append(f.toString(prefix + indent, indent));
456: }
457:
458: // Add extensions
459: sb.append(super .toString(prefix + indent, indent));
460:
461: sb.append(prefix + "</embedded>\n");
462: return sb.toString();
463: }
464: }
|