Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
DependencyTrackingCache |
|
|
2.375;2,375 |
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 | 0 | 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 | 0 | private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); |
92 | ||
93 | /** |
|
94 | * The read lock for synchronized access. |
|
95 | * |
|
96 | * @serial |
|
97 | */ |
|
98 | 0 | private final Lock readLock = lock.readLock(); |
99 | ||
100 | /** |
|
101 | * The write lock for synchronized access. |
|
102 | * |
|
103 | * @serial |
|
104 | */ |
|
105 | 0 | 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 | 0 | { |
117 | 0 | this.cache = |
118 | new ReferenceMap(AbstractReferenceMap.SOFT, AbstractReferenceMap.HARD, |
|
119 | true); |
|
120 | 0 | this.dependencies = HashMultimap.create(); |
121 | 0 | } |
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 | 0 | readLock.lock(); |
135 | try |
|
136 | { |
|
137 | 0 | cacheSize = cache.size(); |
138 | } |
|
139 | finally |
|
140 | { |
|
141 | 0 | readLock.unlock(); |
142 | 0 | } |
143 | ||
144 | 0 | 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 | 0 | readLock.lock(); |
155 | ||
156 | try |
|
157 | { |
|
158 | 0 | final Property property = cache.get(key); |
159 | 0 | if (property instanceof ResolvedProperty) |
160 | { |
|
161 | 0 | LOG.debug("Cache hit: {}", key); |
162 | 0 | return (ResolvedProperty) property; |
163 | } |
|
164 | else |
|
165 | { |
|
166 | 0 | readLock.unlock(); |
167 | ||
168 | 0 | final ResolvedProperty resolvedProperty = |
169 | propertySource.getResolvedProperty(key, defaultValue); |
|
170 | ||
171 | 0 | writeLock.lock(); |
172 | try |
|
173 | { |
|
174 | 0 | cache.put(key, resolvedProperty); |
175 | 0 | attachDependencies(resolvedProperty); |
176 | } |
|
177 | finally |
|
178 | { |
|
179 | 0 | writeLock.unlock(); |
180 | 0 | readLock.lock(); |
181 | 0 | } |
182 | ||
183 | 0 | LOG.debug("Cache miss: {}", key); |
184 | 0 | return resolvedProperty; |
185 | } |
|
186 | } |
|
187 | finally |
|
188 | { |
|
189 | 0 | readLock.unlock(); |
190 | } |
|
191 | } |
|
192 | ||
193 | public Property getProperty(final ConfigurationProperties propertySource, |
|
194 | final String key, final Object defaultValue) |
|
195 | { |
|
196 | 0 | readLock.lock(); |
197 | ||
198 | try |
|
199 | { |
|
200 | 0 | Property property = cache.get(key); |
201 | 0 | if (property != null) |
202 | { |
|
203 | 0 | LOG.debug("Cache hit: {}", key); |
204 | 0 | return property; |
205 | } |
|
206 | else |
|
207 | { |
|
208 | 0 | readLock.unlock(); |
209 | ||
210 | 0 | property = propertySource.getProperty(key, defaultValue); |
211 | ||
212 | 0 | writeLock.lock(); |
213 | try |
|
214 | { |
|
215 | 0 | cache.put(key, property); |
216 | 0 | if (property instanceof ResolvedProperty) |
217 | { |
|
218 | 0 | attachDependencies((ResolvedProperty) property); |
219 | } |
|
220 | } |
|
221 | finally |
|
222 | { |
|
223 | 0 | writeLock.unlock(); |
224 | 0 | readLock.lock(); |
225 | 0 | } |
226 | ||
227 | 0 | LOG.debug("Cache miss: {}", key); |
228 | 0 | return property; |
229 | } |
|
230 | } |
|
231 | finally |
|
232 | { |
|
233 | 0 | readLock.unlock(); |
234 | } |
|
235 | } |
|
236 | ||
237 | public Property removeFromCache(final String key) |
|
238 | { |
|
239 | 0 | writeLock.lock(); |
240 | try |
|
241 | { |
|
242 | 0 | final Property oldProperty = cache.remove(key); |
243 | 0 | removeDependencies(key); |
244 | 0 | LOG.debug("Cache revoke: {}", key); |
245 | 0 | return oldProperty; |
246 | } |
|
247 | finally |
|
248 | { |
|
249 | 0 | writeLock.unlock(); |
250 | } |
|
251 | } |
|
252 | ||
253 | public void removeFromCache(final Properties properties) |
|
254 | { |
|
255 | 0 | writeLock.lock(); |
256 | try |
|
257 | { |
|
258 | 0 | for (final Object key : properties.keySet()) |
259 | { |
|
260 | 0 | cache.remove(key); |
261 | 0 | removeDependencies(ObjectUtils.toString(key, null)); |
262 | 0 | LOG.debug("Cache revoke due to init: {}", key); |
263 | } |
|
264 | } |
|
265 | finally |
|
266 | { |
|
267 | 0 | writeLock.unlock(); |
268 | 0 | } |
269 | 0 | } |
270 | ||
271 | /** |
|
272 | * It is assumed that the caller already holds the write lock. |
|
273 | */ |
|
274 | private void attachDependencies(final ResolvedProperty property) |
|
275 | { |
|
276 | 0 | final String key = property.getName(); |
277 | ||
278 | 0 | for (final String dependency : property.getDependencies()) |
279 | { |
|
280 | 0 | dependencies.put(dependency, key); |
281 | } |
|
282 | 0 | } |
283 | ||
284 | /** |
|
285 | * It is assumed that the caller already holds the write lock. |
|
286 | */ |
|
287 | private void removeDependencies(final String key) |
|
288 | { |
|
289 | 0 | for (final String dependant : dependencies.removeAll(key)) |
290 | { |
|
291 | 0 | LOG.debug("Cache revoke due to dependency on {}: {}", key, dependant); |
292 | 0 | removeFromCache(dependant); |
293 | } |
|
294 | 0 | } |
295 | ||
296 | // --- object basics -------------------------------------------------------- |
|
297 | ||
298 | } |