001: /*
002: * Copyright (c) 1998 - 2005 Versant Corporation
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * Versant Corporation - initial API and implementation
010: */
011: package com.versant.core.metadata;
012:
013: import com.versant.core.common.*;
014: import com.versant.core.util.classhelper.ClassHelper;
015: import com.versant.core.util.IntArray;
016:
017: import javax.jdo.spi.JDOImplHelper;
018: import java.io.PrintStream;
019: import java.io.Serializable;
020: import java.lang.reflect.Constructor;
021: import java.lang.reflect.Modifier;
022: import java.util.*;
023:
024: import com.versant.core.common.BindingSupportImpl;
025: import com.versant.core.jdo.VersantOid;
026:
027: /**
028: * This holds the meta data for all persistent classes.
029: */
030: public final class ModelMetaData implements Serializable {
031:
032: /**
033: * This is only used for refFields. If the ref Field is not found then return a null
034: * instead of a VersantObjectNotFoundException. This is a projectLevel setting
035: */
036: public boolean returnNullForRowNotFound;
037: /**
038: * The persistent classes.
039: */
040: public ClassMetaData[] classes;
041: /**
042: * This maps objectid-class'es to their ClassMetaData. It is used to
043: * find the class for an application identity class.
044: */
045: private final HashMap objectIdClassMap = new HashMap();
046: /**
047: * The max size of the fields array for any class in this meta data.
048: */
049: public int maxFieldsLength;
050: /**
051: * Extra JDBC specific meta data (null if no JdbcDataStore is in use).
052: */
053: public transient Object jdbcMetaData;
054: /**
055: * Extra VDS specific meta data (null if no VdsDataStore is in use).
056: */
057: public transient Object vdsModel;
058: /**
059: * When an instance is deleted should its state (if available) be included
060: * in the DeletePacket? If this is false then only the OID is included.
061: */
062: public boolean sendStateOnDelete;
063: /**
064: * Maps class name to resource name for the enhancer.
065: */
066: public HashMap classResourceMap = new HashMap();
067:
068: private transient RuntimeException error;
069:
070: /**
071: * This is true if we are running unit tests. Some checks are relaxed
072: * so the same model can be used for JDBC and VDS (e.g. if this is true
073: * then collections with no element-type set are allowed).
074: */
075: public transient boolean testing;
076:
077: private static final ThreadLocal META_DATA = new ThreadLocal();
078:
079: private final Class[] EMPTY_CLASS_ARRAY = new Class[0];
080:
081: private Map abstractSchemaNameMap; // abstract schema name -> ClassMetaData
082:
083: private StateAndOIDFactory untypedOIDFactory = new StateAndOIDFactory() {
084: public OID createOID(ClassMetaData cmd, boolean resolved) {
085: throw BindingSupportImpl.getInstance().internal("");
086: }
087:
088: public State createState(ClassMetaData cmd) {
089: throw BindingSupportImpl.getInstance().internal("");
090: }
091:
092: public NewObjectOID createNewObjectOID(ClassMetaData cmd) {
093: throw BindingSupportImpl.getInstance().internal("");
094: }
095:
096: public OID createUntypedOID() {
097: throw BindingSupportImpl.getInstance().unsupported(
098: "Untyped OIDs are not supported by the datastore");
099: }
100: };
101:
102: public ModelMetaData() {
103: }
104:
105: /**
106: * Get the JDOMetaData instance associated with the current thread. This
107: * is used during deserialization.
108: */
109: public static ModelMetaData getThreadMetaData() {
110:
111: return (ModelMetaData) META_DATA.get();
112:
113: }
114:
115: /**
116: * Associate meta data with the current thread. This is used during
117: * deserialization.
118: */
119:
120: public static void setThreadMetaData(ModelMetaData jmd) {
121: META_DATA.set(jmd);
122: }
123:
124: /**
125: * Get meta data for the class by classId or null if not found.
126: */
127: public ClassMetaData getClassMetaData(int classId) {
128: // do a binary search since classes is sorted by classId
129: int low = 0;
130: int high = classes.length - 1;
131: while (low <= high) {
132: int mid = (low + high) / 2;
133: ClassMetaData midVal = classes[mid];
134: int midValClassId = midVal.classId;
135: if (midValClassId < classId) {
136: low = mid + 1;
137: } else if (midValClassId > classId) {
138: high = mid - 1;
139: } else {
140: return midVal;
141: }
142: }
143: return null;
144: }
145:
146: /**
147: * Get the meta data for the class or null if not found.
148: */
149: public ClassMetaData getClassMetaData(Class cls) {
150: for (int i = classes.length - 1; i >= 0; i--) {
151: ClassMetaData cmd = classes[i];
152: if (cmd.cls == cls)
153: return cmd;
154: }
155: return null;
156: }
157:
158: /**
159: * Get the meta data for the class or null if not found.
160: *
161: * @param qname Fully qualified class name
162: */
163: public ClassMetaData getClassMetaData(String qname) {
164: for (int i = classes.length - 1; i >= 0; i--) {
165: ClassMetaData cmd = classes[i];
166: if (cmd.qname.equals(qname))
167: return cmd;
168: }
169: return null;
170: }
171:
172: /**
173: * Get the meta data for the class relative to package of base or null if
174: * not found.
175: */
176: public ClassMetaData getClassMetaData(ClassMetaData base,
177: String className) {
178: ClassMetaData ans = getClassMetaData(className);
179: if (ans == null) {
180: ans = getClassMetaData(base.packageNameWithDot + className);
181: }
182: return ans;
183: }
184:
185: /**
186: * Build the abstract schema name -> ClassMetaData map. This throws
187: * a JDOUserException if there are any duplicates.
188: */
189: public void buildAbstractSchemaNameMap() {
190: abstractSchemaNameMap = new HashMap();
191: for (int i = classes.length - 1; i >= 0; i--) {
192: ClassMetaData cmd = classes[i];
193: ClassMetaData dup = (ClassMetaData) abstractSchemaNameMap
194: .put(cmd.abstractSchemaName, cmd);
195: if (dup != null) {
196: // todo HACK ignore for now
197: // throw BindingSupportImpl.getInstance().invalidOperation(
198: // cmd.qname + " and " + dup.qname +
199: // " have same abstract schema name '" +
200: // cmd.abstractSchemaName + "'");
201: }
202: }
203: }
204:
205: /**
206: * Lookup a class by its abstract schema name or null if not found.
207: */
208: public ClassMetaData getClassMetaByASN(String abstractSchemaName) {
209: return (ClassMetaData) abstractSchemaNameMap
210: .get(abstractSchemaName);
211: }
212:
213: public void dump() {
214: dump(Debug.OUT, "");
215: }
216:
217: public void dump(PrintStream out, String indent) {
218: out.println(indent + this );
219: String is = indent + " ";
220: for (int i = 0; i < classes.length; i++) {
221: classes[i].dump(out, is);
222: }
223: }
224:
225: /**
226: * Return a list of all classes that are not PersistenceCapable i.e.
227: * that have not been enhanced.
228: */
229: public List findNonPCClassNames() {
230: ArrayList a = new ArrayList();
231: int n = classes.length;
232: for (int i = 0; i < n; i++) {
233: if (!classes[i].isPersistenceCapable())
234: a.add(classes[i].qname);
235: }
236: return a;
237: }
238:
239: /**
240: * Check for classes that are not PersistenceCapable i.e. that have not
241: * been enhanced and throw a JDOFatalUserException if there are any.
242: */
243: public void checkForNonPCClasses() {
244: List nonPCList = findNonPCClassNames();
245: if (!nonPCList.isEmpty()) {
246: StringBuffer s = new StringBuffer();
247: s
248: .append("One or more classes in the JDO meta data have not "
249: + "been enhanced:");
250: int n = nonPCList.size();
251: int i;
252: for (i = 0; i < n && i < 10; i++) {
253: s.append('\n');
254: s.append(nonPCList.get(i));
255: }
256: if (i < n)
257: s.append("\n...");
258: throw BindingSupportImpl.getInstance()
259: .runtime(s.toString());
260: }
261: }
262:
263: /**
264: * Build objectIdClassMap mapping objectid-class'es to the corresponding
265: * ClassMetaData.
266: *
267: * @see #getClassMetaDataForObjectIdClass
268: */
269: public void buildObjectIdClassMap() {
270: int n = classes.length;
271: for (int i = 0; i < n; i++) {
272: ClassMetaData cmd = classes[i];
273: if (cmd.pcSuperMetaData == null
274: && cmd.objectIdClass != null) {
275: objectIdClassMap.put(cmd.objectIdClass, cmd);
276: }
277: }
278: }
279:
280: /**
281: * Get the ClassMetaData for an objectid-class or null if none.
282: *
283: * @see #buildObjectIdClassMap
284: */
285: public ClassMetaData getClassMetaDataForObjectIdClass(Class cls) {
286: return (ClassMetaData) objectIdClassMap.get(cls);
287: }
288:
289: /**
290: * Make sure all persistent classes are registered for JDO by creating
291: * an instance of each one. Errors are silently ignored.
292: */
293: public void forceClassRegistration() {
294: HashMap classMap = new HashMap();
295: ClassMetaData[] cmds = classes;
296: for (int i = 0; i < cmds.length; i++) {
297: ClassMetaData cmd = cmds[i];
298: if (cmd.pcSuperClass == null) {
299: initClass(cmd.cls);
300: classMap.put(cmd.cls, null);
301: ClassMetaData[] subCmds = cmd.pcSubclasses;
302: if (subCmds != null) {
303: for (int j = 0; j < subCmds.length; j++) {
304: initClass(subCmds[j].cls);
305: classMap.put(subCmds[j].cls, null);
306: }
307: }
308: }
309: }
310: for (int i = 0; i < cmds.length; i++) {
311: if (!classMap.containsKey(cmds[i].cls)) {
312: initClass(cmds[i].cls);
313: }
314: }
315: }
316:
317: /**
318: * Create a instance of the given class
319: */
320: private void initClass(Class cls) {
321: try {
322: if (!Modifier.isAbstract(cls.getModifiers())) {
323: Constructor cons = cls.getDeclaredConstructor(null);
324: ClassHelper.get().setAccessible(cons, true);
325: cons.newInstance(null);
326: }
327: } catch (Exception e) {
328: //ignore
329: }
330: }
331:
332: /**
333: * Convert an array of classes into an array of class indexes.
334: *
335: * @throws javax.jdo.JDOUserException if any classes are not persistent
336: */
337: public int[] convertToClassIndexes(Class[] classes,
338: boolean includeSubclasses) {
339: if (includeSubclasses) {
340: int n = classes.length;
341: IntArray a = new IntArray(16);
342: for (int i = 0; i < n; i++) {
343: ClassMetaData cmd = getClassMetaData(classes[i]);
344: if (cmd == null) {
345: throw BindingSupportImpl.getInstance()
346: .invalidOperation(
347: "Not a persistent class: "
348: + classes[i].getName());
349: }
350: cmd.findHeirachyIndexes(a);
351: }
352: return a.toArray();
353: } else {
354: int n = classes.length;
355: int[] a = new int[n];
356: for (int i = 0; i < n; i++) {
357: ClassMetaData cmd = getClassMetaData(classes[i]);
358: if (cmd == null) {
359: throw BindingSupportImpl.getInstance()
360: .invalidOperation(
361: "Not a persistent class: "
362: + classes[i].getName());
363: }
364: a[i] = cmd.index;
365: }
366: return a;
367: }
368: }
369:
370: /**
371: * Convert an array of class indexes into an array of classes.
372: *
373: * @throws javax.jdo.JDOUserException if any class indexes are invalid
374: */
375: public Class[] convertFromClassIndexes(int[] classIndexes) {
376: int n = classIndexes.length;
377: Class[] ans = new Class[n];
378: int max = classes.length;
379: for (int i = 0; i < n; i++) {
380: int ci = classIndexes[i];
381: if (ci < 0 || ci >= max) {
382: throw BindingSupportImpl.getInstance()
383: .invalidOperation("Invalid class index: " + ci);
384: }
385: ans[i] = classes[ci].cls;
386: }
387: return ans;
388: }
389:
390: /**
391: * Check the consistency of the meta data. This will try and validate parts
392: * of the data structure against other parts to find bugs.
393: */
394: public void validate() {
395: for (int i = 0; i < classes.length; i++) {
396: classes[i].validate();
397: }
398: }
399:
400: /**
401: * Cleanup any data structures not needed after meta data generation.
402: */
403: public void cleanupAfterMetaDataGeneration() {
404: for (int i = 0; i < classes.length; i++) {
405: classes[i].cleanupAfterMetaDataGeneration();
406: }
407: }
408:
409: /**
410: * Create an OID instance from a datastore identity String.
411: *
412: * @param resolved Mark the OID as resolved (exact class known) or not
413: */
414: public OID newOIDFromIDString(String value, boolean resolved) {
415: try {
416: char c = value.charAt(0);
417: int cid = c - '0';
418: int i = 1;
419: for (;;) {
420: c = value.charAt(i++);
421: if (c == MDStatics.OID_CHAR_SEPERATOR)
422: break;
423: cid = cid * 10 + (c - '0');
424: }
425: ClassMetaData cmd = getClassMetaData(cid);
426: if (cmd == null) {
427: throw BindingSupportImpl.getInstance()
428: .invalidOperation(
429: "Invalid OID String (bad class ID): '"
430: + value + "'");
431: }
432: if (cmd.identityType != MDStatics.IDENTITY_TYPE_DATASTORE) {
433: throw BindingSupportImpl
434: .getInstance()
435: .invalidOperation(
436: "Class "
437: + cmd.qname
438: + " for class-id "
439: + cid
440: + " does not use datastore identity");
441: }
442: resolved = (resolved || (cmd.pcSubclasses == null));
443: OID oid = cmd.createOID(resolved
444: || cmd.pcSubclasses == null);
445: oid.fillFromIDString(value, i);
446: return oid;
447: } catch (RuntimeException e) {
448: if (BindingSupportImpl.getInstance().isOwnException(e)) {
449: throw e;
450: } else {
451: throw BindingSupportImpl.getInstance()
452: .invalidOperation(
453: "Invalid OID String: '" + value + "'",
454: e);
455: }
456: }
457: }
458:
459: /**
460: * Convert an internal OID to a String that can be parsed by
461: * {@link #newOIDFromExternalString(java.lang.String)}. This works for
462: * datastore and application identity classes.
463: */
464: public String toExternalString(OID oid) {
465: StringBuffer b = new StringBuffer();
466: ClassMetaData cmd = oid.getAvailableClassMetaData();
467: if (cmd.top.objectIdClass == null) {
468: b.append('d');
469: b.append(' ');
470: b.append(oid);
471: } else {
472: b.append('a');
473: b.append(' ');
474: b.append(cmd.classIdString);
475: b.append(' ');
476: Object o;
477: try {
478: o = cmd.top.objectIdClass.newInstance();
479: } catch (Exception e) {
480: throw BindingSupportImpl.getInstance().internal(
481: e.toString(), e);
482: }
483: oid.populateObjectIdClassInstance(o);
484: b.append(o);
485: }
486: String ans = b.toString();
487: if (Debug.DEBUG) {
488: OID oid2 = newOIDFromExternalString(ans);
489: if (!oid.equals(oid2)) {
490: throw BindingSupportImpl.getInstance().internal(
491: "string does not parse properly for " + oid);
492: }
493: }
494: return ans;
495: }
496:
497: /**
498: * Create an internal OID from a String previously created with
499: * {@link #toExternalString(com.versant.core.common.OID)}.
500: */
501: public OID newOIDFromExternalString(String s) {
502: switch (s.charAt(0)) {
503: case 'd':
504: return newOIDFromIDString(s.substring(2), true);
505: case 'a':
506: int i = s.indexOf(' ', 2);
507: int cid = Integer.parseInt(s.substring(2, i));
508: ClassMetaData cmd = getClassMetaData(cid);
509: if (cmd == null) {
510: throw BindingSupportImpl.getInstance()
511: .invalidOperation(
512: "Invalid string: '" + s
513: + "', no class found for "
514: + "class-id: " + cid);
515: }
516: Object o;
517: try {
518: o = JDOImplHelper.getInstance().newObjectIdInstance(
519: cmd.cls, s.substring(i + 1));
520: } catch (Exception e) {
521: if (BindingSupportImpl.getInstance().isOwnException(e)) {
522: throw (RuntimeException) e;
523: } else {
524: throw BindingSupportImpl
525: .getInstance()
526: .invalidOperation(
527: "Invalid string: '" + s + "': " + e,
528: e);
529: }
530: }
531: OID oid = cmd.createOID(true);
532: oid.fillFromPK(o);
533: return oid;
534: default:
535: throw BindingSupportImpl.getInstance().invalidOperation(
536: "Invalid string: '" + s + "'");
537: }
538: }
539:
540: public void addError(RuntimeException e, boolean quiet) {
541: if (error == null) {
542: error = e;
543: try {
544: Thread.sleep(1);
545: } catch (InterruptedException e1) {
546: // ignore
547: }
548: }
549: if (!quiet)
550: throw e;
551: }
552:
553: public boolean hasErrors() {
554: if (error != null) {
555: return true;
556: }
557: for (int i = 0; i < classes.length; i++) {
558: if (classes[i].hasErrors())
559: return true;
560: }
561: return false;
562: }
563:
564: public RuntimeException getFirstError() {
565: if (error != null) {
566: return error;
567: }
568: for (int i = 0; i < classes.length; i++) {
569: RuntimeException e = classes[i].getFirstError();
570: if (e != null)
571: return e;
572: }
573: return null;
574: }
575:
576: /**
577: * Convert an instance of an application identity class into an OID.
578: */
579: public OID convertFromAppIdToOID(Object appId) {
580: OID oid = null;
581: ClassMetaData cmd = getClassMetaDataForObjectIdClass(appId
582: .getClass());
583: if (cmd == null) {
584: throw BindingSupportImpl.getInstance().invalidOperation(
585: "Instance is not an objectid-class for any persistent classes: "
586: + appId.getClass().getName() + ": "
587: + toString(appId));
588: }
589: oid = cmd.createOID(false);
590: oid.fillFromPK(appId);
591: return oid;
592: }
593:
594: /**
595: * Converts an instance of a VersantOid to an internal OID.
596: */
597: public OID convertJDOGenieOIDtoOID(VersantOid versantOid) {
598: if (versantOid.actualOID != null) {
599: return versantOid.actualOID.getAvailableOID();
600: }
601: ClassMetaData cmd = getClassMetaData(versantOid.classId);
602: if (cmd == null) {
603: throw BindingSupportImpl.getInstance().invalidOperation(
604: "Invalid classID in VersantOid: " + versantOid);
605: }
606: OID oid = cmd.createOID(true);
607: oid.setLongPrimaryKey(versantOid.pk);
608: return versantOid.actualOID = oid;
609: }
610:
611: /**
612: * Converts an instance of a JDOGenieOID, application identity
613: * ID instance or internal OID to an internal OID.
614: */
615: public OID convertToOID(Object oid) {
616: if (oid instanceof VersantOid) {
617: return convertJDOGenieOIDtoOID((VersantOid) oid);
618: } else if (oid instanceof OID) {
619: return (OID) oid;
620: } else {
621: return convertFromAppIdToOID(oid);
622: }
623: }
624:
625: /**
626: * Converts an array of a JDOGenieOID's, application identity ID
627: * instances or OIDs to an array of internal OID.
628: */
629: public OID[] convertToOID(Object[] oids, int n) {
630: OID[] a = new OID[n];
631: for (int i = 0; i < n; i++) {
632: Object oid = oids[i];
633: if (oid instanceof VersantOid) {
634: a[i] = convertJDOGenieOIDtoOID((VersantOid) oid);
635: } else if (oid instanceof OID) {
636: a[i] = (OID) oid;
637: } else if (oid != null) {
638: a[i] = convertFromAppIdToOID(oid);
639: }
640: }
641: return a;
642: }
643:
644: /**
645: * Safely do toString on o. If there is an exception then return a message
646: * indicating that toString() failed including the exception.
647: */
648: private static String toString(Object o) {
649: if (o == null)
650: return "null";
651: try {
652: return o.toString();
653: } catch (Exception e) {
654: return o.getClass().getName() + ".toString() failed: " + e;
655: }
656: }
657:
658: /**
659: * Get all the ClassMetaData for the heirachy rooted at base.
660: */
661: public ClassMetaData[] getClassMetaDataForHeirachy(
662: ClassMetaData base) {
663: if (base.pcSubclasses == null) {
664: return new ClassMetaData[] { base };
665: }
666: ArrayList a = new ArrayList();
667: getClassMetaDataForHeirachyImp(base, a);
668: ClassMetaData[] ans = new ClassMetaData[a.size()];
669: a.toArray(ans);
670: return ans;
671: }
672:
673: private void getClassMetaDataForHeirachyImp(ClassMetaData cmd,
674: ArrayList a) {
675: a.add(cmd);
676: if (cmd.pcSubclasses != null) {
677: for (int i = 0; i < cmd.pcSubclasses.length; i++) {
678: getClassMetaDataForHeirachyImp(cmd.pcSubclasses[i], a);
679: }
680: }
681: }
682:
683: /**
684: * Create an unresolved OID for the class for classIndex.
685: */
686: public OID createUnresolvedOID(int classIndex) {
687: return classes[classIndex].createOID(false);
688: }
689:
690: /**
691: * Set the factory used to create untyped OIDs. The default factory throws
692: * an unsupported option exception.
693: */
694: public void setUntypedOIDFactory(
695: StateAndOIDFactory untypedOIDFactory) {
696: this .untypedOIDFactory = untypedOIDFactory;
697: }
698:
699: /**
700: * Create a new untyped OID if the store supports this or throw an
701: * unsupported option exception if not.
702: */
703: public OID createUntypedOID() {
704: return untypedOIDFactory.createUntypedOID();
705: }
706:
707: private Map candidatesForClsMap = new HashMap();
708:
709: /**
710: * Return all the root candidate classes for persistent heirarchies that implement
711: * or extend the supplied class.
712: */
713: public synchronized Class[] getQueryCandidatesFor(Class cls) {
714: //if class is an interface and the interface is not a persistent interface
715: //then find all the persistent classes that implement it.
716: //for each of these classes we must find there root persistent object.
717: Class[] clsArray = (Class[]) candidatesForClsMap.get(cls);
718: if (clsArray == null) {
719: ClassMetaData cmd = getClassMetaData(cls);
720: if (cmd != null && !cmd.horizontal) {
721: //this class is persistent and is not horizontally mapped so just return intself
722: clsArray = new Class[] { cls };
723: } else {
724: Set result = new HashSet();
725: if (cls.isInterface()) {
726: throw BindingSupportImpl
727: .getInstance()
728: .unsupported(
729: "Query by interface is not currently supported");
730: } else {
731: for (int i = 0; i < classes.length; i++) {
732: ClassMetaData aClass = classes[i];
733: if (cls.equals(aClass.cls.getSuperclass())) {
734: result.add(aClass.cls);
735: }
736: }
737: }
738: if (result.isEmpty()) {
739: clsArray = EMPTY_CLASS_ARRAY;
740: } else {
741: clsArray = new Class[result.size()];
742: result.toArray(clsArray);
743: }
744: }
745: candidatesForClsMap.put(cls, clsArray);
746: }
747: return clsArray;
748: }
749:
750: }
|