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