001: /**********************************************************************
002: Copyright (c) 2004 Andy Jefferson 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 - added Queryable implementation
018: ...
019: **********************************************************************/package org.jpox.sco;
020:
021: import java.io.ObjectStreamException;
022: import java.util.Collection;
023: import java.util.Iterator;
024: import java.util.Map;
025:
026: import org.jpox.ClassLoaderResolver;
027: import org.jpox.ObjectManager;
028: import org.jpox.StateManager;
029: import org.jpox.metadata.AbstractMemberMetaData;
030: import org.jpox.metadata.FieldPersistenceModifier;
031: import org.jpox.sco.exceptions.IncompatibleFieldTypeException;
032: import org.jpox.sco.exceptions.NullsNotAllowedException;
033: import org.jpox.sco.exceptions.QueryUnownedSCOException;
034: import org.jpox.sco.queued.ClearOperation;
035: import org.jpox.sco.queued.PutOperation;
036: import org.jpox.sco.queued.QueuedOperation;
037: import org.jpox.sco.queued.RemoveOperation;
038: import org.jpox.state.FetchPlanState;
039: import org.jpox.state.StateManagerFactory;
040: import org.jpox.store.DatastoreClass;
041: import org.jpox.store.DatastoreIdentifier;
042: import org.jpox.store.expression.QueryExpression;
043: import org.jpox.store.mapping.JavaTypeMapping;
044: import org.jpox.store.mapping.MapMapping;
045: import org.jpox.store.query.Queryable;
046: import org.jpox.store.query.ResultObjectFactory;
047: import org.jpox.store.scostore.MapStore;
048: import org.jpox.util.JPOXLogger;
049: import org.jpox.util.Localiser;
050: import org.jpox.util.StringUtils;
051:
052: /**
053: * A mutable second-class Hashtable object. Backed by a MapStore object.
054: *
055: * @version $Revision: 1.86 $
056: */
057: public class Hashtable extends java.util.Hashtable implements SCOMap,
058: Cloneable, Queryable {
059: protected static final Localiser LOCALISER = Localiser
060: .getInstance("org.jpox.Localisation");
061:
062: private transient Object owner;
063: private transient StateManager ownerSM;
064: private transient String fieldName;
065: private transient int fieldNumber;
066: private transient Class valueType;
067: private transient boolean allowNulls;
068:
069: /** The "backing store" */
070: protected MapStore backingStore;
071:
072: /** The internal "delegate". */
073: protected java.util.Hashtable delegate;
074:
075: /** Whether to use "delegate" caching. */
076: protected boolean useCache = true;
077:
078: /** Status flag whether the map is loaded into the cache. */
079: protected boolean isCacheLoaded = false;
080:
081: /** Whether the SCO is in "direct" or "queued" mode. */
082: boolean queued = false;
083:
084: /** Queued operations when using "queued" mode. */
085: private java.util.ArrayList queuedOperations = null;
086:
087: /**
088: * Constructor
089: * @param ownerSM the owner of this Map
090: * @param fieldName the declared field name
091: */
092: public Hashtable(StateManager ownerSM, String fieldName) {
093: this .ownerSM = ownerSM;
094: this .fieldName = fieldName;
095: this .allowNulls = false; // Underlying Hashtable prohibits null keys/values
096:
097: // Set up our "delegate"
098: this .delegate = new java.util.Hashtable();
099:
100: if (ownerSM != null) {
101: AbstractMemberMetaData fmd = ownerSM.getClassMetaData()
102: .getMetaDataForMember(fieldName);
103: owner = ownerSM.getObject();
104: fieldNumber = fmd.getAbsoluteFieldNumber();
105: if (ownerSM.getStoreManager().usesDatastoreClass()) {
106: queued = SCOUtils.useContainerQueueing(ownerSM);
107: useCache = SCOUtils.useContainerCache(ownerSM,
108: fieldName);
109: }
110:
111: if (ownerSM.getStoreManager().usesDatastoreClass()
112: && !SCOUtils.mapHasSerialisedKeysAndValues(fmd)
113: && fmd.getPersistenceModifier() == FieldPersistenceModifier.PERSISTENT) {
114: ClassLoaderResolver clr = ownerSM.getObjectManager()
115: .getClassLoaderResolver();
116: DatastoreClass ownerTable = ownerSM.getStoreManager()
117: .getDatastoreClass(owner.getClass().getName(),
118: clr);
119: JavaTypeMapping m = ownerTable.getFieldMapping(fmd);
120: if (!(m instanceof MapMapping)) {
121: throw new IncompatibleFieldTypeException(ownerSM,
122: fieldName, java.util.Hashtable.class
123: .getName(), fmd.getTypeName());
124: }
125:
126: this .backingStore = (MapStore) ownerSM
127: .getStoreManager().getStore(clr, fmd,
128: java.util.Hashtable.class);
129: this .valueType = clr.classForName(backingStore
130: .getValueType());
131: }
132:
133: if (JPOXLogger.PERSISTENCE.isDebugEnabled()) {
134: JPOXLogger.PERSISTENCE.debug(SCOUtils
135: .getContainerInfoMessage(ownerSM, fieldName,
136: this , useCache, queued, allowNulls,
137: SCOUtils.useCachedLazyLoading(ownerSM,
138: fieldName)));
139: }
140: }
141: }
142:
143: /**
144: * Method to initialise the SCO from an existing value.
145: * @param o Object to set value using.
146: * @param forInsert Whether the object needs inserting in the datastore with this value
147: * @param forUpdate Whether to update the datastore with this value
148: */
149: public void initialise(Object o, boolean forInsert,
150: boolean forUpdate) {
151: java.util.Map m = (java.util.Map) o;
152: if (m != null) {
153: // Check for the case of serialised maps, and assign StateManagers to any PC keys/values without
154: AbstractMemberMetaData fmd = ownerSM.getClassMetaData()
155: .getMetaDataForMember(fieldName);
156: if (SCOUtils.mapHasSerialisedKeysAndValues(fmd)
157: && (fmd.getMap().getKeyClassMetaData() != null || fmd
158: .getMap().getValueClassMetaData() != null)) {
159: ObjectManager om = ownerSM.getObjectManager();
160: Iterator iter = m.entrySet().iterator();
161: while (iter.hasNext()) {
162: Map.Entry entry = (Map.Entry) iter.next();
163: Object key = entry.getKey();
164: Object value = entry.getValue();
165: if (fmd.getMap().getKeyClassMetaData() != null) {
166: StateManager objSM = om.findStateManager(key);
167: if (objSM == null) {
168: objSM = StateManagerFactory
169: .newStateManagerForEmbedded(om,
170: key, false);
171: objSM
172: .addEmbeddedOwner(ownerSM,
173: fieldNumber);
174: }
175: }
176: if (fmd.getMap().getValueClassMetaData() != null) {
177: StateManager objSM = om.findStateManager(value);
178: if (objSM == null) {
179: objSM = StateManagerFactory
180: .newStateManagerForEmbedded(om,
181: value, false);
182: objSM
183: .addEmbeddedOwner(ownerSM,
184: fieldNumber);
185: }
186: }
187: }
188: }
189:
190: if (backingStore != null && useCache && !isCacheLoaded) {
191: // Mark as loaded
192: isCacheLoaded = true;
193: }
194:
195: if (forInsert) {
196: if (JPOXLogger.PERSISTENCE.isDebugEnabled()) {
197: JPOXLogger.PERSISTENCE.debug(LOCALISER.msg(
198: "023007", StringUtils.toJVMIDString(ownerSM
199: .getObject()), fieldName, ""
200: + m.size()));
201: }
202: putAll(m);
203: } else if (forUpdate) {
204: if (JPOXLogger.PERSISTENCE.isDebugEnabled()) {
205: JPOXLogger.PERSISTENCE.debug(LOCALISER.msg(
206: "023008", StringUtils.toJVMIDString(ownerSM
207: .getObject()), fieldName, ""
208: + m.size()));
209: }
210: clear();
211: putAll(m);
212: } else {
213: if (JPOXLogger.PERSISTENCE.isDebugEnabled()) {
214: JPOXLogger.PERSISTENCE.debug(LOCALISER.msg(
215: "023007", StringUtils.toJVMIDString(ownerSM
216: .getObject()), fieldName, ""
217: + m.size()));
218: }
219: delegate.clear();
220: delegate.putAll(m);
221: }
222: }
223: }
224:
225: /**
226: * Method to initialise the SCO for use.
227: */
228: public void initialise() {
229: if (useCache
230: && !SCOUtils.useCachedLazyLoading(ownerSM, fieldName)) {
231: // Load up the container now if not using lazy loading
232: loadFromStore();
233: }
234: }
235:
236: /**
237: * Accessor for the unwrapped value that we are wrapping.
238: * @return The unwrapped value
239: */
240: public Object getValue() {
241: // TODO Cater for delegate not being used
242: return delegate;
243: }
244:
245: /**
246: * Method to effect the load of the data in the SCO.
247: * Used when the SCO supports lazy-loading to tell it to load all now.
248: */
249: public void load() {
250: if (useCache) {
251: loadFromStore();
252: }
253: }
254:
255: /**
256: * Method to load all elements from the "backing store" where appropriate.
257: */
258: protected void loadFromStore() {
259: if (backingStore != null && !isCacheLoaded) {
260: if (JPOXLogger.PERSISTENCE.isDebugEnabled()) {
261: JPOXLogger.PERSISTENCE.debug(LOCALISER.msg("023006",
262: StringUtils.toJVMIDString(ownerSM.getObject()),
263: fieldName));
264: }
265: delegate.clear();
266:
267: // Populate the delegate with the keys/values from the store
268: SCOUtils.populateMapDelegateWithStoreData(delegate,
269: backingStore, ownerSM);
270:
271: isCacheLoaded = true;
272: }
273: }
274:
275: /**
276: * Method to flush the changes to the datastore when operating in queued mode.
277: * Does nothing in "direct" mode.
278: */
279: public void flush() {
280: if (queued) {
281: if (queuedOperations != null) {
282: if (JPOXLogger.PERSISTENCE.isDebugEnabled()) {
283: JPOXLogger.PERSISTENCE.debug(LOCALISER.msg(
284: "023005", StringUtils.toJVMIDString(ownerSM
285: .getObject()), fieldName));
286: }
287: Iterator iter = queuedOperations.iterator();
288: while (iter.hasNext()) {
289: QueuedOperation op = (QueuedOperation) iter.next();
290: op.perform(backingStore, ownerSM);
291: }
292:
293: queuedOperations.clear();
294: queuedOperations = null;
295: }
296: }
297: }
298:
299: /**
300: * Convenience method to add a queued operation to the operations we perform at commit.
301: * @param op The operation
302: */
303: protected void addQueuedOperation(QueuedOperation op) {
304: if (queuedOperations == null) {
305: queuedOperations = new java.util.ArrayList();
306: }
307: queuedOperations.add(op);
308: }
309:
310: /**
311: * Method to update an embedded key in this map.
312: * @param key The key
313: * @param fieldNumber Number of field in the key
314: * @param newValue New value for this field
315: */
316: public void updateEmbeddedKey(Object key, int fieldNumber,
317: Object newValue) {
318: if (backingStore != null) {
319: backingStore.updateEmbeddedKey(ownerSM, key, fieldNumber,
320: newValue);
321: }
322: }
323:
324: /**
325: * Method to update an embedded value in this map.
326: * @param value The value
327: * @param fieldNumber Number of field in the value
328: * @param newValue New value for this field
329: */
330: public void updateEmbeddedValue(Object value, int fieldNumber,
331: Object newValue) {
332: if (backingStore != null) {
333: backingStore.updateEmbeddedValue(ownerSM, value,
334: fieldNumber, newValue);
335: }
336: }
337:
338: /**
339: * Accessor for the field name that this Hashtable relates to.
340: * @return The field name
341: **/
342: public String getFieldName() {
343: return fieldName;
344: }
345:
346: /**
347: * Accessor for the owner that this Hashtable relates to.
348: * @return The owner
349: **/
350: public Object getOwner() {
351: return owner;
352: }
353:
354: /**
355: * Method to unset the owner and field details.
356: **/
357: public synchronized void unsetOwner() {
358: if (ownerSM != null) {
359: owner = null;
360: ownerSM = null;
361: fieldName = null;
362: backingStore = null;
363: }
364: }
365:
366: /**
367: * Utility to mark the object as dirty
368: **/
369: public void makeDirty() {
370: // Although we are never really "dirty", the owning object must be
371: // marked dirty so that the proper state change occurs and its
372: // jdoPreStore() gets called properly.
373: if (ownerSM != null) {
374: ownerSM.getObjectManager().getApiAdapter().makeFieldDirty(
375: owner, fieldName);
376: }
377: }
378:
379: /**
380: * Method to return a detached copy of the container.
381: * Recurse sthrough the keys/values so that they are likewise detached.
382: * @param state State for detachment process
383: * @return The detached container
384: */
385: public Object detachCopy(FetchPlanState state) {
386: java.util.Map detached = new java.util.Hashtable();
387: SCOUtils.detachCopyForMap(ownerSM, entrySet(), state, detached);
388: return detached;
389: }
390:
391: /**
392: * Method to return an attached copy of the passed (detached) value. The returned attached copy
393: * is a SCO wrapper. Goes through the existing keys/values in the store for this owner field and
394: * removes ones no longer present, and adds new keys/values. All keys/values in the (detached)
395: * value are attached.
396: * @param value The new (map) value
397: */
398: public void attachCopy(Object value) {
399: java.util.Map m = (java.util.Map) value;
400:
401: // Attach all of the keys/values in the new map
402: AbstractMemberMetaData fmd = ownerSM.getClassMetaData()
403: .getMetaDataForMember(fieldName);
404: boolean keysWithoutIdentity = SCOUtils
405: .mapHasKeysWithoutIdentity(fmd);
406: boolean valuesWithoutIdentity = SCOUtils
407: .mapHasValuesWithoutIdentity(fmd);
408:
409: java.util.Map attachedKeysValues = new java.util.HashMap(m
410: .size());
411: SCOUtils.attachCopyForMap(ownerSM, m.entrySet(),
412: attachedKeysValues, keysWithoutIdentity,
413: valuesWithoutIdentity);
414:
415: // Update the attached map with the detached elements
416: SCOUtils.updateMapWithMapKeysValues(ownerSM.getObjectManager()
417: .getApiAdapter(), this , attachedKeysValues);
418: }
419:
420: // ------------------------- Queryable Methods -----------------------------
421:
422: /**
423: * Method to generate a QueryStatement for the SCO.
424: * @return The QueryStatement
425: */
426: public synchronized QueryExpression newQueryStatement() {
427: return newQueryStatement(valueType, null);
428: }
429:
430: /**
431: * Method to return a QueryStatement, using the specified candidate class.
432: * @param candidateClass the candidate class
433: * @param candidateAlias Alias for the candidate
434: * @return The QueryStatement
435: */
436: public synchronized QueryExpression newQueryStatement(
437: Class candidateClass, DatastoreIdentifier candidateAlias) {
438: if (backingStore == null) {
439: throw new QueryUnownedSCOException();
440: }
441:
442: return backingStore.newQueryStatement(ownerSM, candidateClass
443: .getName(), candidateAlias);
444: }
445:
446: /**
447: * Method to return a new result object factory for processing of Query
448: * statements.
449: * @param stmt The Query Statement.
450: * @param ignoreCache Whether to ignore the cache.
451: * @param resultClass Whether to create objects of a particular class
452: * @param useFetchPlan whether to use the fetch plan to retrieve fields in the same query
453: * @return The result object factory.
454: **/
455: public synchronized ResultObjectFactory newResultObjectFactory(
456: QueryExpression stmt, boolean ignoreCache,
457: Class resultClass, boolean useFetchPlan) {
458: if (backingStore == null) {
459: throw new QueryUnownedSCOException();
460: }
461:
462: return backingStore.newResultObjectFactory(ownerSM, stmt,
463: ignoreCache, useFetchPlan);
464: }
465:
466: // ------------------ Implementation of Hashtable methods ------------------
467:
468: /**
469: * Creates and returns a copy of this object.
470: *
471: * <P>Mutable second-class Objects are required to provide a public
472: * clone method in order to allow for copying PersistenceCapable
473: * objects. In contrast to Object.clone(), this method must not throw a
474: * CloneNotSupportedException.
475: * @return The cloned object
476: */
477: public Object clone() {
478: if (useCache) {
479: loadFromStore();
480: }
481:
482: return delegate.clone();
483: }
484:
485: /**
486: * Method to return if the map contains this key
487: * @param key The key
488: * @return Whether it is contained
489: **/
490: public boolean containsKey(Object key) {
491: if (useCache && isCacheLoaded) {
492: // If the "delegate" is already loaded, use it
493: return delegate.containsKey(key);
494: } else if (backingStore != null) {
495: return backingStore.containsKey(ownerSM, key);
496: }
497:
498: return delegate.containsKey(key);
499: }
500:
501: /**
502: * Method to return if the map contains this value.
503: * @param value The value
504: * @return Whether it is contained
505: **/
506: public boolean containsValue(Object value) {
507: if (useCache && isCacheLoaded) {
508: // If the "delegate" is already loaded, use it
509: return delegate.containsValue(value);
510: } else if (backingStore != null) {
511: return backingStore.containsValue(ownerSM, value);
512: }
513:
514: return delegate.containsValue(value);
515: }
516:
517: /**
518: * Accessor for the set of entries in the Map.
519: * @return Set of entries
520: **/
521: public java.util.Set entrySet() {
522: if (useCache) {
523: loadFromStore();
524: } else if (backingStore != null) {
525: return new Set(ownerSM, fieldName, false, backingStore
526: .entrySetStore());
527: }
528:
529: return delegate.entrySet();
530: }
531:
532: /**
533: * Method to check the equality of this map, and another.
534: * @param o The map to compare against.
535: * @return Whether they are equal.
536: **/
537: public synchronized boolean equals(Object o) {
538: if (useCache) {
539: loadFromStore();
540: }
541:
542: if (o == this ) {
543: return true;
544: }
545: if (!(o instanceof java.util.Map)) {
546: return false;
547: }
548: java.util.Map m = (java.util.Map) o;
549:
550: return entrySet().equals(m.entrySet());
551: }
552:
553: /**
554: * Accessor for the value stored against a key.
555: * @param key The key
556: * @return The value.
557: **/
558: public Object get(Object key) {
559: if (useCache) {
560: loadFromStore();
561: } else if (backingStore != null) {
562: return backingStore.get(ownerSM, key);
563: }
564:
565: return delegate.get(key);
566: }
567:
568: /**
569: * Method to generate a hashcode for this Map.
570: * @return The hashcode.
571: **/
572: public synchronized int hashCode() {
573: if (useCache) {
574: loadFromStore();
575: } else if (backingStore != null) {
576: int h = 0;
577: Iterator i = entrySet().iterator();
578: while (i.hasNext()) {
579: h += i.next().hashCode();
580: }
581:
582: return h;
583: }
584: return delegate.hashCode();
585: }
586:
587: /**
588: * Method to return if the Map is empty.
589: * @return Whether it is empty.
590: **/
591: public boolean isEmpty() {
592: return size() == 0;
593: }
594:
595: /**
596: * Accessor for the set of keys in the Map.
597: * @return Set of keys.
598: **/
599: public java.util.Set keySet() {
600: if (useCache) {
601: loadFromStore();
602: }
603: if (backingStore != null) {
604: return new Set(ownerSM, fieldName, false, backingStore
605: .keySetStore(ownerSM.getObjectManager()
606: .getClassLoaderResolver()));
607: }
608:
609: return delegate.keySet();
610: }
611:
612: /**
613: * Method to return the size of the Map.
614: * @return The size
615: **/
616: public int size() {
617: if (useCache && isCacheLoaded) {
618: // If the "delegate" is already loaded, use it
619: return delegate.size();
620: } else if (backingStore != null) {
621: return backingStore.entrySetStore().size(ownerSM);
622: }
623:
624: return delegate.size();
625: }
626:
627: /**
628: * Accessor for the set of values in the Map.
629: * @return Set of values.
630: **/
631: public Collection values() {
632: if (useCache) {
633: loadFromStore();
634: }
635: if (backingStore != null) {
636: return new Set(ownerSM, fieldName, true, backingStore
637: .valueSetStore(ownerSM.getObjectManager()
638: .getClassLoaderResolver()));
639: }
640:
641: return delegate.values();
642: }
643:
644: // -------------------------------- Mutator methods ------------------------
645:
646: /**
647: * Method to clear the Hashtable
648: **/
649: public synchronized void clear() {
650: makeDirty();
651:
652: if (backingStore != null) {
653: if (queued && !ownerSM.getObjectManager().isFlushing()) {
654: addQueuedOperation(new ClearOperation());
655: } else {
656: backingStore.clear(ownerSM);
657: }
658: }
659: delegate.clear();
660: }
661:
662: /**
663: * Method to add a value against a key to the Hashtable
664: * @param key The key
665: * @param value The value
666: * @return The previous value for the specified key.
667: **/
668: public Object put(Object key, Object value) {
669: // Reject inappropriate elements
670: if (value == null && !allowNulls) {
671: throw new NullsNotAllowedException(ownerSM, fieldName);
672: }
673:
674: makeDirty();
675:
676: // Dont load the entries since we dont need them here
677:
678: Object oldValue = null;
679: if (backingStore != null) {
680: if (queued && !ownerSM.getObjectManager().isFlushing()) {
681: addQueuedOperation(new PutOperation(key, value));
682: } else {
683: oldValue = backingStore.put(ownerSM, key, value);
684: }
685: }
686: Object delegateOldValue = delegate.put(key, value);
687: if (backingStore == null) {
688: oldValue = delegateOldValue;
689: } else if (queued && !ownerSM.getObjectManager().isFlushing()) {
690: oldValue = delegateOldValue;
691: }
692: return oldValue;
693: }
694:
695: /**
696: * Method to add the specified Map's values under their keys here.
697: * @param m The map
698: **/
699: public void putAll(java.util.Map m) {
700: makeDirty();
701:
702: // Dont load the entries since we dont need them here
703:
704: if (backingStore != null) {
705: if (queued && !ownerSM.getObjectManager().isFlushing()) {
706: Iterator iter = m.entrySet().iterator();
707: while (iter.hasNext()) {
708: Map.Entry entry = (Map.Entry) iter.next();
709: addQueuedOperation(new PutOperation(entry.getKey(),
710: entry.getValue()));
711: }
712: } else {
713: backingStore.putAll(ownerSM, m);
714: }
715: }
716: delegate.putAll(m);
717: }
718:
719: /**
720: * Method to remove the value for a key from the Hashtable
721: * @param key The key to remove
722: * @return The value that was removed from this key.
723: **/
724: public Object remove(Object key) {
725: makeDirty();
726:
727: // Dont load the entries since we dont need them here
728:
729: Object removed = null;
730: Object delegateRemoved = delegate.remove(key);
731: if (backingStore != null) {
732: if (queued && !ownerSM.getObjectManager().isFlushing()) {
733: addQueuedOperation(new RemoveOperation(key));
734: removed = delegateRemoved;
735: } else {
736: removed = backingStore.remove(ownerSM, key);
737: }
738: } else {
739: removed = delegateRemoved;
740: }
741: return removed;
742: }
743:
744: /**
745: * The writeReplace method is called when ObjectOutputStream is preparing
746: * to write the object to the stream. The ObjectOutputStream checks whether
747: * the class defines the writeReplace method. If the method is defined, the
748: * writeReplace method is called to allow the object to designate its
749: * replacement in the stream. The object returned should be either of the
750: * same type as the object passed in or an object that when read and
751: * resolved will result in an object of a type that is compatible with all
752: * references to the object.
753: *
754: * @return the replaced object
755: * @throws ObjectStreamException
756: */
757: protected Object writeReplace() throws ObjectStreamException {
758: if (useCache) {
759: loadFromStore();
760: return new java.util.Hashtable(delegate);
761: } else {
762: // TODO Cater for non-cached map, load elements in a DB call.
763: return new java.util.Hashtable(delegate);
764: }
765: }
766: }
|