View Javadoc

1   /*
2    * Copyright 2012-2013 smartics, Kronseder & Reiner GmbH
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  package de.smartics.properties.impl.config.cache;
17  
18  import java.io.Serializable;
19  import java.util.Properties;
20  import java.util.concurrent.TimeUnit;
21  import java.util.concurrent.locks.Lock;
22  import java.util.concurrent.locks.ReentrantReadWriteLock;
23  
24  import javax.annotation.concurrent.ThreadSafe;
25  
26  import org.apache.commons.lang.ObjectUtils;
27  import org.slf4j.Logger;
28  import org.slf4j.LoggerFactory;
29  
30  import com.google.common.collect.HashMultimap;
31  import com.google.common.collect.Multimap;
32  
33  import de.smartics.properties.api.config.domain.ConfigurationProperties;
34  import de.smartics.properties.api.config.domain.DescribedProperty;
35  import de.smartics.properties.api.config.domain.Property;
36  import de.smartics.properties.api.config.domain.PropertyCollection;
37  import de.smartics.properties.api.config.domain.UnknownPropertyException;
38  import de.smartics.properties.api.config.domain.ValidatedProperty;
39  import de.smartics.properties.api.config.domain.key.ConfigurationKey;
40  import de.smartics.properties.api.core.domain.PropertyDescriptor;
41  import de.smartics.properties.api.core.domain.PropertyValidationException;
42  import de.smartics.properties.spi.config.cache.CacheManagerFactory;
43  import de.smartics.properties.spi.config.cache.UnawareCache;
44  import de.smartics.util.lang.Arg;
45  
46  /**
47   * A cache implementation that tracks dependencies via placeholder contained in
48   * property values and property default expressions. If a property is
49   * invalidated, all properties that refer to this property (even transitively)
50   * will also be invalidated. Invalidation implies, that the property is revoked
51   * from the cache.
52   */
53  @ThreadSafe
54  final class DependencyTrackingCache implements Serializable
55  {
56    // ********************************* Fields *********************************
57  
58    // --- constants ------------------------------------------------------------
59  
60    /**
61     * The class version identifier.
62     */
63    private static final long serialVersionUID = 1L;
64  
65    /**
66     * Reference to the logger for this class.
67     */
68    private static final Logger LOG = LoggerFactory
69        .getLogger(DependencyTrackingCache.class);
70  
71    // --- members --------------------------------------------------------------
72  
73    /**
74     * The key to the configuration the cache is associated with.
75     *
76     * @serial
77     */
78    private final ConfigurationKey<?> configurationKey;
79  
80    /**
81     * The synchronized cache. The property stored may be a property or a resolved
82     * property.
83     *
84     * @serial
85     */
86    private final UnawareCache<String, DescribedProperty> cache;
87  
88    /**
89     * The dependencies of key to cascade the revoking of elements from the cache.
90     * The key is a property name the values depend upon. If the property with the
91     * given name is revoked from the cache, all the dependent properties of its
92     * list must also be revoked.
93     *
94     * @serial
95     */
96    private final Multimap<String, String> dependencies;
97  
98    /**
99     * The lock for synchronized access.
100    *
101    * @serial
102    */
103   private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
104 
105   /**
106    * The read lock for synchronized access.
107    *
108    * @serial
109    */
110   private final Lock readLock = lock.readLock();
111 
112   /**
113    * The write lock for synchronized access.
114    *
115    * @serial
116    */
117   private final Lock writeLock = lock.writeLock();
118 
119   // ****************************** Initializer *******************************
120 
121   // ****************************** Constructors ******************************
122 
123   /**
124    * Default constructor.
125    *
126    * @param configurationKey the key to the configuration the cache is
127    *          associated with.
128    * @throws NullPointerException if {@code configurationKey} is
129    *           <code>null</code>.
130    */
131   @SuppressWarnings("unchecked")
132   public DependencyTrackingCache(final ConfigurationKey<?> configurationKey)
133     throws NullPointerException
134   {
135     this.configurationKey =
136         Arg.checkNotNull("configurationKey", configurationKey);
137 
138     final String cacheName = configurationKey.toString();
139     this.cache =
140         (UnawareCache<String, DescribedProperty>) CacheManagerFactory
141             .getInstance().createManager().getPropertiesCache(cacheName);
142     LOG.debug("Caches: {}", CacheManagerFactory.getInstance().createManager()
143         .getCacheNames());
144     this.dependencies = HashMultimap.create();
145   }
146 
147   // ****************************** Inner Classes *****************************
148 
149   // ********************************* Methods ********************************
150 
151   // --- init -----------------------------------------------------------------
152 
153   // --- get&set --------------------------------------------------------------
154 
155   /**
156    * Returns the key to the configuration the cache is associated with.
157    *
158    * @return the key to the configuration the cache is associated with.
159    */
160   public ConfigurationKey<?> getConfigurationKey()
161   {
162     return configurationKey;
163   }
164 
165   // --- business -------------------------------------------------------------
166 
167   // CHECKSTYLE:OFF
168   public ValidatedProperty getValidatedProperty(
169       // CHECKSTYLE:ON
170       final ConfigurationProperties propertySource,
171       final PropertyDescriptor descriptor, final Object defaultValue)
172     throws IllegalArgumentException, UnknownPropertyException,
173     PropertyValidationException
174   {
175     readLock.lock();
176     boolean doReadUnlock = true;
177     final String key = descriptor.getKey().toString();
178     try
179     {
180       final Property property = cache.get(key);
181       if (property instanceof ValidatedProperty)
182       {
183         LOG.debug("Cache hit: {}", key);
184         return (ValidatedProperty) property;
185       }
186       else
187       {
188         readLock.unlock();
189         doReadUnlock = false;
190 
191         final ValidatedProperty validatedProperty =
192             propertySource.getValidatedProperty(key, defaultValue);
193 
194         if (validatedProperty != null)
195         {
196           writeLock.lock();
197           try
198           {
199             putToCache(descriptor, key, validatedProperty);
200             attachDependencies(validatedProperty);
201           }
202           finally
203           {
204             writeLock.unlock();
205           }
206         }
207 
208         LOG.debug("Cache miss: {}", key);
209         return validatedProperty;
210       }
211     }
212     finally
213     {
214       if (doReadUnlock)
215       {
216         readLock.unlock();
217       }
218     }
219   }
220 
221   private void putToCache(final PropertyDescriptor descriptor,
222       final String key, final ValidatedProperty validatedProperty)
223   {
224     final long updateIntervalInMs = descriptor.getUpdateIntervalInMs();
225     cache
226         .put(key, validatedProperty, updateIntervalInMs, TimeUnit.MILLISECONDS);
227   }
228 
229   private void putToCache(final PropertyDescriptor descriptor,
230       final String key, final DescribedProperty property)
231   {
232     final long updateIntervalInMs = descriptor.getUpdateIntervalInMs();
233     cache.put(key, property, updateIntervalInMs, TimeUnit.MILLISECONDS);
234   }
235 
236   // CHECKSTYLE:OFF
237   public DescribedProperty getProperty(
238       final ConfigurationProperties propertySource,
239       final PropertyDescriptor descriptor, final Object defaultValue)
240   // CHECKSTYLE:ON
241   {
242     readLock.lock();
243     boolean doReadUnlock = true;
244 
245     final String key = descriptor.getKey().toString();
246     try
247     {
248       DescribedProperty property = cache.get(key);
249       if (property != null)
250       {
251         LOG.debug("Cache hit: {}", key);
252         return property;
253       }
254       else
255       {
256         readLock.unlock();
257         doReadUnlock = false;
258 
259         property = propertySource.getProperty(key, defaultValue);
260 
261         if (property != null)
262         {
263           writeLock.lock();
264           try
265           {
266             putToCache(descriptor, key, property);
267             if (property instanceof ValidatedProperty)
268             {
269               attachDependencies((ValidatedProperty) property);
270             }
271           }
272           finally
273           {
274             writeLock.unlock();
275           }
276         }
277 
278         LOG.debug("Cache miss: {}", key);
279         return property;
280       }
281     }
282     finally
283     {
284       if (doReadUnlock)
285       {
286         readLock.unlock();
287       }
288     }
289   }
290 
291   public Property removeFromCache(final String key)
292   {
293     writeLock.lock();
294     try
295     {
296       final Property oldProperty = cache.remove(key);
297       removeDependencies(key);
298       LOG.debug("Cache revoke: {}", key);
299       return oldProperty;
300     }
301     finally
302     {
303       writeLock.unlock();
304     }
305   }
306 
307   public void removeFromCache(final Properties properties)
308   {
309     writeLock.lock();
310     try
311     {
312       for (final Object key : properties.keySet())
313       {
314         cache.remove((String) key);
315         removeDependencies(ObjectUtils.toString(key, null));
316         LOG.debug("Cache revoke due to init: {}", key);
317       }
318     }
319     finally
320     {
321       writeLock.unlock();
322     }
323   }
324 
325   public void removeFromCache(final PropertyCollection collection)
326   {
327     writeLock.lock();
328     try
329     {
330       for (final Property property : collection)
331       {
332         final String key = property.getName();
333         cache.remove(key);
334         removeDependencies(ObjectUtils.toString(key, null));
335         LOG.debug("Cache revoke due to init: {}", key);
336       }
337     }
338     finally
339     {
340       collection.close();
341       writeLock.unlock();
342     }
343   }
344 
345   /**
346    * It is assumed that the caller already holds the write lock.
347    */
348   private void attachDependencies(final ValidatedProperty property)
349   {
350     final String key = property.getName();
351 
352     for (final String dependency : property.getDependencies())
353     {
354       dependencies.put(dependency, key);
355     }
356   }
357 
358   /**
359    * It is assumed that the caller already holds the write lock.
360    */
361   private void removeDependencies(final String key)
362   {
363     for (final String dependant : dependencies.removeAll(key))
364     {
365       LOG.debug("Cache revoke due to dependency on {}: {}", key, dependant);
366       removeFromCache(dependant);
367     }
368   }
369 
370   // --- object basics --------------------------------------------------------
371 
372 }