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