View Javadoc

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