001: package org.apache.torque.manager;
002:
003: /*
004: * Licensed to the Apache Software Foundation (ASF) under one
005: * or more contributor license agreements. See the NOTICE file
006: * distributed with this work for additional information
007: * regarding copyright ownership. The ASF licenses this file
008: * to you under the Apache License, Version 2.0 (the
009: * "License"); you may not use this file except in compliance
010: * with the License. You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing,
015: * software distributed under the License is distributed on an
016: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017: * KIND, either express or implied. See the License for the
018: * specific language governing permissions and limitations
019: * under the License.
020: */
021:
022: import java.lang.ref.WeakReference;
023: import java.util.Arrays;
024: import java.util.List;
025: import java.util.ArrayList;
026: import java.util.Map;
027: import java.util.HashMap;
028: import java.util.Iterator;
029: import java.io.Serializable;
030: import java.io.IOException;
031: import java.io.ObjectInputStream;
032:
033: import org.apache.commons.collections.FastArrayList;
034: import org.apache.jcs.JCS;
035: import org.apache.jcs.access.GroupCacheAccess;
036: import org.apache.jcs.access.exception.CacheException;
037:
038: import org.apache.torque.Torque;
039: import org.apache.torque.TorqueException;
040: import org.apache.torque.om.ObjectKey;
041: import org.apache.torque.om.Persistent;
042:
043: import org.apache.commons.logging.Log;
044: import org.apache.commons.logging.LogFactory;
045:
046: /**
047: * This class contains common functionality of a Manager for
048: * instantiating OM's.
049: *
050: * @author <a href="mailto:jmcnally@collab.net">John McNally</a>
051: * @version $Id: AbstractBaseManager.java 571790 2007-09-01 12:40:44Z tv $
052: */
053: public abstract class AbstractBaseManager implements Serializable {
054: /** the log */
055: protected static final Log log = LogFactory
056: .getLog(AbstractBaseManager.class);
057:
058: /** used to cache the om objects. cache is set by the region property */
059: protected transient GroupCacheAccess cache;
060:
061: /** method results cache */
062: protected MethodResultCache mrCache;
063:
064: /** the class that the service will instantiate */
065: private Class omClass;
066:
067: private String className;
068:
069: private String region;
070:
071: private boolean isNew = true;
072:
073: protected Map validFields;
074: protected Map listenersMap = new HashMap();
075:
076: /**
077: * Get the Class instance
078: *
079: * @return the om class
080: */
081: protected Class getOMClass() {
082: return omClass;
083: }
084:
085: /**
086: * Set the Class that will be instantiated by this manager
087: *
088: * @param omClass the om class
089: */
090: protected void setOMClass(Class omClass) {
091: this .omClass = omClass;
092: }
093:
094: /**
095: * Get a fresh instance of an om
096: *
097: * @return an instance of the om class
098: * @throws InstantiationException
099: * @throws IllegalAccessException
100: */
101: protected Persistent getOMInstance() throws InstantiationException,
102: IllegalAccessException {
103: return (Persistent) omClass.newInstance();
104: }
105:
106: /**
107: * Get the classname to instantiate for getInstance()
108: * @return value of className.
109: */
110: public String getClassName() {
111: return className;
112: }
113:
114: /**
115: * Set the classname to instantiate for getInstance()
116: * @param v Value to assign to className.
117: * @throws TorqueException Any exceptions caught during processing will be
118: * rethrown wrapped into a TorqueException.
119: */
120: public void setClassName(String v) throws TorqueException {
121: this .className = v;
122:
123: try {
124: setOMClass(Class.forName(getClassName()));
125: } catch (ClassNotFoundException cnfe) {
126: throw new TorqueException("Could not load "
127: + getClassName());
128: }
129: }
130:
131: /**
132: * Return an instance of an om based on the id
133: *
134: * @param id the primary key of the object
135: * @return the object from persistent storage or from cache
136: * @throws TorqueException Any exceptions caught during processing will be
137: * rethrown wrapped into a TorqueException.
138: */
139: protected Persistent getOMInstance(ObjectKey id)
140: throws TorqueException {
141: return getOMInstance(id, true);
142: }
143:
144: /**
145: * Return an instance of an om based on the id
146: *
147: * @param key the primary key of the object
148: * @param fromCache true if the object should be retrieved from cache
149: * @return the object from persistent storage or from cache
150: * @throws TorqueException Any exceptions caught during processing will be
151: * rethrown wrapped into a TorqueException.
152: */
153: protected Persistent getOMInstance(ObjectKey key, boolean fromCache)
154: throws TorqueException {
155: Persistent om = null;
156: if (fromCache) {
157: om = cacheGet(key);
158: }
159:
160: if (om == null) {
161: om = retrieveStoredOM(key);
162: if (fromCache) {
163: putInstanceImpl(om);
164: }
165: }
166:
167: return om;
168: }
169:
170: /**
171: * Get an object from cache
172: *
173: * @param key the primary key of the object
174: * @return the object from cache
175: */
176: protected Persistent cacheGet(Serializable key) {
177: Persistent om = null;
178: if (cache != null) {
179: synchronized (this ) {
180: om = (Persistent) cache.get(key);
181: }
182: }
183: return om;
184: }
185:
186: /**
187: * Clears the cache
188: *
189: * @throws TorqueException Any exceptions caught during processing will be
190: * rethrown wrapped into a TorqueException.
191: */
192: protected void clearImpl() throws TorqueException {
193: if (cache != null) {
194: try {
195: cache.clear();
196: } catch (CacheException ce) {
197: throw new TorqueException(
198: "Could not clear cache due to internal JCS error.",
199: ce);
200: }
201: }
202: }
203:
204: /**
205: * Disposes of the cache. This triggers a shutdown of the connected cache
206: * instances. This method should only be used during shutdown of Torque. The
207: * manager instance will not cache anymore after this call.
208: */
209: public void dispose() {
210: if (cache != null) {
211: cache.dispose();
212: cache = null;
213: }
214: }
215:
216: /**
217: * Remove an object from the cache
218: *
219: * @param key the cache key for the object
220: * @return the object one last time
221: * @throws TorqueException Any exceptions caught during processing will be
222: * rethrown wrapped into a TorqueException.
223: */
224: protected Persistent removeInstanceImpl(Serializable key)
225: throws TorqueException {
226: Persistent oldOm = null;
227: if (cache != null) {
228: try {
229: synchronized (this ) {
230: oldOm = (Persistent) cache.get(key);
231: cache.remove(key);
232: }
233: } catch (CacheException ce) {
234: throw new TorqueException(
235: "Could not remove from cache due to internal JCS error",
236: ce);
237: }
238: }
239: return oldOm;
240: }
241:
242: /**
243: * Put an object into the cache
244: *
245: * @param om the object
246: * @return if an object with the same key already is in the cache
247: * this object will be returned, else null
248: * @throws TorqueException Any exceptions caught during processing will be
249: * rethrown wrapped into a TorqueException.
250: */
251: protected Persistent putInstanceImpl(Persistent om)
252: throws TorqueException {
253: ObjectKey key = om.getPrimaryKey();
254: return putInstanceImpl(key, om);
255: }
256:
257: /**
258: * Put an object into the cache
259: *
260: * @param key the cache key for the object
261: * @param om the object
262: * @return if an object with this key already is in the cache
263: * this object will be returned, else null
264: * @throws TorqueException Any exceptions caught during processing will be
265: * rethrown wrapped into a TorqueException.
266: */
267: protected Persistent putInstanceImpl(Serializable key, Persistent om)
268: throws TorqueException {
269: if (getOMClass() != null && !getOMClass().isInstance(om)) {
270: throw new TorqueException(om + "; class="
271: + om.getClass().getName() + "; id="
272: + om.getPrimaryKey() + " cannot be cached with "
273: + getOMClass().getName() + " objects");
274: }
275:
276: Persistent oldOm = null;
277: if (cache != null) {
278: try {
279: synchronized (this ) {
280: oldOm = (Persistent) cache.get(key);
281: cache.put(key, om);
282: }
283: } catch (CacheException ce) {
284: throw new TorqueException(
285: "Could not cache due to internal JCS error", ce);
286: }
287: }
288: return oldOm;
289: }
290:
291: /**
292: * Retrieve an object from persistent storage
293: *
294: * @param id the primary key of the object
295: * @return the object
296: * @throws TorqueException Any exceptions caught during processing will be
297: * rethrown wrapped into a TorqueException.
298: */
299: protected abstract Persistent retrieveStoredOM(ObjectKey id)
300: throws TorqueException;
301:
302: /**
303: * Gets a list of om's based on id's.
304: *
305: * @param ids a <code>ObjectKey[]</code> value
306: * @return a <code>List</code> value
307: * @throws TorqueException Any exceptions caught during processing will be
308: * rethrown wrapped into a TorqueException.
309: */
310: protected List getOMs(ObjectKey[] ids) throws TorqueException {
311: return getOMs(Arrays.asList(ids));
312: }
313:
314: /**
315: * Gets a list of om's based on id's.
316: *
317: * @param ids a <code>List</code> of <code>ObjectKey</code>'s
318: * @return a <code>List</code> value
319: * @throws TorqueException Any exceptions caught during processing will be
320: * rethrown wrapped into a TorqueException.
321: */
322: protected List getOMs(List ids) throws TorqueException {
323: return getOMs(ids, true);
324: }
325:
326: /**
327: * Gets a list of om's based on id's.
328: *
329: * @param ids a <code>List</code> of <code>ObjectKey</code>'s
330: * @return a <code>List</code> value
331: * @throws TorqueException Any exceptions caught during processing will be
332: * rethrown wrapped into a TorqueException.
333: */
334: protected List getOMs(List ids, boolean fromCache)
335: throws TorqueException {
336: List oms = null;
337: if (ids != null && ids.size() > 0) {
338: // start a new list where we will replace the id's with om's
339: oms = new ArrayList(ids);
340: List newIds = new ArrayList(ids.size());
341: for (int i = 0; i < ids.size(); i++) {
342: ObjectKey key = (ObjectKey) ids.get(i);
343: Persistent om = null;
344: if (fromCache) {
345: om = cacheGet(key);
346: }
347: if (om == null) {
348: newIds.add(key);
349: } else {
350: oms.set(i, om);
351: }
352: }
353:
354: if (newIds.size() > 0) {
355: List newOms = retrieveStoredOMs(newIds);
356: for (int i = 0; i < oms.size(); i++) {
357: if (oms.get(i) instanceof ObjectKey) {
358: for (int j = newOms.size() - 1; j >= 0; j--) {
359: Persistent om = (Persistent) newOms.get(j);
360: if (om.getPrimaryKey().equals(oms.get(i))) {
361: // replace the id with the om and add the om
362: // to the cache
363: oms.set(i, om);
364: newOms.remove(j);
365: if (fromCache) {
366: putInstanceImpl(om);
367: }
368: break;
369: }
370: }
371: }
372: }
373: }
374: }
375: return oms;
376: }
377:
378: /**
379: * Gets a list of om's based on id's.
380: * This method must be implemented in the drived class
381: *
382: * @param ids a <code>List</code> of <code>ObjectKey</code>'s
383: * @return a <code>List</code> value
384: * @throws TorqueException Any exceptions caught during processing will be
385: * rethrown wrapped into a TorqueException.
386: */
387: protected abstract List retrieveStoredOMs(List ids)
388: throws TorqueException;
389:
390: /**
391: * Get the value of region.
392: *
393: * @return value of region.
394: */
395: public String getRegion() {
396: return region;
397: }
398:
399: /**
400: * Set the value of region.
401: *
402: * @param v Value to assign to region.
403: * @throws TorqueException Any exceptions caught during processing will be
404: * rethrown wrapped into a TorqueException.
405: */
406: public void setRegion(String v) throws TorqueException {
407: this .region = v;
408: try {
409: if (Torque.getConfiguration().getBoolean(Torque.CACHE_KEY,
410: false)) {
411: cache = JCS.getInstance(getRegion());
412: mrCache = new MethodResultCache(cache);
413: } else {
414: mrCache = new NoOpMethodResultCache(cache);
415: }
416: } catch (CacheException e) {
417: throw new TorqueException("Cache could not be initialized",
418: e);
419: }
420:
421: if (cache == null) {
422: log.info("Cache could not be initialized for region: " + v);
423: }
424: }
425:
426: /**
427: * @return The cache instance.
428: */
429: public MethodResultCache getMethodResultCache() {
430: if (isNew) {
431: synchronized (this ) {
432: if (isNew) {
433: registerAsListener();
434: isNew = false;
435: }
436: }
437: }
438: return mrCache;
439: }
440:
441: /**
442: * NoOp version. Managers should override this method to notify other
443: * managers that they are interested in CacheEvents.
444: */
445: protected void registerAsListener() {
446: }
447:
448: /**
449: *
450: * @param listener A new listener for cache events.
451: */
452: public void addCacheListenerImpl(CacheListener listener) {
453: List keys = listener.getInterestedFields();
454: Iterator i = keys.iterator();
455: while (i.hasNext()) {
456: String key = (String) i.next();
457: // Peer.column names are the fields
458: if (validFields != null && validFields.containsKey(key)) {
459: List listeners = (List) listenersMap.get(key);
460: if (listeners == null) {
461: listeners = createSubsetList(key);
462: }
463:
464: boolean isNew = true;
465: Iterator j = listeners.iterator();
466: while (j.hasNext()) {
467: Object listener2 = ((WeakReference) j.next()).get();
468: if (listener2 == null) {
469: // do a little cleanup while checking for dupes
470: // not thread-safe, not likely to be many nulls
471: // but should revisit
472: //j.remove();
473: } else if (listener2 == listener) {
474: isNew = false;
475: break;
476: }
477: }
478: if (isNew) {
479: listeners.add(new WeakReference(listener));
480: }
481: }
482: }
483: }
484:
485: /**
486: *
487: * @param key
488: * @return A subset of the list identified by <code>key</code>.
489: */
490: private synchronized List createSubsetList(String key) {
491: FastArrayList list = null;
492: if (listenersMap.containsKey(key)) {
493: list = (FastArrayList) listenersMap.get(key);
494: } else {
495: list = new FastArrayList();
496: list.setFast(true);
497: listenersMap.put(key, list);
498: }
499: return list;
500: }
501:
502: /**
503: *
504: * @param listeners
505: * @param oldOm
506: * @param om
507: */
508: protected void notifyListeners(List listeners, Persistent oldOm,
509: Persistent om) {
510: if (listeners != null) {
511: synchronized (listeners) {
512: Iterator i = listeners.iterator();
513: while (i.hasNext()) {
514: CacheListener listener = (CacheListener) ((WeakReference) i
515: .next()).get();
516: if (listener == null) {
517: // remove reference as its object was cleared
518: i.remove();
519: } else {
520: if (oldOm == null) {
521: // object was added
522: listener.addedObject(om);
523: } else {
524: // object was refreshed
525: listener.refreshedObject(om);
526: }
527: }
528: }
529: }
530: }
531: }
532:
533: /**
534: * helper methods for the Serializable interface
535: *
536: * @param out
537: * @throws IOException
538: */
539: private void writeObject(java.io.ObjectOutputStream out)
540: throws IOException {
541: out.defaultWriteObject();
542: }
543:
544: /**
545: * Helper methods for the <code>Serializable</code> interface.
546: *
547: * @param in The stream to read a <code>Serializable</code> from.
548: * @throws IOException
549: * @throws ClassNotFoundException
550: */
551: private void readObject(ObjectInputStream in) throws IOException,
552: ClassNotFoundException {
553: in.defaultReadObject();
554: // initialize the cache
555: try {
556: if (region != null) {
557: setRegion(region);
558: }
559: } catch (Exception e) {
560: log.error("Cache could not be initialized for region '"
561: + region + "' after deserialization");
562: }
563: }
564: }
|