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