001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.commons.configuration;
018
019 import java.util.ArrayList;
020 import java.util.Collections;
021 import java.util.Iterator;
022 import java.util.List;
023
024 import org.apache.commons.configuration.interpol.ConfigurationInterpolator;
025 import org.apache.commons.configuration.tree.ConfigurationNode;
026 import org.apache.commons.configuration.reloading.Reloadable;
027
028 /**
029 * <p>
030 * A specialized hierarchical configuration class that wraps a single node of
031 * its parent configuration.
032 * </p>
033 * <p>
034 * Configurations of this type are initialized with a parent configuration and a
035 * configuration node of this configuration. This node becomes the root node of
036 * the subnode configuration. All property accessor methods are evaluated
037 * relative to this root node. A good use case for a
038 * <code>SubnodeConfiguration</code> is when multiple properties from a
039 * specific sub tree of the whole configuration need to be accessed. Then a
040 * <code>SubnodeConfiguration</code> can be created with the parent node of
041 * the affected sub tree as root node. This allows for simpler property keys and
042 * is also more efficient.
043 * </p>
044 * <p>
045 * A subnode configuration and its parent configuration operate on the same
046 * hierarchy of configuration nodes. So if modifications are performed at the
047 * subnode configuration, these changes are immideately visible in the parent
048 * configuration. Analogously will updates of the parent configuration affect
049 * the subnode configuration if the sub tree spanned by the subnode
050 * configuration's root node is involved.
051 * </p>
052 * <p>
053 * There are however changes at the parent configuration, which cause the
054 * subnode configuration to become detached. An example for such a change is a
055 * reload operation of a file-based configuration, which replaces all nodes of
056 * the parent configuration. The subnode configuration per default still
057 * references the old nodes. Another example are list structures: a subnode
058 * configuration can be created to point on the <em>i</em>th element of the
059 * list. Now list elements can be added or removed, so that the list elements'
060 * indices change. In such a scenario the subnode configuration would always
061 * point to the same list element, regardless of its current index.
062 * </p>
063 * <p>
064 * To solve these problems and make a subnode configuration aware of
065 * such structural changes of its parent, it is possible to associate a
066 * subnode configuration with a configuration key. This can be done by calling
067 * the <code>setSubnodeKey()</code> method. If here a key is set, the subnode
068 * configuration will evaluate it on each access, thus ensuring that it is
069 * always in sync with its parent. In this mode the subnode configuration really
070 * behaves like a live-view on its parent. The price for this is a decreased
071 * performance because now an additional evaluation has to be performed on each
072 * property access. So this mode should only be used if necessary; if for
073 * instance a subnode configuration is only used for a temporary convenient
074 * access to a complex configuration, there is no need to make it aware for
075 * structural changes of its parent. If a subnode configuration is created
076 * using the <code>{@link HierarchicalConfiguration#configurationAt(String, boolean)
077 * configurationAt()}</code> method of <code>HierarchicalConfiguration</code>
078 * (which should be the preferred way), with an additional boolean parameter it
079 * can be specified whether the resulting subnode configuration should be
080 * aware of structural changes or not. Then the configuration key will be
081 * automatically set.
082 * </p>
083 * <p>
084 * <em>Note:</em> At the moment support for creating a subnode configuration
085 * that is aware of structural changes of its parent from another subnode
086 * configuration (a "sub subnode configuration") is limited. This only works if
087 * <ol><li>the subnode configuration that serves as the parent for the new
088 * subnode configuration is itself associated with a configuration key and</li>
089 * <li>the key passed in to create the new subnode configuration is not too
090 * complex (if configuration keys are used that contain indices, a corresponding
091 * key that is valid from the parent configuration's point of view cannot be
092 * constructed).</li></ol>
093 * </p>
094 * <p>
095 * When a subnode configuration is created, it inherits the settings of its
096 * parent configuration, e.g. some flags like the
097 * <code>throwExceptionOnMissing</code> flag or the settings for handling list
098 * delimiters) or the expression engine. If these settings are changed later in
099 * either the subnode or the parent configuration, the changes are not visible
100 * for each other. So you could create a subnode configuration, change its
101 * expression engine without affecting the parent configuration.
102 * </p>
103 * <p>
104 * From its purpose this class is quite similar to
105 * <code>{@link SubsetConfiguration}</code>. The difference is that a subset
106 * configuration of a hierarchical configuration may combine multiple
107 * configuration nodes from different sub trees of the configuration, while all
108 * nodes in a subnode configuration belong to the same sub tree. If an
109 * application can live with this limitation, it is recommended to use this
110 * class instead of <code>SubsetConfiguration</code> because creating a subset
111 * configuration is more expensive than creating a subnode configuration.
112 * </p>
113 *
114 * @since 1.3
115 * @author Oliver Heger
116 * @version $Id: SubnodeConfiguration.java 823891 2009-10-10 17:17:44Z rgoers $
117 */
118 public class SubnodeConfiguration extends HierarchicalReloadableConfiguration
119 {
120 /**
121 * The serial version UID.
122 */
123 private static final long serialVersionUID = 3105734147019386480L;
124
125 /** Stores the parent configuration. */
126 private HierarchicalConfiguration parent;
127
128 /** Stores the key that was used to construct this configuration.*/
129 private String subnodeKey;
130
131 /**
132 * Creates a new instance of <code>SubnodeConfiguration</code> and
133 * initializes it with the parent configuration and the new root node.
134 *
135 * @param parent the parent configuration
136 * @param root the root node of this subnode configuration
137 */
138 public SubnodeConfiguration(HierarchicalConfiguration parent, ConfigurationNode root)
139 {
140 super(parent instanceof Reloadable ? ((Reloadable) parent).getReloadLock() : null);
141 if (parent == null)
142 {
143 throw new IllegalArgumentException(
144 "Parent configuration must not be null!");
145 }
146 if (root == null)
147 {
148 throw new IllegalArgumentException("Root node must not be null!");
149 }
150
151 setRootNode(root);
152 this.parent = parent;
153 initFromParent(parent);
154 }
155
156 /**
157 * Returns the parent configuration of this subnode configuration.
158 *
159 * @return the parent configuration
160 */
161 public HierarchicalConfiguration getParent()
162 {
163 return parent;
164 }
165
166 /**
167 * Returns the key that was used to construct this configuration. If here a
168 * non-<b>null</b> value is returned, the subnode configuration will
169 * always check its parent for structural changes and reconstruct itself if
170 * necessary.
171 *
172 * @return the key for selecting this configuration's root node
173 * @since 1.5
174 */
175 public String getSubnodeKey()
176 {
177 return subnodeKey;
178 }
179
180 /**
181 * Sets the key to the root node of this subnode configuration. If here a
182 * key is set, the subnode configuration will behave like a live-view on its
183 * parent for this key. See the class comment for more details.
184 *
185 * @param subnodeKey the key used to construct this configuration
186 * @since 1.5
187 */
188 public void setSubnodeKey(String subnodeKey)
189 {
190 this.subnodeKey = subnodeKey;
191 }
192
193 /**
194 * Returns the root node for this configuration. If a subnode key is set,
195 * this implementation re-evaluates this key to find out if this subnode
196 * configuration needs to be reconstructed. This ensures that the subnode
197 * configuration is always synchronized with its parent configuration.
198 *
199 * @return the root node of this configuration
200 * @since 1.5
201 * @see #setSubnodeKey(String)
202 */
203 public ConfigurationNode getRootNode()
204 {
205 if (getSubnodeKey() != null)
206 {
207 try
208 {
209 List nodes = getParent().fetchNodeList(getSubnodeKey());
210 if (nodes.size() != 1)
211 {
212 // key is invalid, so detach this subnode configuration
213 setSubnodeKey(null);
214 }
215 else
216 {
217 ConfigurationNode currentRoot = (ConfigurationNode) nodes
218 .get(0);
219 if (currentRoot != super.getRootNode())
220 {
221 // the root node was changed due to a change of the
222 // parent
223 fireEvent(EVENT_SUBNODE_CHANGED, null, null, true);
224 setRootNode(currentRoot);
225 fireEvent(EVENT_SUBNODE_CHANGED, null, null, false);
226 }
227 return currentRoot;
228 }
229 }
230 catch (Exception ex)
231 {
232 // Evaluation of the key caused an exception. Probably the
233 // expression engine has changed on the parent. Detach this
234 // configuration, there is not much we can do about this.
235 setSubnodeKey(null);
236 }
237 }
238
239 return super.getRootNode(); // use stored root node
240 }
241
242 /**
243 * Returns a hierarchical configuration object for the given sub node.
244 * This implementation will ensure that the returned
245 * <code>SubnodeConfiguration</code> object will have the same parent than
246 * this object.
247 *
248 * @param node the sub node, for which the configuration is to be created
249 * @return a hierarchical configuration for this sub node
250 */
251 protected SubnodeConfiguration createSubnodeConfiguration(ConfigurationNode node)
252 {
253 SubnodeConfiguration result = new SubnodeConfiguration(getParent(), node);
254 getParent().registerSubnodeConfiguration(result);
255 return result;
256 }
257
258 /**
259 * Returns a hierarchical configuration object for the given sub node that
260 * is aware of structural changes of its parent. Works like the method with
261 * the same name, but also sets the subnode key for the new subnode
262 * configuration, so it can check whether the parent has been changed. This
263 * only works if this subnode configuration has itself a valid subnode key.
264 * So if a subnode configuration that should be aware of structural changes
265 * is created from an already existing subnode configuration, this subnode
266 * configuration must also be aware of such changes.
267 *
268 * @param node the sub node, for which the configuration is to be created
269 * @param subnodeKey the construction key
270 * @return a hierarchical configuration for this sub node
271 * @since 1.5
272 */
273 protected SubnodeConfiguration createSubnodeConfiguration(
274 ConfigurationNode node, String subnodeKey)
275 {
276 SubnodeConfiguration result = createSubnodeConfiguration(node);
277
278 if (getSubnodeKey() != null)
279 {
280 // construct the correct subnode key
281 // determine path to root node
282 List lstPathToRoot = new ArrayList();
283 ConfigurationNode top = super.getRootNode();
284 ConfigurationNode nd = node;
285 while (nd != top)
286 {
287 lstPathToRoot.add(nd);
288 nd = nd.getParentNode();
289 }
290
291 // construct the keys for the nodes on this path
292 Collections.reverse(lstPathToRoot);
293 String key = getSubnodeKey();
294 for (Iterator it = lstPathToRoot.iterator(); it.hasNext();)
295 {
296 key = getParent().getExpressionEngine().nodeKey(
297 (ConfigurationNode) it.next(), key);
298 }
299 result.setSubnodeKey(key);
300 }
301
302 return result;
303 }
304
305 /**
306 * Creates a new node. This task is delegated to the parent.
307 *
308 * @param name the node's name
309 * @return the new node
310 */
311 protected Node createNode(String name)
312 {
313 return getParent().createNode(name);
314 }
315
316 /**
317 * Initializes this subnode configuration from the given parent
318 * configuration. This method is called by the constructor. It will copy
319 * many settings from the parent.
320 *
321 * @param parentConfig the parent configuration
322 */
323 protected void initFromParent(HierarchicalConfiguration parentConfig)
324 {
325 setExpressionEngine(parentConfig.getExpressionEngine());
326 setListDelimiter(parentConfig.getListDelimiter());
327 setDelimiterParsingDisabled(parentConfig.isDelimiterParsingDisabled());
328 setThrowExceptionOnMissing(parentConfig.isThrowExceptionOnMissing());
329 }
330
331 /**
332 * Creates a ConfigurationInterpolator with a chain to the parent's
333 * interpolator.
334 *
335 * @return the new interpolator
336 */
337 protected ConfigurationInterpolator createInterpolator()
338 {
339 ConfigurationInterpolator interpolator = super.createInterpolator();
340 interpolator.setParentInterpolator(getParent().getInterpolator());
341 return interpolator;
342 }
343 }