1   /*
2    * Copyright 2003 - 2013 The eFaps Team
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   *
16   * Revision:        $Rev$
17   * Last Changed:    $Date$
18   * Last Changed By: $Author$
19   */
20  
21  package org.efaps.util.cache;
22  
23  import java.util.Collections;
24  import java.util.HashMap;
25  import java.util.HashSet;
26  import java.util.Map;
27  import java.util.Set;
28  import java.util.UUID;
29  
30  import org.slf4j.Logger;
31  import org.slf4j.LoggerFactory;
32  
33  /**
34   * <p>The class is used to cache three not independent information which are
35   * completely defined once but needed many times within eFaps. An example
36   * is to load all types at startup and access the cached
37   * data instead of reading the cached information each time they are
38   * needed.</p>
39   * <p>If a reload of the cache is needed, the cached object could be accessed,
40   * but returns the &quot;old&quot; values until the old cache is replaced by
41   * the new read values.</p>
42   * <p>The class is thread save, but a cached instance could be accessed much
43   * faster than a synchronized hash map.</p>
44   *
45   * @author The eFaps Team
46   * @param <T> class implementing CacheObjectInterface
47   * @version $Id$
48   */
49  public abstract class AbstractCache<T extends CacheObjectInterface>
50  {
51      /**
52       * Logging instance used to give logging information of this class.
53       */
54      private static final Logger LOG = LoggerFactory.getLogger(AbstractCache.class);
55  
56      /**
57       * Set that stores all initialized Caches.
58       */
59      private static Set<AbstractCache<?>> CACHES = Collections.synchronizedSet(new HashSet<AbstractCache<?>>());
60  
61      /**
62       * The map holds all cached data instances by Id. Because of the
63       * double-checked locking idiom, the instance variable is defined
64       * <i>volatile</i>.
65       *
66       * @see #get(Long)
67       */
68      private volatile Map<Long, T> cache4Id = null;
69  
70      /**
71       * The map holds all cached data instances by Name. Because of the
72       * double-checked locking idiom, the instance variable is defined
73       * <i>volatile</i>.
74       *
75       * @see #get(String)
76       */
77      private volatile Map<String, T> cache4Name = null;
78  
79      /**
80       * The map holds all cached data instances by UUID. Because of the
81       * double-checked locking idiom, the instance variable is defined
82       * <i>volatile</i>.
83       *
84       * @see #get(UUID)
85       */
86      private volatile Map<UUID, T> cache4UUID = null;
87  
88      /**
89       * Stores the class name of the class that initialized this cache.
90       */
91      private String initializer;
92  
93      /**
94       * Constructor adding this Cache to the set of Caches.
95       */
96      protected AbstractCache()
97      {
98          AbstractCache.CACHES.add(this);
99      }
100 
101     /**
102      * Returns for given key id the cached object from the cache4Id cache. If
103      * the cache was not initialized yet, <code>null</code> is returned.
104      *
105      * @see #getCache4Id()
106      * @param _id       id of the searched cached object
107      * @return cached object
108      *
109      */
110     public T get(final long _id)
111     {
112         return getCache4Id() == null ? null : getCache4Id().get(new Long(_id));
113     }
114 
115     /**
116      * Returns for given key id the cached object from the cache4Id cache. If
117      * the cache was not initialized yet, <code>null</code> is returned.
118      *
119      * @see #getCache4Name()
120      * @param _name     name the cached object
121      * @return cached object
122      */
123     public T get(final String _name)
124     {
125         return getCache4Name() == null ? null : getCache4Name().get(_name);
126     }
127 
128     /**
129      * Returns for given key id the cached object from the cache4Id cache. If
130      * the cache was not initialized yet, <code>null</code> is returned.
131      *
132      * @see #getCache4UUID()
133      * @param _uuid     UUID of the cached object
134      * @return cached object
135      *
136      */
137     public T get(final UUID _uuid)
138     {
139         return getCache4UUID() == null ? null : getCache4UUID().get(_uuid);
140     }
141 
142     /**
143      * Add an object to this Cache. This method should only be used to add some
144      * objects, due to the reason that it is very slow!!! Normally the values
145      * should be added by using abstract method
146      * {@link #readCache(Map, Map, Map)}.
147      *
148      * @param _object Object to be added
149      */
150     public void addObject(final T _object)
151     {
152         final Map<Long, T> newCache4Id =  getNewCache4Id();
153         final Map<String, T> newCache4Name = getNewCache4Name();
154         final Map<UUID, T>  newCache4UUID = getNewCache4UUID();
155 
156         if (getCache4Id() != null) {
157             newCache4Id.putAll(getCache4Id());
158         }
159 
160         if (getCache4Name() != null) {
161             newCache4Name.putAll(getCache4Name());
162         }
163 
164         if (getCache4UUID() != null) {
165             newCache4UUID.putAll(getCache4UUID());
166         }
167 
168         newCache4Id.put(_object.getId(), _object);
169         newCache4Name.put(_object.getName(), _object);
170         newCache4UUID.put(_object.getUUID(), _object);
171 
172         // replace old cache with new values
173         // it is thread save because of volatile
174         setCache4Id(newCache4Id);
175         setCache4Name(newCache4Name);
176         setCache4UUID(newCache4UUID);
177     }
178 
179     /**
180      * The method tests, if the cache has stored some entries.
181      *
182      * @return <i>true</i> if the cache has some entries, otherwise
183      *         <i>false</i>
184      */
185     public boolean hasEntries()
186     {
187         return (getCache4Id() != null && !getCache4Id().isEmpty())
188             || (getCache4Name() != null && !getCache4Name().isEmpty())
189             || (getCache4UUID() != null && !getCache4UUID().isEmpty());
190     }
191 
192     /**
193      * The method initialize the cache.
194      *
195      * @param _initializer class name of the class initializing
196      *
197      */
198     public void initialize(final Class<?> _initializer)
199     {
200         this.initializer = _initializer.getName();
201         synchronized (this) {
202             try {
203                 readCache();
204             } catch (final CacheReloadException e) {
205                 AbstractCache.LOG.error("Unexpected error while initializing Cache for " + getClass(), e);
206             }
207         }
208     }
209 
210     /**
211      * The complete cache is read and then replaces the current stored values
212      * in the cache. The cache must be read in this order:
213      * <ul>
214      * <li>create new cache map</li>
215      * <li>read cache in the new cache map</li>
216      * <li>replace old cache map with the new cache map</li>
217      * </ul>
218      * This order is imported, otherwise
219      * <ul>
220      * <li>if the cache initialized first time, the cache is not
221      *     <code>null</code> and returns then wrong values</li>
222      * <li>existing and read values could not be read while a reload is
223      *     done</li>
224      * </ul>
225      *
226      * @throws CacheReloadException if the cache could not be read (the
227      *                              exception is also written into the error
228      *                              log)
229      */
230     private void readCache()
231         throws CacheReloadException
232     {
233         // if cache is not initialized, the correct order is required!
234         // otherwise the cache is not null and returns wrong values!
235         final Map<Long, T> newCache4Id = getNewCache4Id();
236         final Map<String, T> newCache4Name = getNewCache4Name();
237         final Map<UUID, T>  newCache4UUID = getNewCache4UUID();
238         try {
239             readCache(newCache4Id, newCache4Name, newCache4UUID);
240         } catch (final CacheReloadException e) {
241             AbstractCache.LOG.error("Read Cache for " + getClass() + " failed", e);
242             throw e;
243             //CHECKSTYLE:OFF
244         } catch (final Exception e) {
245             //CHECKSTYLE:ON
246             AbstractCache.LOG.error("Unexpected error while reading Cache for " + getClass(), e);
247             throw new CacheReloadException("Unexpected error while reading Cache " + "for " + getClass(), e);
248         }
249         // replace old cache with new values
250         // it is thread save because of volatile
251         this.cache4Id = newCache4Id;
252         this.cache4Name = newCache4Name;
253         this.cache4UUID = newCache4UUID;
254     }
255 
256     /**
257      * Method to fill this cache with objects.
258      *
259      * @param _newCache4Id      cache for id
260      * @param _newCache4Name    cache for name
261      * @param _newCache4UUID    cache for UUID
262      * @throws CacheReloadException on error during reading
263      */
264     protected abstract void readCache(final Map<Long, T> _newCache4Id,
265                                       final Map<String, T> _newCache4Name,
266                                       final Map<UUID, T> _newCache4UUID)
267         throws CacheReloadException;
268 
269     /**
270      * Method to get a new empty Map for the id Cache.
271      * The method is implemented to provide the possibility to use
272      * different Map types in the extending classes.
273      *
274      * @return new empty Instance of a HashMap
275      */
276     protected Map<Long, T> getNewCache4Id()
277     {
278         return new HashMap<Long, T>();
279     }
280 
281     /**
282      * Method to get a new empty Map for the id Cache.
283      * The method is implemented to provide the possibility to use
284      * different Map types in the extending classes.
285      *
286      * @return new empty Instance of a HashMap
287      */
288     protected Map<String, T> getNewCache4Name()
289     {
290         return new HashMap<String, T>();
291     }
292 
293     /**
294      * Method to get a new empty Map for the id Cache.
295      * The method is implemented to provide the possibility to use
296      * different Map types in the extending classes.
297      *
298      * @return new empty Instance of a HashMap
299      */
300     protected Map<UUID, T> getNewCache4UUID()
301     {
302         return new HashMap<UUID, T>();
303     }
304 
305     /**
306      * Getter method for instance variable {@link #cache4Id}.
307      *
308      * @return value of instance variable {@link #cache4Id}
309      */
310     public Map<Long, T> getCache4Id()
311     {
312         return this.cache4Id;
313     }
314 
315     /**
316      * Setter method for instance variable {@link #cache4Id}.
317      *
318      * @param _cache4Id value for instance variable {@link #cache4Id}
319      */
320     protected void setCache4Id(final Map<Long, T> _cache4Id)
321     {
322         this.cache4Id = _cache4Id;
323     }
324 
325     /**
326      * Getter method for instance variable {@link #cache4Name}.
327      *
328      * @return value of instance variable {@link #cache4Name}
329      */
330     protected Map<String, T> getCache4Name()
331     {
332         return this.cache4Name;
333     }
334 
335     /**
336      * Setter method for instance variable {@link #cache4Name}.
337      *
338      * @param _cache4Name value for instance variable {@link #cache4Name}
339      */
340     protected void setCache4Name(final Map<String, T> _cache4Name)
341     {
342         this.cache4Name = _cache4Name;
343     }
344 
345     /**
346      * Setter method for instance variable {@link #cache4UUID}.
347      *
348      * @param _cache4uuid value for instance variable {@link #cache4UUID}
349      */
350     protected void setCache4UUID(final Map<UUID, T> _cache4uuid)
351     {
352         this.cache4UUID = _cache4uuid;
353     }
354 
355     /**
356      * Getter method for instance variable {@link #cache4UUID}.
357      *
358      * @return value of instance variable {@link #cache4UUID}
359      */
360     protected Map<UUID, T> getCache4UUID()
361     {
362         return this.cache4UUID;
363     }
364 
365     /**
366      * Getter method for instance variable {@link #initializer}.
367      *
368      * @return value of instance variable {@link #initializer}
369      */
370     public String getInitializer()
371     {
372         return this.initializer;
373     }
374 
375     /**
376      * Clear all values of this Cache.
377      */
378     public void clear()
379     {
380         if (getCache4Id() != null) {
381             getCache4Id().clear();
382         }
383         if (getCache4Name() != null) {
384             getCache4Name().clear();
385         }
386         if (getCache4UUID() != null) {
387             getCache4UUID().clear();
388         }
389     }
390 
391     /**
392      * The static method removes all values in all caches.
393      */
394     public static void clearCaches()
395     {
396         synchronized (AbstractCache.CACHES) {
397             for (final AbstractCache<?> cache : AbstractCache.CACHES) {
398                 cache.getCache4Id().clear();
399                 cache.getCache4Name().clear();
400                 cache.getCache4UUID().clear();
401             }
402         }
403     }
404 
405     /**
406      * Getter method for variable {@link #CACHES}.
407      *
408      * @return value of variable {@link #CACHES}
409      */
410     public static Set<AbstractCache<?>> getCaches()
411     {
412         return AbstractCache.CACHES;
413     }
414 }