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 473821 2006-11-11 22:37:25Z 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.remove();
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      * Remove an object from the cache
229      *
230      * @param key the cache key for the object
231      * @return the object one last time
232      * @throws TorqueException Any exceptions caught during processing will be
233      *         rethrown wrapped into a TorqueException.
234      */
235     protected Persistent removeInstanceImpl(Serializable key)
236         throws TorqueException
237     {
238         Persistent oldOm = null;
239         if (cache != null)
240         {
241             try
242             {
243                 synchronized (this)
244                 {
245                     oldOm = (Persistent) cache.get(key);
246                     cache.remove(key);
247                 }
248             }
249             catch (CacheException ce)
250             {
251                 throw new TorqueException
252                     ("Could not remove from cache due to internal JCS error",
253                      ce);
254             }
255         }
256         return oldOm;
257     }
258 
259     /***
260      * Put an object into the cache
261      *
262      * @param om the object
263      * @return if an object with the same key already is in the cache
264      *         this object will be returned, else null
265      * @throws TorqueException Any exceptions caught during processing will be
266      *         rethrown wrapped into a TorqueException.
267      */
268     protected Persistent putInstanceImpl(Persistent om)
269         throws TorqueException
270     {
271         ObjectKey key = om.getPrimaryKey();
272         return putInstanceImpl(key, om);
273     }
274 
275     /***
276      * Put an object into the cache
277      *
278      * @param key the cache key for the object
279      * @param om the object
280      * @return if an object with this key already is in the cache
281      *         this object will be returned, else null
282      * @throws TorqueException Any exceptions caught during processing will be
283      *         rethrown wrapped into a TorqueException.
284      */
285     protected Persistent putInstanceImpl(Serializable key, Persistent om)
286         throws TorqueException
287     {
288         if (getOMClass() != null && !getOMClass().isInstance(om))
289         {
290             throw new TorqueException(om + "; class=" + om.getClass().getName()
291                 + "; id=" + om.getPrimaryKey() + " cannot be cached with "
292                 + getOMClass().getName() + " objects");
293         }
294 
295         Persistent oldOm = null;
296         if (cache != null)
297         {
298             try
299             {
300                 synchronized (this)
301                 {
302                     oldOm = (Persistent) cache.get(key);
303                     cache.put(key, om);
304                 }
305             }
306             catch (CacheException ce)
307             {
308                 throw new TorqueException
309                     ("Could not cache due to internal JCS error", ce);
310             }
311         }
312         return oldOm;
313     }
314 
315     /***
316      * Retrieve an object from persistent storage
317      *
318      * @param id the primary key of the object
319      * @return the object
320      * @throws TorqueException Any exceptions caught during processing will be
321      *         rethrown wrapped into a TorqueException.
322      */
323     protected abstract Persistent retrieveStoredOM(ObjectKey id)
324         throws TorqueException;
325 
326     /***
327      * Gets a list of om's based on id's.
328      *
329      * @param ids a <code>ObjectKey[]</code> value
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(ObjectKey[] ids)
335         throws TorqueException
336     {
337         return getOMs(Arrays.asList(ids));
338     }
339 
340     /***
341      * Gets a list of om's based on id's.
342      *
343      * @param ids a <code>List</code> of <code>ObjectKey</code>'s
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(List ids)
349         throws TorqueException
350     {
351         return getOMs(ids, true);
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, boolean fromCache)
363         throws TorqueException
364     {
365         List oms = null;
366         if (ids != null && ids.size() > 0)
367         {
368             // start a new list where we will replace the id's with om's
369             oms = new ArrayList(ids);
370             List newIds = new ArrayList(ids.size());
371             for (int i = 0; i < ids.size(); i++)
372             {
373                 ObjectKey key = (ObjectKey) ids.get(i);
374                 Persistent om = null;
375                 if (fromCache)
376                 {
377                     om = cacheGet(key);
378                 }
379                 if (om == null)
380                 {
381                     newIds.add(key);
382                 }
383                 else
384                 {
385                     oms.set(i, om);
386                 }
387             }
388 
389             if (newIds.size() > 0)
390             {
391                 List newOms = retrieveStoredOMs(newIds);
392                 for (int i = 0; i < oms.size(); i++)
393                 {
394                     if (oms.get(i) instanceof ObjectKey)
395                     {
396                         for (int j = newOms.size() - 1; j >= 0; j--)
397                         {
398                             Persistent om = (Persistent) newOms.get(j);
399                             if (om.getPrimaryKey().equals(oms.get(i)))
400                             {
401                                 // replace the id with the om and add the om
402                                 // to the cache
403                                 oms.set(i, om);
404                                 newOms.remove(j);
405                                 if (fromCache)
406                                 {
407                                     putInstanceImpl(om);
408                                 }
409                                 break;
410                             }
411                         }
412                     }
413                 }
414             }
415         }
416         return oms;
417     }
418 
419     /***
420      * Gets a list of om's based on id's.
421      * This method must be implemented in the drived class
422      *
423      * @param ids a <code>List</code> of <code>ObjectKey</code>'s
424      * @return a <code>List</code> value
425      * @throws TorqueException Any exceptions caught during processing will be
426      *         rethrown wrapped into a TorqueException.
427      */
428     protected abstract List retrieveStoredOMs(List ids)
429         throws TorqueException;
430 
431     /***
432      * Get the value of region.
433      *
434      * @return value of region.
435      */
436     public String getRegion()
437     {
438         return region;
439     }
440 
441     /***
442      * Set the value of region.
443      *
444      * @param v  Value to assign to region.
445      * @throws TorqueException Any exceptions caught during processing will be
446      *         rethrown wrapped into a TorqueException.
447      */
448     public void setRegion(String v)
449         throws TorqueException
450     {
451         this.region = v;
452         try
453         {
454             if (Torque.getConfiguration().getBoolean(Torque.CACHE_KEY, false))
455             {
456                 cache = JCS.getInstance(getRegion());
457                 mrCache = new MethodResultCache(cache);
458             }
459             else
460             {
461                 mrCache = new NoOpMethodResultCache(cache);
462             }
463         }
464         catch (CacheException e)
465         {
466             throw new TorqueException("Cache could not be initialized", e);
467         }
468 
469         if (cache == null)
470         {
471             log.info("Cache could not be initialized for region: " + v);
472         }
473     }
474 
475     /***
476      * @return The cache instance.
477      */
478     public MethodResultCache getMethodResultCache()
479     {
480         if (isNew)
481         {
482             synchronized (this)
483             {
484                 if (isNew)
485                 {
486                     registerAsListener();
487                     isNew = false;
488                 }
489             }
490         }
491         return mrCache;
492     }
493 
494     /***
495      * NoOp version.  Managers should override this method to notify other
496      * managers that they are interested in CacheEvents.
497      */
498     protected void registerAsListener()
499     {
500     }
501 
502     /***
503      *
504      * @param listener A new listener for cache events.
505      */
506     public void addCacheListenerImpl(CacheListener listener)
507     {
508         List keys = listener.getInterestedFields();
509         Iterator i = keys.iterator();
510         while (i.hasNext())
511         {
512             String key = (String) i.next();
513             // Peer.column names are the fields
514             if (validFields != null && validFields.containsKey(key))
515             {
516                 List listeners = (List) listenersMap.get(key);
517                 if (listeners == null)
518                 {
519                     listeners = createSubsetList(key);
520                 }
521 
522                 boolean isNew = true;
523                 Iterator j = listeners.iterator();
524                 while (j.hasNext())
525                 {
526                     Object listener2 =
527                         ((WeakReference) j.next()).get();
528                     if (listener2 == null)
529                     {
530                         // do a little cleanup while checking for dupes
531                         // not thread-safe, not likely to be many nulls
532                         // but should revisit
533                         //j.remove();
534                     }
535                     else if (listener2 == listener)
536                     {
537                         isNew = false;
538                         break;
539                     }
540                 }
541                 if (isNew)
542                 {
543                     listeners.add(new WeakReference(listener));
544                 }
545             }
546         }
547     }
548 
549     /***
550      *
551      * @param key
552      * @return A subset of the list identified by <code>key</code>.
553      */
554     private synchronized List createSubsetList(String key)
555     {
556         FastArrayList list = null;
557         if (listenersMap.containsKey(key))
558         {
559             list = (FastArrayList) listenersMap.get(key);
560         }
561         else
562         {
563             list = new FastArrayList();
564             list.setFast(true);
565             listenersMap.put(key, list);
566         }
567         return list;
568     }
569 
570     /***
571      *
572      * @param listeners
573      * @param oldOm
574      * @param om
575      */
576     protected void notifyListeners(List listeners,
577                                    Persistent oldOm, Persistent om)
578     {
579         if (listeners != null)
580         {
581             synchronized (listeners)
582             {
583                 Iterator i = listeners.iterator();
584                 while (i.hasNext())
585                 {
586                     CacheListener listener = (CacheListener)
587                         ((WeakReference) i.next()).get();
588                     if (listener == null)
589                     {
590                         // remove reference as its object was cleared
591                         i.remove();
592                     }
593                     else
594                     {
595                         if (oldOm == null)
596                         {
597                             // object was added
598                             listener.addedObject(om);
599                         }
600                         else
601                         {
602                             // object was refreshed
603                             listener.refreshedObject(om);
604                         }
605                     }
606                 }
607             }
608         }
609     }
610 
611 
612     /***
613      * helper methods for the Serializable interface
614      *
615      * @param out
616      * @throws IOException
617      */
618     private void writeObject(java.io.ObjectOutputStream out)
619         throws IOException
620     {
621         out.defaultWriteObject();
622     }
623 
624     /***
625      * Helper methods for the <code>Serializable</code> interface.
626      *
627      * @param in The stream to read a <code>Serializable</code> from.
628      * @throws IOException
629      * @throws ClassNotFoundException
630      */
631     private void readObject(ObjectInputStream in)
632         throws IOException, ClassNotFoundException
633     {
634         in.defaultReadObject();
635         // initialize the cache
636         try
637         {
638             if (region != null)
639             {
640                 setRegion(region);
641             }
642         }
643         catch (Exception e)
644         {
645             log.error("Cache could not be initialized for region '"
646                       + region + "' after deserialization");
647         }
648     }
649 }