View Javadoc

1   package org.apache.torque.manager;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.lang.ref.WeakReference;
23  import java.util.Arrays;
24  import java.util.List;
25  import java.util.ArrayList;
26  import java.util.Map;
27  import java.util.HashMap;
28  import java.util.Iterator;
29  import java.io.Serializable;
30  import java.io.IOException;
31  import java.io.ObjectInputStream;
32  
33  import org.apache.commons.collections.FastArrayList;
34  import org.apache.jcs.JCS;
35  import org.apache.jcs.access.GroupCacheAccess;
36  import org.apache.jcs.access.exception.CacheException;
37  
38  import org.apache.torque.Torque;
39  import org.apache.torque.TorqueException;
40  import org.apache.torque.om.ObjectKey;
41  import org.apache.torque.om.Persistent;
42  
43  import org.apache.commons.logging.Log;
44  import org.apache.commons.logging.LogFactory;
45  
46  /***
47   * This class contains common functionality of a Manager for
48   * instantiating OM's.
49   *
50   * @author <a href="mailto:jmcnally@collab.net">John McNally</a>
51   * @version $Id: AbstractBaseManager.java 571790 2007-09-01 12:40:44Z tv $
52   */
53  public abstract class AbstractBaseManager
54      implements Serializable
55  {
56      /*** the log */
57      protected static final Log log = LogFactory.getLog(AbstractBaseManager.class);
58  
59      /*** used to cache the om objects. cache is set by the region property */
60      protected transient GroupCacheAccess cache;
61  
62      /*** method results cache */
63      protected MethodResultCache mrCache;
64  
65      /*** the class that the service will instantiate */
66      private Class omClass;
67  
68      private String className;
69  
70      private String region;
71  
72      private boolean isNew = true;
73  
74      protected Map validFields;
75      protected Map listenersMap = new HashMap();
76  
77      /***
78       * Get the Class instance
79       *
80       * @return the om class
81       */
82      protected Class getOMClass()
83      {
84          return omClass;
85      }
86  
87      /***
88       * Set the Class that will be instantiated by this manager
89       *
90       * @param omClass the om class
91       */
92      protected void setOMClass(Class omClass)
93      {
94          this.omClass = omClass;
95      }
96  
97      /***
98       * Get a fresh instance of an om
99       *
100      * @return an instance of the om class
101      * @throws InstantiationException
102      * @throws IllegalAccessException
103      */
104     protected Persistent getOMInstance()
105         throws InstantiationException, IllegalAccessException
106     {
107         return (Persistent) omClass.newInstance();
108     }
109 
110     /***
111      * Get the classname to instantiate for getInstance()
112      * @return value of className.
113      */
114     public String getClassName()
115     {
116         return className;
117     }
118 
119     /***
120      * Set the classname to instantiate for getInstance()
121      * @param v  Value to assign to className.
122      * @throws TorqueException Any exceptions caught during processing will be
123      *         rethrown wrapped into a TorqueException.
124      */
125     public void setClassName(String  v)
126         throws TorqueException
127     {
128         this.className = v;
129 
130         try
131         {
132             setOMClass(Class.forName(getClassName()));
133         }
134         catch (ClassNotFoundException cnfe)
135         {
136             throw new TorqueException("Could not load " + getClassName());
137         }
138     }
139 
140 
141     /***
142      * Return an instance of an om based on the id
143      *
144      * @param id the primary key of the object
145      * @return the object from persistent storage or from cache
146      * @throws TorqueException Any exceptions caught during processing will be
147      *         rethrown wrapped into a TorqueException.
148      */
149     protected Persistent getOMInstance(ObjectKey id)
150         throws TorqueException
151     {
152         return getOMInstance(id, true);
153     }
154 
155     /***
156      * Return an instance of an om based on the id
157      *
158      * @param key the primary key of the object
159      * @param fromCache true if the object should be retrieved from cache
160      * @return the object from persistent storage or from cache
161      * @throws TorqueException Any exceptions caught during processing will be
162      *         rethrown wrapped into a TorqueException.
163      */
164     protected Persistent getOMInstance(ObjectKey key, boolean fromCache)
165         throws TorqueException
166     {
167         Persistent om = null;
168         if (fromCache)
169         {
170             om = cacheGet(key);
171         }
172 
173         if (om == null)
174         {
175             om = retrieveStoredOM(key);
176             if (fromCache)
177             {
178                 putInstanceImpl(om);
179             }
180         }
181 
182         return om;
183     }
184 
185     /***
186      * Get an object from cache
187      *
188      * @param key the primary key of the object
189      * @return the object from cache
190      */
191     protected Persistent cacheGet(Serializable key)
192     {
193         Persistent om = null;
194         if (cache != null)
195         {
196             synchronized (this)
197             {
198                 om = (Persistent) cache.get(key);
199             }
200         }
201         return om;
202     }
203 
204     /***
205      * Clears the cache
206      *
207      * @throws TorqueException Any exceptions caught during processing will be
208      *         rethrown wrapped into a TorqueException.
209      */
210     protected void clearImpl()
211         throws TorqueException
212     {
213         if (cache != null)
214         {
215             try
216             {
217                 cache.clear();
218             }
219             catch (CacheException ce)
220             {
221                 throw new TorqueException(
222                         "Could not clear cache due to internal JCS error.", ce);
223             }
224         }
225     }
226 
227     /***
228      * Disposes of the cache. This triggers a shutdown of the connected cache
229      * instances. This method should only be used during shutdown of Torque. The
230      * manager instance will not cache anymore after this call.
231      */
232     public void dispose()
233     {
234         if (cache != null)
235         {
236             cache.dispose();
237             cache = null;
238         }
239     }
240 
241     /***
242      * Remove an object from the cache
243      *
244      * @param key the cache key for the object
245      * @return the object one last time
246      * @throws TorqueException Any exceptions caught during processing will be
247      *         rethrown wrapped into a TorqueException.
248      */
249     protected Persistent removeInstanceImpl(Serializable key)
250         throws TorqueException
251     {
252         Persistent oldOm = null;
253         if (cache != null)
254         {
255             try
256             {
257                 synchronized (this)
258                 {
259                     oldOm = (Persistent) cache.get(key);
260                     cache.remove(key);
261                 }
262             }
263             catch (CacheException ce)
264             {
265                 throw new TorqueException
266                     ("Could not remove from cache due to internal JCS error",
267                      ce);
268             }
269         }
270         return oldOm;
271     }
272 
273     /***
274      * Put an object into the cache
275      *
276      * @param om the object
277      * @return if an object with the same key already is in the cache
278      *         this object will be returned, else null
279      * @throws TorqueException Any exceptions caught during processing will be
280      *         rethrown wrapped into a TorqueException.
281      */
282     protected Persistent putInstanceImpl(Persistent om)
283         throws TorqueException
284     {
285         ObjectKey key = om.getPrimaryKey();
286         return putInstanceImpl(key, om);
287     }
288 
289     /***
290      * Put an object into the cache
291      *
292      * @param key the cache key for the object
293      * @param om the object
294      * @return if an object with this key already is in the cache
295      *         this object will be returned, else null
296      * @throws TorqueException Any exceptions caught during processing will be
297      *         rethrown wrapped into a TorqueException.
298      */
299     protected Persistent putInstanceImpl(Serializable key, Persistent om)
300         throws TorqueException
301     {
302         if (getOMClass() != null && !getOMClass().isInstance(om))
303         {
304             throw new TorqueException(om + "; class=" + om.getClass().getName()
305                 + "; id=" + om.getPrimaryKey() + " cannot be cached with "
306                 + getOMClass().getName() + " objects");
307         }
308 
309         Persistent oldOm = null;
310         if (cache != null)
311         {
312             try
313             {
314                 synchronized (this)
315                 {
316                     oldOm = (Persistent) cache.get(key);
317                     cache.put(key, om);
318                 }
319             }
320             catch (CacheException ce)
321             {
322                 throw new TorqueException
323                     ("Could not cache due to internal JCS error", ce);
324             }
325         }
326         return oldOm;
327     }
328 
329     /***
330      * Retrieve an object from persistent storage
331      *
332      * @param id the primary key of the object
333      * @return the object
334      * @throws TorqueException Any exceptions caught during processing will be
335      *         rethrown wrapped into a TorqueException.
336      */
337     protected abstract Persistent retrieveStoredOM(ObjectKey id)
338         throws TorqueException;
339 
340     /***
341      * Gets a list of om's based on id's.
342      *
343      * @param ids a <code>ObjectKey[]</code> value
344      * @return a <code>List</code> value
345      * @throws TorqueException Any exceptions caught during processing will be
346      *         rethrown wrapped into a TorqueException.
347      */
348     protected List getOMs(ObjectKey[] ids)
349         throws TorqueException
350     {
351         return getOMs(Arrays.asList(ids));
352     }
353 
354     /***
355      * Gets a list of om's based on id's.
356      *
357      * @param ids a <code>List</code> of <code>ObjectKey</code>'s
358      * @return a <code>List</code> value
359      * @throws TorqueException Any exceptions caught during processing will be
360      *         rethrown wrapped into a TorqueException.
361      */
362     protected List getOMs(List ids)
363         throws TorqueException
364     {
365         return getOMs(ids, true);
366     }
367 
368     /***
369      * Gets a list of om's based on id's.
370      *
371      * @param ids a <code>List</code> of <code>ObjectKey</code>'s
372      * @return a <code>List</code> value
373      * @throws TorqueException Any exceptions caught during processing will be
374      *         rethrown wrapped into a TorqueException.
375      */
376     protected List getOMs(List ids, boolean fromCache)
377         throws TorqueException
378     {
379         List oms = null;
380         if (ids != null && ids.size() > 0)
381         {
382             // start a new list where we will replace the id's with om's
383             oms = new ArrayList(ids);
384             List newIds = new ArrayList(ids.size());
385             for (int i = 0; i < ids.size(); i++)
386             {
387                 ObjectKey key = (ObjectKey) ids.get(i);
388                 Persistent om = null;
389                 if (fromCache)
390                 {
391                     om = cacheGet(key);
392                 }
393                 if (om == null)
394                 {
395                     newIds.add(key);
396                 }
397                 else
398                 {
399                     oms.set(i, om);
400                 }
401             }
402 
403             if (newIds.size() > 0)
404             {
405                 List newOms = retrieveStoredOMs(newIds);
406                 for (int i = 0; i < oms.size(); i++)
407                 {
408                     if (oms.get(i) instanceof ObjectKey)
409                     {
410                         for (int j = newOms.size() - 1; j >= 0; j--)
411                         {
412                             Persistent om = (Persistent) newOms.get(j);
413                             if (om.getPrimaryKey().equals(oms.get(i)))
414                             {
415                                 // replace the id with the om and add the om
416                                 // to the cache
417                                 oms.set(i, om);
418                                 newOms.remove(j);
419                                 if (fromCache)
420                                 {
421                                     putInstanceImpl(om);
422                                 }
423                                 break;
424                             }
425                         }
426                     }
427                 }
428             }
429         }
430         return oms;
431     }
432 
433     /***
434      * Gets a list of om's based on id's.
435      * This method must be implemented in the drived class
436      *
437      * @param ids a <code>List</code> of <code>ObjectKey</code>'s
438      * @return a <code>List</code> value
439      * @throws TorqueException Any exceptions caught during processing will be
440      *         rethrown wrapped into a TorqueException.
441      */
442     protected abstract List retrieveStoredOMs(List ids)
443         throws TorqueException;
444 
445     /***
446      * Get the value of region.
447      *
448      * @return value of region.
449      */
450     public String getRegion()
451     {
452         return region;
453     }
454 
455     /***
456      * Set the value of region.
457      *
458      * @param v  Value to assign to region.
459      * @throws TorqueException Any exceptions caught during processing will be
460      *         rethrown wrapped into a TorqueException.
461      */
462     public void setRegion(String v)
463         throws TorqueException
464     {
465         this.region = v;
466         try
467         {
468             if (Torque.getConfiguration().getBoolean(Torque.CACHE_KEY, false))
469             {
470                 cache = JCS.getInstance(getRegion());
471                 mrCache = new MethodResultCache(cache);
472             }
473             else
474             {
475                 mrCache = new NoOpMethodResultCache(cache);
476             }
477         }
478         catch (CacheException e)
479         {
480             throw new TorqueException("Cache could not be initialized", e);
481         }
482 
483         if (cache == null)
484         {
485             log.info("Cache could not be initialized for region: " + v);
486         }
487     }
488 
489     /***
490      * @return The cache instance.
491      */
492     public MethodResultCache getMethodResultCache()
493     {
494         if (isNew)
495         {
496             synchronized (this)
497             {
498                 if (isNew)
499                 {
500                     registerAsListener();
501                     isNew = false;
502                 }
503             }
504         }
505         return mrCache;
506     }
507 
508     /***
509      * NoOp version.  Managers should override this method to notify other
510      * managers that they are interested in CacheEvents.
511      */
512     protected void registerAsListener()
513     {
514     }
515 
516     /***
517      *
518      * @param listener A new listener for cache events.
519      */
520     public void addCacheListenerImpl(CacheListener listener)
521     {
522         List keys = listener.getInterestedFields();
523         Iterator i = keys.iterator();
524         while (i.hasNext())
525         {
526             String key = (String) i.next();
527             // Peer.column names are the fields
528             if (validFields != null && validFields.containsKey(key))
529             {
530                 List listeners = (List) listenersMap.get(key);
531                 if (listeners == null)
532                 {
533                     listeners = createSubsetList(key);
534                 }
535 
536                 boolean isNew = true;
537                 Iterator j = listeners.iterator();
538                 while (j.hasNext())
539                 {
540                     Object listener2 =
541                         ((WeakReference) j.next()).get();
542                     if (listener2 == null)
543                     {
544                         // do a little cleanup while checking for dupes
545                         // not thread-safe, not likely to be many nulls
546                         // but should revisit
547                         //j.remove();
548                     }
549                     else if (listener2 == listener)
550                     {
551                         isNew = false;
552                         break;
553                     }
554                 }
555                 if (isNew)
556                 {
557                     listeners.add(new WeakReference(listener));
558                 }
559             }
560         }
561     }
562 
563     /***
564      *
565      * @param key
566      * @return A subset of the list identified by <code>key</code>.
567      */
568     private synchronized List createSubsetList(String key)
569     {
570         FastArrayList list = null;
571         if (listenersMap.containsKey(key))
572         {
573             list = (FastArrayList) listenersMap.get(key);
574         }
575         else
576         {
577             list = new FastArrayList();
578             list.setFast(true);
579             listenersMap.put(key, list);
580         }
581         return list;
582     }
583 
584     /***
585      *
586      * @param listeners
587      * @param oldOm
588      * @param om
589      */
590     protected void notifyListeners(List listeners,
591                                    Persistent oldOm, Persistent om)
592     {
593         if (listeners != null)
594         {
595             synchronized (listeners)
596             {
597                 Iterator i = listeners.iterator();
598                 while (i.hasNext())
599                 {
600                     CacheListener listener = (CacheListener)
601                         ((WeakReference) i.next()).get();
602                     if (listener == null)
603                     {
604                         // remove reference as its object was cleared
605                         i.remove();
606                     }
607                     else
608                     {
609                         if (oldOm == null)
610                         {
611                             // object was added
612                             listener.addedObject(om);
613                         }
614                         else
615                         {
616                             // object was refreshed
617                             listener.refreshedObject(om);
618                         }
619                     }
620                 }
621             }
622         }
623     }
624 
625 
626     /***
627      * helper methods for the Serializable interface
628      *
629      * @param out
630      * @throws IOException
631      */
632     private void writeObject(java.io.ObjectOutputStream out)
633         throws IOException
634     {
635         out.defaultWriteObject();
636     }
637 
638     /***
639      * Helper methods for the <code>Serializable</code> interface.
640      *
641      * @param in The stream to read a <code>Serializable</code> from.
642      * @throws IOException
643      * @throws ClassNotFoundException
644      */
645     private void readObject(ObjectInputStream in)
646         throws IOException, ClassNotFoundException
647     {
648         in.defaultReadObject();
649         // initialize the cache
650         try
651         {
652             if (region != null)
653             {
654                 setRegion(region);
655             }
656         }
657         catch (Exception e)
658         {
659             log.error("Cache could not be initialized for region '"
660                       + region + "' after deserialization");
661         }
662     }
663 }