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: ...
018: **********************************************************************/package org.jpox.cache;
019:
020: import java.lang.ref.Reference;
021: import java.util.Collection;
022: import java.util.HashMap;
023: import java.util.HashSet;
024: import java.util.Iterator;
025: import java.util.Map;
026: import java.util.Properties;
027:
028: import org.jpox.ObjectManagerHelper;
029: import org.jpox.util.JPOXLogger;
030: import org.jpox.util.Localiser;
031: import org.jpox.util.WeakValueMap;
032:
033: /**
034: * Default implementation of a Level 2 cache for JPOX.
035: * <p>
036: * Operates with 2 maps internally. One stores all pinned objects that have been
037: * selected to be retained by user's application. The other stores all other objects.
038: * This second map is the default location where objects are placed when being added here.
039: * The second (unpinned) map stores weak references meaning that they can get garbage
040: * collected as necessary by the JVM.
041: * </P>
042: * <P>
043: * Maintains collections of the classes and the identities that are to be pinned if they ever
044: * are put into the cache. These are defined by the pinAll(), pin() methods.
045: * </P>
046: * <P>
047: * All mutating methods, and the get method have been synchronized to prevent conflicts.
048: * </P>
049: *
050: * @version $Revision: 1.18 $
051: */
052: public class DefaultLevel2Cache implements Level2Cache {
053: /** Localiser for messages */
054: private static Localiser LOCALISER = Localiser
055: .getInstance("org.jpox.Localisation");
056:
057: /** Collection of pinned classes whose objects should be pinned if they ever reach the cache. */
058: protected Collection pinnedClasses;
059:
060: /** Collection of ids whose objects should be pinned if they ever reach the cache. */
061: protected Collection pinnedIds;
062:
063: /** Pinned objects cache. */
064: protected Map pinnedCache;
065:
066: /** Unpinned objects cache. */
067: protected Map unpinnedCache;
068:
069: /**
070: * Constructor.
071: */
072: protected DefaultLevel2Cache() {
073: // nothing to do
074: }
075:
076: /**
077: * Constructor.
078: * @param props Any properties to control the cache
079: */
080: public DefaultLevel2Cache(Properties props) {
081: // Do nothing with the properties
082: pinnedCache = new HashMap();
083: unpinnedCache = new WeakValueMap();
084: }
085:
086: /**
087: * Method to evict an object from the cache.
088: * @param oid The id of the object to evict
089: */
090: public synchronized void evict(Object oid) {
091: if (oid == null) {
092: return;
093: }
094:
095: unpinnedCache.remove(oid);
096: pinnedCache.remove(oid);
097: }
098:
099: /**
100: * Method to evict all objects from the L2 cache.
101: */
102: public synchronized void evictAll() {
103: unpinnedCache.clear();
104: pinnedCache.clear();
105: }
106:
107: /**
108: * Method to evict all objects of the given types from the cache.
109: * @param pcClass The class to evict
110: * @param subclasses Whether to also evict subclasses
111: */
112: public synchronized void evictAll(Class pcClass, boolean subclasses) {
113: if (pcClass == null) {
114: return;
115: }
116:
117: Collection oidsToEvict = new HashSet();
118:
119: // Find objects to evict from pinned
120: Collection pinnedObjects = pinnedCache.entrySet();
121: Iterator pinnedIter = pinnedObjects.iterator();
122: while (pinnedIter.hasNext()) {
123: Map.Entry entry = (Map.Entry) pinnedIter.next();
124: CachedPC pc = (CachedPC) entry.getValue();
125: if (pcClass.getName().equals(pc.getPCClass().getName())
126: || (subclasses && pcClass.isAssignableFrom(pc
127: .getPCClass()))) {
128: oidsToEvict.add(entry.getKey());
129: }
130: }
131:
132: // Find objects to evict from unpinned
133: Collection unpinnedObjects = unpinnedCache.entrySet();
134: Iterator unpinnedIter = unpinnedObjects.iterator();
135: while (unpinnedIter.hasNext()) {
136: Map.Entry entry = (Map.Entry) unpinnedIter.next();
137: CachedPC pc = (CachedPC) entry.getValue();
138: if (pc != null
139: && pcClass.getName().equals(
140: pc.getPCClass().getName())
141: || (subclasses && pcClass.isAssignableFrom(pc
142: .getPCClass()))) {
143: oidsToEvict.add(entry.getKey());
144: }
145: }
146:
147: // Evict the objects
148: if (!oidsToEvict.isEmpty()) {
149: evictAll(oidsToEvict);
150: }
151: }
152:
153: /**
154: * Method to evict the objects with the specified ids.
155: * @param oids The ids of the objects to evict
156: */
157: public synchronized void evictAll(Collection oids) {
158: if (oids == null) {
159: return;
160: }
161:
162: Iterator iter = oids.iterator();
163: while (iter.hasNext()) {
164: evict(iter.next());
165: }
166: }
167:
168: /**
169: * Method to evict the objects with the specified ids.
170: * @param oids The ids of the objects to evict
171: */
172: public synchronized void evictAll(Object[] oids) {
173: if (oids == null) {
174: return;
175: }
176:
177: for (int i = 0; i < oids.length; i++) {
178: evict(oids[i]);
179: }
180: }
181:
182: /**
183: * Method to pin an object to the cache.
184: * @param oid The id of the object to pin
185: */
186: public synchronized void pin(Object oid) {
187: if (oid == null) {
188: return;
189: }
190:
191: if (pinnedIds == null) {
192: pinnedIds = new HashSet();
193: } else if (!pinnedIds.contains(oid)) {
194: // Add this oid to the to-be-pinned collection
195: pinnedIds.add(oid);
196: }
197:
198: Object pc = unpinnedCache.get(oid);
199: if (pc != null) {
200: pinnedCache.put(oid, pc);
201: unpinnedCache.remove(oid);
202: }
203: }
204:
205: /**
206: * Method to pin all objects of the given types.
207: * @param cls The class
208: * @param subs Whether to include subclasses
209: */
210: public synchronized void pinAll(Class cls, boolean subs) {
211: if (cls == null) {
212: return;
213: }
214:
215: if (pinnedClasses == null) {
216: pinnedClasses = new HashSet();
217: }
218:
219: // Check if it already exists as a pinned class
220: PinnedClass pinnedCls = new PinnedClass(cls, subs);
221: if (pinnedClasses.contains(pinnedCls)) {
222: return;
223: }
224: pinnedClasses.add(pinnedCls);
225:
226: // Update all currently unpinned objects to comply with the new class specification
227: Collection unpinnedObjects = unpinnedCache.values();
228: Iterator unpinnedIter = unpinnedObjects.iterator();
229: while (unpinnedIter.hasNext()) {
230: CachedPC obj = (CachedPC) unpinnedIter.next();
231: if ((subs && cls.isInstance(obj.getPCClass()))
232: || cls.getName().equals(obj.getPCClass().getName())) {
233: pin(obj);
234: }
235: }
236: }
237:
238: /**
239: * Method to pin all of the supplied objects
240: * @param oids The Object ids to pin
241: */
242: public synchronized void pinAll(Collection oids) {
243: if (oids == null) {
244: return;
245: }
246:
247: Iterator iter = oids.iterator();
248: while (iter.hasNext()) {
249: pin(iter.next());
250: }
251: }
252:
253: /**
254: * Method to pin all of the supplied objects
255: * @param oids The object ids to pin
256: */
257: public synchronized void pinAll(Object[] oids) {
258: if (oids == null) {
259: return;
260: }
261:
262: for (int i = 0; i < oids.length; i++) {
263: pin(oids[i]);
264: }
265: }
266:
267: /**
268: * Method to unpin an object
269: * @param oid The object id
270: */
271: public synchronized void unpin(Object oid) {
272: if (oid == null) {
273: return;
274: }
275:
276: Object pc = pinnedCache.get(oid);
277: if (pc != null) {
278: unpinnedCache.put(oid, pc);
279: pinnedCache.remove(oid);
280: }
281:
282: if (pinnedIds != null && pinnedIds.contains(oid)) {
283: // Remove this oid from the to-be-pinned collection
284: pinnedIds.remove(oid);
285: }
286: }
287:
288: /**
289: * Method to unpin all objects of the specified types.
290: * @param cls Base class
291: * @param subs Whether to include subclasses
292: */
293: public synchronized void unpinAll(Class cls, boolean subs) {
294: if (cls == null) {
295: return;
296: }
297:
298: // Remove the class from the pinned collection
299: if (pinnedClasses != null) {
300: PinnedClass pinnedCls = new PinnedClass(cls, subs);
301: pinnedClasses.remove(pinnedCls);
302: }
303:
304: // Unpin all objects of this type currently pinned
305: Collection pinnedObjects = pinnedCache.values();
306: Iterator pinnedIter = pinnedObjects.iterator();
307: while (pinnedIter.hasNext()) {
308: CachedPC obj = (CachedPC) pinnedIter.next();
309: if ((subs && cls.isInstance(obj.getPCClass()))
310: || cls.getName().equals(obj.getPCClass().getName())) {
311: unpin(obj);
312: }
313: }
314: }
315:
316: /**
317: * Method to unpin all of the supplied objects
318: * @param oids The object ids to unpin
319: */
320: public synchronized void unpinAll(Collection oids) {
321: if (oids == null) {
322: return;
323: }
324:
325: Iterator iter = oids.iterator();
326: while (iter.hasNext()) {
327: unpin(iter.next());
328: }
329: }
330:
331: /**
332: * Method to unpin all of the specified objects
333: * @param oids The object ids to unpin
334: */
335: public synchronized void unpinAll(Object[] oids) {
336: if (oids == null) {
337: return;
338: }
339:
340: for (int i = 0; i < oids.length; i++) {
341: unpin(oids[i]);
342: }
343: }
344:
345: /**
346: * Method to clear the cache.
347: */
348: public void clear() {
349: pinnedCache.clear();
350: unpinnedCache.clear();
351: }
352:
353: /**
354: * Accessor for an object from the cache.
355: * The returned object will not have a StateManager connected. This is
356: * because data stored in the Level 2 cache is StateManager and
357: * PersistenceManager independent.
358: * @param oid The Object ID
359: * @return The L2 cacheable object
360: */
361: public synchronized CachedPC get(Object oid) {
362: if (oid == null) {
363: return null;
364: }
365:
366: CachedPC pc = (CachedPC) pinnedCache.get(oid);
367: if (pc != null) {
368: return pc;
369: }
370: pc = (CachedPC) unpinnedCache.get(oid);
371: return pc;
372: }
373:
374: /**
375: * Accessor for the number of pinned objects in the cache.
376: * @return Number of pinned objects
377: */
378: public int getNumberOfPinnedObjects() {
379: return pinnedCache.size();
380: }
381:
382: /**
383: * Accessor for the number of unpinned objects in the cache.
384: * @return Number of unpinned objects
385: */
386: public int getNumberOfUnpinnedObjects() {
387: return unpinnedCache.size();
388: }
389:
390: /**
391: * Accessor for the total number of objects in the L2 cache.
392: * @return Number of objects
393: */
394: public int getSize() {
395: return getNumberOfPinnedObjects()
396: + getNumberOfUnpinnedObjects();
397: }
398:
399: /**
400: * Method to put an object in the cache. Note that the pc object being
401: * passed in must NOT have a StateManager connected. Data stored in
402: * the Level 2 cache has to be independent of PersistenceManager and
403: * StateManager.
404: * @param oid The Object id for this object
405: * @param pc The cacheable object
406: * @return The value previously associated with this oid
407: */
408: public synchronized CachedPC put(Object oid, CachedPC pc) {
409: if (oid == null || pc == null) {
410: JPOXLogger.CACHE.warn(LOCALISER.msg("004011"));
411: return null;
412: }
413:
414: // Check if the object is disconnected from its StateManager/PM
415: if (ObjectManagerHelper.getObjectManager(pc
416: .getPersistableObject()) != null) {
417: JPOXLogger.CACHE.error(LOCALISER.msg("004012", oid));
418: return null;
419: }
420:
421: // Check if we should pin this
422: // a). check if the object class type is to be pinned
423: boolean toBePinned = false;
424: if (pinnedClasses != null) {
425: Iterator pinnedClsIter = pinnedClasses.iterator();
426: while (pinnedClsIter.hasNext()) {
427: PinnedClass pinCls = (PinnedClass) pinnedClsIter.next();
428: if (pinCls.cls.getName().equals(
429: pc.getPCClass().getName())
430: || (pinCls.subclasses && pinCls.cls
431: .isAssignableFrom(pc.getPCClass()))) {
432: toBePinned = true;
433: break;
434: }
435: }
436: }
437:
438: // b). check if the id is to be pinned
439: if (pinnedIds != null && pinnedIds.contains(oid)) {
440: toBePinned = true;
441: }
442:
443: Object obj = null;
444: if (pinnedCache.get(oid) != null) {
445: // Update the pinned cache if object is already there
446: obj = pinnedCache.put(oid, pc);
447: if (obj != null) {
448: return (CachedPC) obj;
449: }
450: } else {
451: if (toBePinned) {
452: // Update the pinned cache
453: pinnedCache.put(oid, pc);
454: unpinnedCache.remove(oid); // Just in case it was unpinned previously
455: } else {
456: // Update the unpinned cache otherwise
457: obj = unpinnedCache.put(oid, pc);
458: if (obj != null) {
459: // Values in a WeakRefCache are of type Reference
460: Reference ref = (Reference) obj;
461: return (CachedPC) ref.get();
462: }
463: }
464: }
465:
466: return null;
467: }
468:
469: /**
470: * Method to check if an object with the specified id is in the cache
471: * @param oid The object ID
472: * @return Whether it is present
473: */
474: public boolean containsOid(Object oid) {
475: return (pinnedCache.containsKey(oid) || unpinnedCache
476: .containsKey(oid));
477: }
478:
479: /**
480: * Accessor for whether the cache is empty.
481: * @return Whether it is empty.
482: */
483: public boolean isEmpty() {
484: return (pinnedCache.isEmpty() && unpinnedCache.isEmpty());
485: }
486: }
|