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