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.Map;
20  import java.util.Properties;
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.collections.map.AbstractReferenceMap;
27  import org.apache.commons.collections.map.ReferenceMap;
28  import org.apache.commons.lang.ObjectUtils;
29  import org.slf4j.Logger;
30  import org.slf4j.LoggerFactory;
31  
32  import com.google.common.collect.HashMultimap;
33  import com.google.common.collect.Multimap;
34  
35  import de.smartics.properties.api.config.domain.ConfigurationProperties;
36  import de.smartics.properties.api.config.domain.Property;
37  import de.smartics.properties.api.config.domain.ResolvedProperty;
38  import de.smartics.properties.api.config.domain.UnknownPropertyException;
39  import de.smartics.properties.api.core.domain.PropertyValidationException;
40  
41  /**
42   * A cache implementation that tracks dependencies via placeholder contained in
43   * property values and property default expressions. If a property is
44   * invalidated, all properties that refer to this property (even transitively)
45   * will also be invalidated. Invalidation implies, that the property is revoked
46   * from the cache.
47   */
48  @ThreadSafe
49  final class DependencyTrackingCache implements Serializable
50  {
51    // ********************************* Fields *********************************
52  
53    // --- constants ------------------------------------------------------------
54  
55    /**
56     * The class version identifier.
57     */
58    private static final long serialVersionUID = 1L;
59  
60    /**
61     * Reference to the logger for this class.
62     */
63    private static final Logger LOG = LoggerFactory
64        .getLogger(DependencyTrackingCache.class);
65  
66    // --- members --------------------------------------------------------------
67  
68    /**
69     * The synchronized cache. The property stored may be a property or a resolved
70     * property.
71     *
72     * @serial
73     */
74    private final Map<String, Property> cache;
75  
76    /**
77     * The dependencies of key to cascade the revoking of elements from the cache.
78     * The key is a property name the values depend upon. If the property with the
79     * given name is revoked from the cache, all the dependent properties of its
80     * list must also be revoked.
81     *
82     * @serial
83     */
84    private final Multimap<String, String> dependencies;
85  
86    /**
87     * The lock for synchronized access.
88     *
89     * @serial
90     */
91    private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
92  
93    /**
94     * The read lock for synchronized access.
95     *
96     * @serial
97     */
98    private final Lock readLock = lock.readLock();
99  
100   /**
101    * The write lock for synchronized access.
102    *
103    * @serial
104    */
105   private final Lock writeLock = lock.writeLock();
106 
107   // ****************************** Initializer *******************************
108 
109   // ****************************** Constructors ******************************
110 
111   /**
112    * Default constructor.
113    */
114   @SuppressWarnings("unchecked")
115   public DependencyTrackingCache()
116   {
117     this.cache =
118         new ReferenceMap(AbstractReferenceMap.SOFT, AbstractReferenceMap.HARD,
119             true);
120     this.dependencies = HashMultimap.create();
121   }
122 
123   // ****************************** Inner Classes *****************************
124 
125   // ********************************* Methods ********************************
126 
127   // --- init -----------------------------------------------------------------
128 
129   // --- get&set --------------------------------------------------------------
130 
131   public int getCacheSize()
132   {
133     final int cacheSize;
134     readLock.lock();
135     try
136     {
137       cacheSize = cache.size();
138     }
139     finally
140     {
141       readLock.unlock();
142     }
143 
144     return cacheSize;
145   }
146 
147   // --- business -------------------------------------------------------------
148 
149   public ResolvedProperty getResolvedProperty(
150       final ConfigurationProperties propertySource, final String key,
151       final Object defaultValue) throws IllegalArgumentException,
152     UnknownPropertyException, PropertyValidationException
153   {
154     readLock.lock();
155 
156     try
157     {
158       final Property property = cache.get(key);
159       if (property instanceof ResolvedProperty)
160       {
161         LOG.debug("Cache hit: {}", key);
162         return (ResolvedProperty) property;
163       }
164       else
165       {
166         readLock.unlock();
167 
168         final ResolvedProperty resolvedProperty =
169             propertySource.getResolvedProperty(key, defaultValue);
170 
171         writeLock.lock();
172         try
173         {
174           cache.put(key, resolvedProperty);
175           attachDependencies(resolvedProperty);
176         }
177         finally
178         {
179           writeLock.unlock();
180           readLock.lock();
181         }
182 
183         LOG.debug("Cache miss: {}", key);
184         return resolvedProperty;
185       }
186     }
187     finally
188     {
189       readLock.unlock();
190     }
191   }
192 
193   public Property getProperty(final ConfigurationProperties propertySource,
194       final String key, final Object defaultValue)
195   {
196     readLock.lock();
197 
198     try
199     {
200       Property property = cache.get(key);
201       if (property != null)
202       {
203         LOG.debug("Cache hit: {}", key);
204         return property;
205       }
206       else
207       {
208         readLock.unlock();
209 
210         property = propertySource.getProperty(key, defaultValue);
211 
212         writeLock.lock();
213         try
214         {
215           cache.put(key, property);
216           if (property instanceof ResolvedProperty)
217           {
218             attachDependencies((ResolvedProperty) property);
219           }
220         }
221         finally
222         {
223           writeLock.unlock();
224           readLock.lock();
225         }
226 
227         LOG.debug("Cache miss: {}", key);
228         return property;
229       }
230     }
231     finally
232     {
233       readLock.unlock();
234     }
235   }
236 
237   public Property removeFromCache(final String key)
238   {
239     writeLock.lock();
240     try
241     {
242       final Property oldProperty = cache.remove(key);
243       removeDependencies(key);
244       LOG.debug("Cache revoke: {}", key);
245       return oldProperty;
246     }
247     finally
248     {
249       writeLock.unlock();
250     }
251   }
252 
253   public void removeFromCache(final Properties properties)
254   {
255     writeLock.lock();
256     try
257     {
258       for (final Object key : properties.keySet())
259       {
260         cache.remove(key);
261         removeDependencies(ObjectUtils.toString(key, null));
262         LOG.debug("Cache revoke due to init: {}", key);
263       }
264     }
265     finally
266     {
267       writeLock.unlock();
268     }
269   }
270 
271   /**
272    * It is assumed that the caller already holds the write lock.
273    */
274   private void attachDependencies(final ResolvedProperty property)
275   {
276     final String key = property.getName();
277 
278     for (final String dependency : property.getDependencies())
279     {
280       dependencies.put(dependency, key);
281     }
282   }
283 
284   /**
285    * It is assumed that the caller already holds the write lock.
286    */
287   private void removeDependencies(final String key)
288   {
289     for (final String dependant : dependencies.removeAll(key))
290     {
291       LOG.debug("Cache revoke due to dependency on {}: {}", key, dependant);
292       removeFromCache(dependant);
293     }
294   }
295 
296   // --- object basics --------------------------------------------------------
297 
298 }