001: /**
002: * Copyright (C) 2006 NetMind Consulting Bt.
003: *
004: * This library is free software; you can redistribute it and/or
005: * modify it under the terms of the GNU Lesser General Public
006: * License as published by the Free Software Foundation; either
007: * version 3 of the License, or (at your option) any later version.
008: *
009: * This library is distributed in the hope that it will be useful,
010: * but WITHOUT ANY WARRANTY; without even the implied warranty of
011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012: * Lesser General Public License for more details.
013: *
014: * You should have received a copy of the GNU Lesser General Public
015: * License along with this library; if not, write to the Free Software
016: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
017: */package hu.netmind.persistence;
018:
019: import java.util.Map;
020: import java.util.HashMap;
021: import java.util.List;
022: import java.util.Iterator;
023: import java.util.Date;
024: import java.util.Vector;
025: import java.util.HashSet;
026: import java.util.Stack;
027: import java.lang.reflect.Modifier;
028: import hu.netmind.persistence.parser.QueryStatement;
029: import org.apache.log4j.Logger;
030:
031: /**
032: * This class keeps track of different classes and objects. It's main
033: * purpose is to implement object reflection based logic, and provide
034: * type information.
035: * @author Brautigam Robert
036: * @version Revision: $Revision$
037: */
038: public class ClassTracker implements TransactionListener {
039: private static Logger logger = Logger.getLogger(ClassTracker.class);
040:
041: public final static int TYPE_NONE = 0;
042: public final static int TYPE_PRIMITIVE = 1;
043: public final static int TYPE_HANDLED = 2;
044: public final static int TYPE_OBJECT = 4;
045: public final static int TYPE_JAVA = 6;
046: public final static int TYPE_RESERVED = 5;
047:
048: private StoreContext context;
049: private Map classInfos; // Entry->Info mapping
050: private Map tableEntries; // Table name->Entry mapping
051: private Map transactionTableEntries; // Transaction->(Name->Entry)
052: private Map relatedClassEntries; // Entry->Entry list
053: private Map rootClassEntries; // Entry->Entry list
054:
055: public ClassTracker(StoreContext context) {
056: this .context = context;
057: context.getTransactionTracker().addListener(this );
058: classInfos = new HashMap();
059: tableEntries = new HashMap();
060: transactionTableEntries = new HashMap();
061: relatedClassEntries = new HashMap();
062: rootClassEntries = new HashMap();
063: // Load class names
064: Transaction transaction = context.getTransactionTracker()
065: .getTransaction(TransactionTracker.TX_REQUIRED);
066: transaction.begin();
067: try {
068: loadTableNames(transaction);
069: } catch (StoreException e) {
070: transaction.markRollbackOnly();
071: throw e;
072: } catch (Throwable e) {
073: transaction.markRollbackOnly();
074: throw new StoreException("unexcepted error", e);
075: } finally {
076: transaction.commit();
077: }
078: }
079:
080: /**
081: * Return whether the given class is a primitive type.
082: */
083: public static boolean isPrimitive(Class clazz) {
084: if (clazz == null)
085: return false;
086: return (clazz.equals(int.class)) || (clazz.equals(short.class))
087: || (clazz.equals(byte.class))
088: || (clazz.equals(long.class))
089: || (clazz.equals(float.class))
090: || (clazz.equals(double.class))
091: || (clazz.equals(char.class))
092: || (clazz.equals(boolean.class))
093: || (clazz.equals(Integer.class))
094: || (clazz.equals(Short.class))
095: || (clazz.equals(Byte.class))
096: || (clazz.equals(Long.class))
097: || (clazz.equals(Float.class))
098: || (clazz.equals(Double.class))
099: || (clazz.equals(Character.class))
100: || (clazz.equals(String.class))
101: || (clazz.equals(Boolean.class))
102: || (clazz.equals(byte[].class))
103: || (clazz.equals(Date.class))
104: || clazz.equals(Character.class);
105: }
106:
107: /**
108: * Get the type id of given class.
109: */
110: public int getType(Class clazz) {
111: if (clazz == null)
112: return TYPE_NONE;
113: if (context.getTypeHandlerTracker().isHandled(clazz))
114: return TYPE_HANDLED;
115: if (isPrimitive(clazz))
116: return TYPE_PRIMITIVE;
117: // Array types not allowed
118: if (clazz.getName().startsWith("["))
119: return TYPE_RESERVED;
120: // The rest is custom objects.
121: return TYPE_OBJECT;
122: }
123:
124: /**
125: * Get class info for a class entry.
126: */
127: public ClassInfo getClassInfo(ClassEntry entry) {
128: return getClassInfo(entry.getSourceClass(), entry
129: .getDynamicName());
130: }
131:
132: /**
133: * Get class info for an object and it's class.
134: */
135: public ClassInfo getClassInfo(Class clazz, Object obj) {
136: if ((obj instanceof DynamicObject)
137: && (obj.getClass().equals(clazz)))
138: return getClassInfo(clazz, ((DynamicObject) obj)
139: .getPersistenceDynamicName());
140: else
141: return getClassInfo(clazz, null);
142: }
143:
144: /**
145: * Get class info from a string.
146: */
147: public ClassInfo getClassInfo(String className, String dynamicName) {
148: if (className == null)
149: return null;
150: try {
151: Class clazz = Class.forName(className);
152: return getClassInfo(clazz, dynamicName);
153: } catch (StoreException e) {
154: throw e;
155: } catch (Exception e) {
156: throw new StoreException(
157: "class cannot be located with name '" + className
158: + "'", e);
159: }
160: }
161:
162: /**
163: * Get all related classes to the given entry. Related class entries are
164: * all given class' super- and sub-classes which are all storable.
165: * Calling this method on non-storable entry will result in an undefined
166: * result.
167: */
168: public List getRelatedClassEntries(ClassEntry entry) {
169: ClassInfo info = getClassInfo(entry); // Register
170: List result = (List) relatedClassEntries.get(entry);
171: if (result == null)
172: return new Vector();
173: return result;
174: }
175:
176: /**
177: * Get all subclasses of given entry, including itself.
178: */
179: public List getSubClasses(ClassEntry entry) {
180: List relatedEntries = getRelatedClassEntries(entry);
181: Vector result = new Vector();
182: result.add(entry.getSourceClass());
183: for (int i = 0; i < relatedEntries.size(); i++) {
184: ClassEntry subEntry = (ClassEntry) relatedEntries.get(i);
185: if ((entry.getSourceClass().isAssignableFrom(subEntry
186: .getSourceClass()))
187: && (!result.contains(subEntry.getSourceClass())))
188: result.add(subEntry.getSourceClass());
189: }
190: if (logger.isDebugEnabled())
191: logger.debug("returning subclasses: " + result + ", for: "
192: + entry);
193: return result;
194: }
195:
196: /**
197: * Get all storable roots for given entry. A storable root for a
198: * storable entry is itself. A non-storable entry (such as java.lang.Object)
199: * will have potentially a lot of storable roots: All classes in the
200: * class hierarchy which are storable, but have non-storable superclasses.
201: * So, storable roots are the first storable entry in a class hierarchy
202: * path (roots of the storable sub-forest). When a query is received
203: * for a non-stored class, the query will split into queries for all
204: * storable roots.
205: */
206: public List getStorableRootClassEntries(ClassEntry entry) {
207: ClassInfo info = getClassInfo(entry); // Register
208: List result = (List) rootClassEntries.get(entry);
209: if (result == null)
210: return new Vector();
211: return result;
212: }
213:
214: /**
215: * Get class information object of given class. Also, if class
216: * information does not exist, it will be created.
217: */
218: public ClassInfo getClassInfo(Class clazz, String dynamicName) {
219: if (clazz == null)
220: return null;
221: ClassInfo result = null;
222: // Create entry
223: ClassEntry entry = new ClassEntry(clazz, dynamicName);
224: // Compute full name
225: String fullClassName = entry.getFullName();
226: // Get the classinfo from cache. If it's not there, then
227: // create it.
228: synchronized (classInfos) {
229: result = (ClassInfo) classInfos.get(fullClassName);
230: if (result == null) {
231: logger.debug("class info for class '" + fullClassName
232: + "' did not exist, creating.");
233: // Not present, so create.
234: // First, the superclasses if this is a static class,
235: // or the common class under the dynamic class.
236: if (entry.getDynamicName() == null) {
237: // A non-dynamic class has it's superclass as
238: // superclass (no surprises here)
239: if (getType(clazz.getSuperclass()) == TYPE_PRIMITIVE)
240: throw new StoreException("class " + clazz
241: + " subclassed a primitive type ("
242: + clazz.getSuperclass()
243: + "), this is not allowed.");
244: getClassInfo(clazz.getSuperclass(), null);
245: // Now register all interface classes also, so
246: // we can later select this class with it's interfaces
247: // also
248: Class interfaces[] = clazz.getInterfaces();
249: for (int i = 0; i < interfaces.length; i++)
250: getClassInfo(interfaces[i], null);
251: } else {
252: // Dynamic class has the class with 'null' dynamic name
253: // as superclass. It has no interface classes directly
254: // associated.
255: getClassInfo(clazz, null);
256: }
257: // Now register the class itself
258: result = new ClassInfo(context, entry);
259: classInfos.put(fullClassName, result);
260: // Update class relations
261: updateClassEntryRelations(entry);
262: } else {
263: // If the class info did not change, then
264: // return them, else update the classinfo.
265: if (result.hasChanged()) {
266: logger.debug("class info for class '" + clazz
267: + "' has changed.");
268: // Allocate new instance and update classinfo table
269: result = new ClassInfo(context, entry);
270: // Replace old info
271: classInfos.put(fullClassName, result);
272: } else {
273: logger.debug("class info for class '" + clazz
274: + "' did not change, returning.");
275: // Return old class info, because nothing changed.
276: return result;
277: }
278: }
279: // Check whether the class is valid, if not, remove it from the
280: // infos.
281: try {
282: isValid(result);
283: } catch (StoreException e) {
284: classInfos.remove(fullClassName);
285: throw e;
286: }
287: }
288: // Check whether this class is entitled to a table.
289: // Interface classes, and reserved classes are not, so the method
290: // will return, and not create the table for it (note, that the
291: // class relations were updated, so we remember this class none
292: // theless)
293: boolean create = result.getSourceEntry().isStorable();
294: // Check that this is not an inner or anonymous class, which are
295: // not handled, so an error must be generated.
296: if (clazz.getDeclaringClass() != null)
297: throw new StoreException(
298: "class '"
299: + clazz
300: + "' is an inner or anonymous class, it won't be handled.");
301: // Check that the object is valid, meaning it has a default
302: // constructor (if it's non-abstract)
303: if ((create) && (!Modifier.isAbstract(clazz.getModifiers()))
304: && (getType(clazz) == TYPE_OBJECT)) {
305: // Only check, if it will be storable.
306: try {
307: clazz.getConstructor(new Class[0]);
308: } catch (NoSuchMethodException e) {
309: throw new StoreException(
310: "class invalid, it had no default constructor: "
311: + clazz);
312: }
313: }
314: // If all checks are successful, and the class info was not yet present,
315: // we should ensure that the table exists in database.
316: Transaction transaction = context.getTransactionTracker()
317: .getTransaction(TransactionTracker.TX_NEW);
318: transaction.begin();
319: try {
320: // Generate table schemas, one for class an one for each list or
321: // map type
322: Iterator iterator = result.getStrictAttributeNames(entry)
323: .iterator();
324: HashMap attributeTypes = new HashMap();
325: while (iterator.hasNext()) {
326: String attributeName = iterator.next().toString();
327: Class type = result.getAttributeType(attributeName);
328: switch (getType(type)) {
329: case TYPE_PRIMITIVE:
330: attributeTypes.put(attributeName, type);
331: break;
332: case TYPE_OBJECT:
333: attributeTypes.put(attributeName, Long.class);
334: break;
335: case TYPE_HANDLED:
336: TypeHandler handler = context
337: .getTypeHandlerTracker().getHandler(type);
338: handler.ensureTableExists(result, attributeName,
339: create);
340: attributeTypes.putAll(handler
341: .getAttributeTypes(attributeName));
342: break;
343: default:
344: // Unknown type, leave it
345: logger.warn(clazz.toString()
346: + " has attribute of unhandled class: "
347: + type);
348: break;
349: }
350: }
351: // Remove reserved names
352: Iterator typesIterator = attributeTypes.keySet().iterator();
353: while (typesIterator.hasNext()) {
354: String name = (String) typesIterator.next();
355: if (name.startsWith("persistence"))
356: typesIterator.remove();
357: }
358: // Make object table
359: Vector objectKeys = new Vector();
360: objectKeys.add("persistence_id");
361: objectKeys.add("persistence_txstart");
362: attributeTypes.put("persistence_id", Long.class);
363: attributeTypes.put("persistence_start", Long.class);
364: attributeTypes.put("persistence_end", Long.class);
365: attributeTypes.put("persistence_txstartid", Long.class);
366: attributeTypes.put("persistence_txstart", Long.class);
367: attributeTypes.put("persistence_txendid", Long.class);
368: attributeTypes.put("persistence_txend", Long.class);
369: logger.debug("creating table for class: " + clazz
370: + ", fields: " + attributeTypes + ", keys: "
371: + objectKeys);
372: context.getDatabase().ensureTable(transaction,
373: result.getTableName(entry), attributeTypes,
374: objectKeys, create);
375: // Add new table to table names if not present.
376: // Look in the global map, then in the transaction map
377: boolean classPresent = false;
378: synchronized (tableEntries) {
379: classPresent = tableEntries.containsKey(result
380: .getTableName(entry));
381: if (!classPresent) {
382: Map transactionMap = (Map) transactionTableEntries
383: .get(transaction);
384: if (transactionMap == null) {
385: transactionMap = new HashMap();
386: transactionTableEntries.put(transaction,
387: transactionMap);
388: } else {
389: classPresent = transactionMap
390: .containsKey(result.getTableName(entry));
391: }
392: if (!classPresent)
393: transactionMap.put(result.getTableName(entry),
394: entry);
395: }
396: }
397: // Add new table to table names table if not yet present.
398: // To work around the following problem, the class is tried
399: // to be removed first: When two nodes are started with new
400: // classes, the first is able to insert the class info, but
401: // the second will not be able to insert it, because of duplicate
402: // keys.
403: if (!classPresent) {
404: logger
405: .debug("entry: "
406: + result.getSourceEntry()
407: + " was not present in database, so adding. It received tablename: "
408: + result.getTableName(entry));
409: Map attrs = new HashMap();
410: attrs.put("tablename", result.getTableName(entry));
411: context.getDatabase().remove(transaction,
412: "persistence_classes", attrs);
413: attrs.put("classname", result.getSourceEntry()
414: .getSourceClass().getName());
415: attrs.put("dynamic", dynamicName);
416: context.getDatabase().insert(transaction,
417: "persistence_classes", attrs);
418: }
419: } catch (StoreException e) {
420: transaction.markRollbackOnly();
421: throw e;
422: } catch (Throwable e) {
423: transaction.markRollbackOnly();
424: throw new StoreException("unexpected error.", e);
425: } finally {
426: transaction.commit();
427: }
428: // Return class info
429: logger.debug("returning classinfo for class: "
430: + clazz.getName() + ": " + result);
431: return result;
432: }
433:
434: /**
435: * Determine whether a class is valid. The current algorithm checks:
436: * <ul>
437: * <li>Class can't be empty (abstract and no attributes) and have dynamic attributes.</li>
438: * <li>Class can't have an attribute which is contained in a related
439: * class.</li>
440: * </ul>
441: * If the class is invalid, a StoreException is thrown with the description
442: * of the error.
443: */
444: private void isValid(ClassInfo info) {
445: // Check emptyness against dynamic attributes
446: if ((info.getSourceEntry().isEmpty())
447: && (info.getSourceEntry().hasDynamicAttributes()))
448: throw new StoreException(
449: "class info: "
450: + info
451: + " is empty (abstract and has no static attributes), but has dynamic attributes. "
452: + "This is not allowed, because it could be mistaken for a non-storable class. If it should be stored, please declare it non-abstract, "
453: + "or if it has important attributes, declare them static.");
454: // Search for common attributes
455: List attributeNames = info.getStrictAttributeNames(info
456: .getSourceEntry());
457: // Search for the root superentry.
458: ClassEntry entry = info.getSourceEntry();
459: ClassEntry super Entry = entry.getSuperEntry();
460: while ((super Entry != null) && (super Entry.isStorable())) {
461: entry = super Entry;
462: super Entry = entry.getSuperEntry();
463: }
464: // Now check all related classes and ensure, they don't have
465: // any attributes common with this one.
466: List relatedEntries = getRelatedClassEntries(entry);
467: logger
468: .debug("making sure, that no common attributes are present for entry: "
469: + info.getSourceEntry()
470: + ", with: "
471: + relatedEntries);
472: for (int i = 0; i < relatedEntries.size(); i++) {
473: ClassInfo check = getClassInfo((ClassEntry) relatedEntries
474: .get(i));
475: if (check.equals(info))
476: continue;
477: for (int o = 0; o < attributeNames.size(); o++) {
478: String attributeName = (String) attributeNames.get(o);
479: if (check.getStrictAttributeNames(
480: check.getSourceEntry()).contains(attributeName))
481: throw new StoreException(
482: "class info: "
483: + info
484: + " had a common attribute '"
485: + attributeName
486: + "' with '"
487: + check
488: + "', and common attributes in related classes are currently not allowed");
489: }
490: }
491: }
492:
493: /**
494: * Get the class info for a given table name.
495: */
496: public ClassInfo getTableClassInfo(String tableName) {
497: ClassEntry entry = null;
498: synchronized (tableEntries) {
499: entry = (ClassEntry) tableEntries.get(tableName);
500: }
501: if (entry == null)
502: return null;
503: return getClassInfo(entry.getSourceClass(), entry
504: .getDynamicName());
505: }
506:
507: private void loadTableNames(Transaction transaction) {
508: // First ensure that table exists
509: HashMap tableAttrs = new HashMap();
510: tableAttrs.put("tablename", String.class);
511: tableAttrs.put("classname", String.class);
512: tableAttrs.put("dynamic", String.class);
513: Vector tableKeys = new Vector();
514: tableKeys.add("tablename");
515: context.getDatabase().ensureTable(transaction,
516: "persistence_classes", tableAttrs, tableKeys, true);
517: // Load table
518: QueryStatement stmt = new QueryStatement("persistence_classes",
519: null, null);
520: SearchResult result = context.getDatabase().search(transaction,
521: stmt, null);
522: for (int i = 0; i < result.getResult().size(); i++) {
523: Map attributes = (Map) result.getResult().get(i);
524: String tableName = (String) attributes.get("tablename");
525: String className = (String) attributes.get("classname");
526: String dynamicName = (String) attributes.get("dynamic");
527: try {
528: Class clazz = Class.forName(className);
529: ClassEntry entry = new ClassEntry(clazz, dynamicName);
530: tableEntries.put(tableName, entry);
531: updateClassEntryRelations(entry);
532: logger.debug("class tables loaded known class name: "
533: + className + ", class: " + clazz);
534: } catch (StoreException e) {
535: throw e;
536: } catch (Exception e) {
537: logger
538: .warn("could not find class ("
539: + className
540: + ") to table: "
541: + tableName
542: + ". This may only mean that a class was removed, but there are traces in the database for it, in this case, you may disregard this message: "
543: + e.getMessage() + " ("
544: + e.getClass().getName() + ")");
545: }
546: }
547: }
548:
549: /**
550: * Update class relations for a given class. This means insert this class
551: * as related to all subclasses, and mark all subclasses related for this
552: * class.
553: */
554: private void updateClassEntryRelations(ClassEntry entry) {
555: // The first superclass differs when handling dynamic classes,
556: // because then, the superclass is the ordinary class of dynamic class
557: ClassEntry super Entry = entry.getSuperEntry();
558: // Enter into graph walker as initial node
559: Stack openEntries = new Stack();
560: if (super Entry != null)
561: openEntries.push(super Entry);
562: // This class will be potentially a storable root for all it's
563: // interfaces. This is not yet sure, but we will check later.
564: Vector storableSuperEntries = new Vector();
565: Vector nonstorableSuperEntries = new Vector();
566: for (int i = 0; i < entry.getSourceClass().getInterfaces().length; i++)
567: nonstorableSuperEntries.add(new ClassEntry(entry
568: .getSourceClass().getInterfaces()[i], null));
569: // Go through all superclasses and insert this class
570: // as related to them. Insert to interfaces and reserved classes
571: // too, because we need to know if they are related.
572: // The algorithm is a simple graph walker. All not yet visited
573: // nodes are stored in openEntries, and visited one-by-one. Note, that
574: // this works, because there is no cycle in the class hierarchy.
575: while (!openEntries.empty()) {
576: // Get top
577: super Entry = (ClassEntry) openEntries.pop();
578: // Add entry to superclass' related entries
579: Vector classEntries = (Vector) relatedClassEntries
580: .get(super Entry);
581: if (classEntries == null) {
582: classEntries = new Vector();
583: relatedClassEntries.put(super Entry, classEntries);
584: }
585: if (!classEntries.contains(entry))
586: classEntries.add(entry);
587: // Insert into storable super entries if this entry
588: // is storable, to non-storable super if it's not.
589: if (super Entry.isStorable()) {
590: // Entry is storable, so we will enter this as a related
591: // class.
592: storableSuperEntries.add(super Entry);
593: } else {
594: // Entry is not storable, so potentially it's first
595: // storable root is this entry. Of course, if this entry
596: // has a storable root, then this is not the real root,
597: // but this will be detected later.
598: nonstorableSuperEntries.add(super Entry);
599: }
600: // Add superclass to open nodes
601: if (super Entry.getSourceClass().getSuperclass() != null)
602: openEntries.push(new ClassEntry(super Entry
603: .getSourceClass().getSuperclass(), null));
604: // Also add all interfaces as not visited
605: for (int i = 0; i < super Entry.getSourceClass()
606: .getInterfaces().length; i++)
607: openEntries.add(new ClassEntry(super Entry
608: .getSourceClass().getInterfaces()[i], null));
609: }
610: if (logger.isDebugEnabled())
611: logger.debug("class tracker determined superclasses for "
612: + entry + ", non-storable: "
613: + nonstorableSuperEntries + ", storable: "
614: + storableSuperEntries);
615: // Now insert all storable superclasses as related to this class.
616: // If this class is not storable, this should be empty anyway.
617: Vector classEntries = (Vector) relatedClassEntries.get(entry);
618: if (classEntries == null) {
619: classEntries = new Vector();
620: relatedClassEntries.put(entry, classEntries);
621: }
622: classEntries.removeAll(storableSuperEntries);
623: classEntries.addAll(storableSuperEntries);
624: // Now add this class as a storable root to all non-storable
625: // superclasses and superinterfaces, which do not have a storable
626: // root which is a superclass to this entry.
627: if (entry.isStorable()) {
628: // This entry is storable, so it's only storable root is itself
629: List rootEntries = new Vector();
630: rootEntries.add(entry);
631: rootClassEntries.put(entry, rootEntries);
632: // Now check it's non-storable supers one-by-one to see
633: // if this class is really their storable root.
634: for (int i = 0; i < nonstorableSuperEntries.size(); i++) {
635: ClassEntry nonstorableSuperEntry = (ClassEntry) nonstorableSuperEntries
636: .get(i);
637: rootEntries = (List) rootClassEntries
638: .get(nonstorableSuperEntry);
639: if (rootEntries == null) {
640: // Entry did not have a storable root yet, so this is
641: // surely a storable root.
642: rootEntries = new Vector();
643: rootEntries.add(entry);
644: rootClassEntries.put(nonstorableSuperEntry,
645: rootEntries);
646: } else {
647: // Entry has some storable roots, so check them.
648: // If a root is a superclass of this class, then this
649: // class is not the storable root.
650: // If a root is a subclass of this class, that should not
651: // happen, because superclasses are always handled first!
652: boolean hasSuperRoot = false;
653: for (int o = 0; (o < rootEntries.size())
654: && (!hasSuperRoot); o++) {
655: ClassEntry rootEntry = (ClassEntry) rootEntries
656: .get(o);
657: if (rootEntry.getSourceClass().equals(
658: entry.getSourceClass())) {
659: // The class is present as root. If this entry is
660: // a dynamic subclass of this class, then it can't
661: // be the root, because it superclass is already root.
662: hasSuperRoot = true;
663: } else {
664: if (rootEntry.getSourceClass()
665: .isAssignableFrom(
666: entry.getSourceClass()))
667: hasSuperRoot = true; // Real superclass found
668: if (entry.getSourceClass()
669: .isAssignableFrom(
670: rootEntry.getSourceClass())) {
671: // Subclass found, so replace it
672: logger.debug("replacing storable root "
673: + rootEntry + ", with: "
674: + entry);
675: rootEntries.remove(o--); // Remove this, we found a more suitable root
676: }
677: }
678: }
679: if (!hasSuperRoot) {
680: rootEntries.add(entry);
681: if (logger.isDebugEnabled())
682: logger.debug("added " + entry
683: + " as storable root for: "
684: + nonstorableSuperEntry
685: + ", roots until now: "
686: + rootEntries);
687: }
688: }
689: }
690: } else {
691: // This is a non-storable entry, so of course non of the
692: // non-storable superclasses will have this as storable root.
693: }
694: }
695:
696: /**
697: * Get a Class instance for a class name postfix. The given parameter
698: * is treated as a postfix for a fully qualified class name. The postfix
699: * is considered matching, when it contains whole class of package
700: * qualifiers. For example: "book" matches "hu.netmind.persistence.Book"
701: * class, but does not match "hu.netmind.persistence.CookBook". Also
702: * "persistence.book" matches "hu.netmind.persistence.Book", but
703: * "tence.book" does not match to previous class.<br>
704: * If no classes are found null is returned. If more than one matching
705: * class is present, then one of them is returned (no guarantees which
706: * one is picked).
707: * @param postfix The class name postfix.
708: * @return The class info for which the postfix applies, or null.
709: */
710: public ClassInfo getMatchingClassInfo(String postfix) {
711: Transaction transaction = context.getTransactionTracker()
712: .getTransaction(TransactionTracker.TX_OPTIONAL);
713: // Get a list of all known classes from database
714: HashSet entries = new HashSet();
715: synchronized (tableEntries) {
716: Map transactionMap = (Map) transactionTableEntries
717: .get(transaction);
718: if (transactionMap != null)
719: entries.addAll(transactionMap.values());
720: entries.addAll(relatedClassEntries.keySet());
721: }
722: // Search for given prefix
723: postfix = postfix.toLowerCase();
724: Iterator iterator = entries.iterator();
725: while (iterator.hasNext()) {
726: // Check class
727: ClassEntry entry = (ClassEntry) iterator.next();
728: String className = entry.getFullName().toLowerCase();
729: if ((className.endsWith(postfix))
730: && ((className.length() == postfix.length()) || (className
731: .charAt(className.length()
732: - postfix.length() - 1) == '.')))
733: return getClassInfo(entry.getSourceClass(), entry
734: .getDynamicName());
735: }
736: // None found
737: return null;
738: }
739:
740: /**
741: * Activate table names added in the transaction.
742: */
743: public void transactionCommited(Transaction transaction) {
744: synchronized (tableEntries) {
745: Map transactionMap = (Map) transactionTableEntries
746: .get(transaction);
747: if (transactionMap == null)
748: return;
749: tableEntries.putAll(transactionMap);
750: transactionTableEntries.remove(transaction);
751: }
752: }
753:
754: /**
755: * Discard table names added in the transaction.
756: */
757: public void transactionRolledback(Transaction transaction) {
758: synchronized (tableEntries) {
759: transactionTableEntries.remove(transaction);
760: }
761: }
762:
763: /**
764: * Return whether given class can be dynamic.
765: */
766: public boolean isDynamicCanidate(Class clazz) {
767: return DynamicObject.class.isAssignableFrom(clazz);
768: }
769:
770: }
|