KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > sape > carbon > core > config > cache > DoubleCheckConfigurationCache


1 /*
2  * The contents of this file are subject to the Sapient Public License
3  * Version 1.0 (the "License"); you may not use this file except in compliance
4  * with the License. You may obtain a copy of the License at
5  * http://carbon.sf.net/License.html.
6  *
7  * Software distributed under the License is distributed on an "AS IS" basis,
8  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
9  * the specific language governing rights and limitations under the License.
10  *
11  * The Original Code is The Carbon Component Framework.
12  *
13  * The Initial Developer of the Original Code is Sapient Corporation
14  *
15  * Copyright (C) 2003 Sapient Corporation. All Rights Reserved.
16  */

17
18 package org.sape.carbon.core.config.cache;
19
20 import java.util.HashMap JavaDoc;
21 import java.util.Map JavaDoc;
22
23 import org.sape.carbon.core.config.Configuration;
24 import org.sape.carbon.core.config.ConfigurationNotFoundException;
25 import org.sape.carbon.core.config.ConfigurationService;
26 import org.sape.carbon.core.config.format.ConfigurationFormatException;
27 import org.sape.carbon.core.config.node.ConfigurationDocument;
28 import org.sape.carbon.core.config.node.Node;
29 import org.sape.carbon.core.config.node.NodeIOException;
30 import org.sape.carbon.core.config.node.NodeNotFoundException;
31 import org.sape.carbon.core.config.node.event.NodeEventListener;
32 import org.sape.carbon.core.exception.ExceptionUtility;
33
34 import org.apache.commons.logging.Log;
35 import org.apache.commons.logging.LogFactory;
36
37
38 /**
39  * <p>
40  * This implementation uses the infamous double-check lock to minimize the
41  * synchronization required. Multi-threaded read access to the cache will
42  * not cause contention. Adding new configurations to the cache is synchronized.
43  * </p>
44  * <p>
45  * <b>Caution</b>: Double-check locking is not bulletproof. There is a small
46  * possibility when using optimizing compilers or multi-processor machines that
47  * stale configurations or invalid configurations are returned when
48  * multiple threads concurrently access the same configuration when that
49  * configuration is being loaded, changed, or removed.
50  * </p>
51  * <p>
52  * This implementation uses 2 maps to store configuration data, an active map
53  * and a background map. When the cache is modified, the background map is
54  * updated first. Then the maps are swapped making the background map the active
55  * one. Then the background (used to be active) is updated. This is done to
56  * make use of the fact that reference assignment is atomic and prevent map
57  * from being accessed while they are being updated.
58  * </p>
59  * <p>
60  * Configuration objects are cached lazily, i.e. they are cached after they
61  * are requested the first time. Once a configuration object is cached, the
62  * cache will listen for Node events on the configuration. If a node event
63  * occurs, the entire cache is cleared. This is done to prevent inconsistencies
64  * between this cache and caches internal to the configuration objects
65  * themselves in the case of nested configurations.
66  * </p>
67  *
68  * Copyright 2002 Sapient
69  * @since carbon 1.1
70  * @author Douglas Voet, Oct 24, 2002
71  * @version $Revision: 1.10 $($Author: dvoet $ / $Date: 2003/05/05 21:21:16 $)
72  */

73 public class DoubleCheckConfigurationCache
74     implements ConfigurationCache, NodeEventListener {
75
76     /**
77      * Provides a handle to Apache-commons logger
78      */

79     private Log log = LogFactory.getLog(this.getClass());
80
81     /** Active cache used to return value. */
82     private Map JavaDoc activeCache = new HashMap JavaDoc();
83
84     /** Background cache used for loading. */
85     private Map JavaDoc backgroundCache = new HashMap JavaDoc();
86
87     /** Holds the config service being cached. */
88     private ConfigurationService configService;
89
90     /** Holds the entrance count for re-entrant calls. */
91     private int entranceCount = 0;
92
93     /** Map of loaded configuration. */
94     private Map JavaDoc loadingConfigurations = new HashMap JavaDoc();
95
96     /**
97      * Constructs a new DoubleCheckConfigurationCache around the given
98      * service.
99      *
100      * @param configService service to provide a double-check cache for.
101      */

102     public DoubleCheckConfigurationCache(ConfigurationService configService) {
103         this.configService = configService;
104     }
105
106     /**
107      * Gets back a configuration with the given name. This will
108      * either return the configuration from the cache or load it
109      * if needed.
110      *
111      * @param name the name of the configuration to load
112      * @return configuration at the given name
113      * @throws ConfigurationNotFoundException indicates there is
114      * no configuration at the given name
115      */

116     public Configuration getConfiguration(String JavaDoc name) {
117         Configuration config = (Configuration) this.activeCache.get(name);
118         if (config == null) {
119             synchronized (this) {
120                 // ye old double check
121
config = (Configuration) this.activeCache.get(name);
122                 if (config == null) {
123                     // see if it has already been loaded
124
config =
125                         (Configuration) this.loadingConfigurations.get(name);
126
127                     if (config == null) {
128                         // ok no one loaded the config before we got the
129
// lock and the config was not loaded by this thread
130
// since we got the lock, go ahead and load the config
131

132                         // entranceCount makes sure that only the first
133
// entry into this method in any circular reference
134
// scenario actually activates the freshly loaded
135
// configurations
136
this.entranceCount++;
137                         try {
138                             config =
139                                 this.configService.fetchWritableConfiguration(
140                                     name);
141
142                             this.loadingConfigurations.put(name, config);
143                             config.setConfigurationReadOnly();
144                             this.configService.addNodeListener(name, this);
145
146                             if (this.entranceCount == 1) {
147                                 // this is the first entry
148
// do the following steps to activate all
149
// loaded configs for public consumption
150

151                                 // add the config to the background cache
152
this.backgroundCache.putAll(
153                                     this.loadingConfigurations);
154
155                                 // activate the background cache
156
activateBackgroundCache();
157
158                                 // add the config to the background cache
159
// (used to be the active) cache so both
160
// caches are in sync
161
this.backgroundCache.putAll(
162                                     this.loadingConfigurations);
163                             }
164
165                         } catch (NodeNotFoundException nnfe) {
166                             // Catch checked configuration exception and
167
// rethrow as runtime
168
throw new ConfigurationNotFoundException(
169                                 this.getClass(),
170                                 "The configuration ["
171                                     + name
172                                     + "] was not found",
173                                 nnfe);
174                         } finally {
175                             this.entranceCount--;
176
177                             if (this.entranceCount == 0) {
178                                 // cleanup, this is the first entry so we
179
// can't be loading any more configurations,
180
// clear any that are in loadingConfigurations
181
this.loadingConfigurations.clear();
182                             }
183                         }
184                     }
185                 }
186             }
187         }
188
189         return config;
190
191     }
192
193     /**
194      * Updates the configuration cache with the new instance of the node.
195      *
196      * @param changedNode the node that has been changed
197      */

198     public synchronized void nodeChanged(Node changedNode) {
199         try {
200             Configuration config =
201                 ((ConfigurationDocument) changedNode).readConfiguration();
202
203             // add the config to the background cache
204
this.backgroundCache.clear();
205             this.backgroundCache.put(config.getConfigurationName(), config);
206
207             // activate the background cache
208
activateBackgroundCache();
209
210             // add the config to the background cache (used to
211
// be the active) cache so both caches are in sync
212
this.backgroundCache.clear();
213             this.backgroundCache.put(config.getConfigurationName(), config);
214
215         } catch (NodeIOException nioe) {
216             if (log.isWarnEnabled()) {
217                 log.warn("Could not read new configuration: "
218                     + ExceptionUtility.printStackTracesToString(nioe));
219             }
220         } catch (ConfigurationFormatException cfe) {
221             if (log.isWarnEnabled()) {
222                 log.warn("Could not read new configuration: "
223                     + ExceptionUtility.printStackTracesToString(cfe));
224             }
225         }
226     }
227
228     /**
229      * Removes a given node from the cache.
230      *
231      * @param removedNodeName node to remove from the cache
232      */

233     public synchronized void nodeRemoved(String JavaDoc removedNodeName) {
234         // add the config to the background cache
235
this.backgroundCache.clear();
236         this.backgroundCache.remove(removedNodeName);
237
238         // activate the background cache
239
activateBackgroundCache();
240
241         // add the config to the background cache (used to
242
// be the active) cache so both caches are in sync
243
this.backgroundCache.clear();
244         this.backgroundCache.remove(removedNodeName);
245     }
246
247     /**
248      * Makes the background cache active and the active cache the background.
249      */

250     private void activateBackgroundCache() {
251         // swap the active cache and background cache
252
// making the background cache active
253
Map JavaDoc swapMap = this.activeCache;
254         this.activeCache = this.backgroundCache;
255         this.backgroundCache = swapMap;
256     }
257 }
258
Popular Tags