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             .getPropertiesCache(cacheName);
142     LOG.debug("Caches: {}", CacheManagerFactory.getCacheNames());
143     this.dependencies = HashMultimap.create();
144   }
145 
146   // ****************************** Inner Classes *****************************
147 
148   // ********************************* Methods ********************************
149 
150   // --- init -----------------------------------------------------------------
151 
152   // --- get&set --------------------------------------------------------------
153 
154   /**
155    * Returns the key to the configuration the cache is associated with.
156    *
157    * @return the key to the configuration the cache is associated with.
158    */
159   public ConfigurationKey<?> getConfigurationKey()
160   {
161     return configurationKey;
162   }
163 
164   // --- business -------------------------------------------------------------
165 
166   // CHECKSTYLE:OFF
167   public ValidatedProperty getValidatedProperty(
168       // CHECKSTYLE:ON
169       final ConfigurationProperties propertySource,
170       final PropertyDescriptor descriptor, final Object defaultValue)
171     throws IllegalArgumentException, UnknownPropertyException,
172     PropertyValidationException
173   {
174     readLock.lock();
175     boolean doReadUnlock = true;
176     final String key = descriptor.getKey().toString();
177     try
178     {
179       final Property property = cache.get(key);
180       if (property instanceof ValidatedProperty)
181       {
182         LOG.debug("Cache hit: {}", key);
183         return (ValidatedProperty) property;
184       }
185       else
186       {
187         readLock.unlock();
188         doReadUnlock = false;
189 
190         final ValidatedProperty validatedProperty =
191             propertySource.getValidatedProperty(key, defaultValue);
192 
193         if (validatedProperty != null)
194         {
195           writeLock.lock();
196           try
197           {
198             putToCache(descriptor, key, validatedProperty);
199             attachDependencies(validatedProperty);
200           }
201           finally
202           {
203             writeLock.unlock();
204           }
205         }
206 
207         LOG.debug("Cache miss: {}", key);
208         return validatedProperty;
209       }
210     }
211     finally
212     {
213       if (doReadUnlock)
214       {
215         readLock.unlock();
216       }
217     }
218   }
219 
220   private void putToCache(final PropertyDescriptor descriptor,
221       final String key, final ValidatedProperty validatedProperty)
222   {
223     final long updateIntervalInMs = descriptor.getUpdateIntervalInMs();
224     cache
225         .put(key, validatedProperty, updateIntervalInMs, TimeUnit.MILLISECONDS);
226   }
227 
228   private void putToCache(final PropertyDescriptor descriptor,
229       final String key, final DescribedProperty property)
230   {
231     final long updateIntervalInMs = descriptor.getUpdateIntervalInMs();
232     cache.put(key, property, updateIntervalInMs, TimeUnit.MILLISECONDS);
233   }
234 
235   // CHECKSTYLE:OFF
236   public DescribedProperty getProperty(final ConfigurationProperties propertySource,
237       final PropertyDescriptor descriptor, final Object defaultValue)
238   // CHECKSTYLE:ON
239   {
240     readLock.lock();
241     boolean doReadUnlock = true;
242 
243     final String key = descriptor.getKey().toString();
244     try
245     {
246       DescribedProperty property = cache.get(key);
247       if (property != null)
248       {
249         LOG.debug("Cache hit: {}", key);
250         return property;
251       }
252       else
253       {
254         readLock.unlock();
255         doReadUnlock = false;
256 
257         property = propertySource.getProperty(key, defaultValue);
258 
259         if (property != null)
260         {
261           writeLock.lock();
262           try
263           {
264             putToCache(descriptor, key, property);
265             if (property instanceof ValidatedProperty)
266             {
267               attachDependencies((ValidatedProperty) property);
268             }
269           }
270           finally
271           {
272             writeLock.unlock();
273           }
274         }
275 
276         LOG.debug("Cache miss: {}", key);
277         return property;
278       }
279     }
280     finally
281     {
282       if (doReadUnlock)
283       {
284         readLock.unlock();
285       }
286     }
287   }
288 
289   public Property removeFromCache(final String key)
290   {
291     writeLock.lock();
292     try
293     {
294       final Property oldProperty = cache.remove(key);
295       removeDependencies(key);
296       LOG.debug("Cache revoke: {}", key);
297       return oldProperty;
298     }
299     finally
300     {
301       writeLock.unlock();
302     }
303   }
304 
305   public void removeFromCache(final Properties properties)
306   {
307     writeLock.lock();
308     try
309     {
310       for (final Object key : properties.keySet())
311       {
312         cache.remove((String) key);
313         removeDependencies(ObjectUtils.toString(key, null));
314         LOG.debug("Cache revoke due to init: {}", key);
315       }
316     }
317     finally
318     {
319       writeLock.unlock();
320     }
321   }
322 
323   public void removeFromCache(final PropertyCollection collection)
324   {
325     writeLock.lock();
326     try
327     {
328       for (final Property property : collection)
329       {
330         final String key = property.getName();
331         cache.remove(key);
332         removeDependencies(ObjectUtils.toString(key, null));
333         LOG.debug("Cache revoke due to init: {}", key);
334       }
335     }
336     finally
337     {
338       collection.close();
339       writeLock.unlock();
340     }
341   }
342 
343   /**
344    * It is assumed that the caller already holds the write lock.
345    */
346   private void attachDependencies(final ValidatedProperty property)
347   {
348     final String key = property.getName();
349 
350     for (final String dependency : property.getDependencies())
351     {
352       dependencies.put(dependency, key);
353     }
354   }
355 
356   /**
357    * It is assumed that the caller already holds the write lock.
358    */
359   private void removeDependencies(final String key)
360   {
361     for (final String dependant : dependencies.removeAll(key))
362     {
363       LOG.debug("Cache revoke due to dependency on {}: {}", key, dependant);
364       removeFromCache(dependant);
365     }
366   }
367 
368   // --- object basics --------------------------------------------------------
369 
370 }