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    }