View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.configuration2;
18  
19  import org.apache.commons.configuration2.tree.ImmutableNode;
20  import org.apache.commons.configuration2.tree.InMemoryNodeModel;
21  import org.apache.commons.configuration2.tree.InMemoryNodeModelSupport;
22  import org.apache.commons.configuration2.tree.NodeModel;
23  import org.apache.commons.configuration2.tree.NodeSelector;
24  import org.apache.commons.configuration2.tree.TrackedNodeModel;
25  
26  /**
27   * <p>
28   * A specialized hierarchical configuration class with a node model that uses a tracked node of another node model as
29   * its root node.
30   * </p>
31   * <p>
32   * Configurations of this type are initialized with a special {@link NodeModel} operating on a specific tracked node of
33   * the parent configuration and the corresponding {@link NodeSelector}. All property accessor methods are evaluated
34   * relative to this root node. A good use case for a {@code SubnodeConfiguration} is when multiple properties from a
35   * specific sub tree of the whole configuration need to be accessed. Then a {@code SubnodeConfiguration} can be created
36   * with the parent node of the affected sub tree as root node. This allows for simpler property keys and is also more
37   * efficient.
38   * </p>
39   * <p>
40   * By making use of a tracked node as root node, a {@code SubnodeConfiguration} and its parent configuration initially
41   * operate on the same hierarchy of configuration nodes. So if modifications are performed at the subnode configuration,
42   * these changes are immediately visible in the parent configuration. Analogously will updates of the parent
43   * configuration affect the {@code SubnodeConfiguration} if the sub tree spanned by the {@code SubnodeConfiguration}'s
44   * root node is involved.
45   * </p>
46   * <p>
47   * Note that by making use of a {@code NodeSelector} the {@code SubnodeConfiguration} is not associated with a physical
48   * node instance, but the selection criteria stored in the selector are evaluated after each change of the nodes
49   * structure. As an example consider that the selector uses a key with an index into a list element, say index 2. Now if
50   * an update occurs on the underlying nodes structure which removes the first element in this list structure, the
51   * {@code SubnodeConfiguration} still references the element with index 2 which is now another one.
52   * </p>
53   * <p>
54   * There are also possible changes of the underlying nodes structure which completely detach the
55   * {@code SubnodeConfiguration} from its parent configuration. For instance, the key referenced by the
56   * {@code SubnodeConfiguration} could be removed in the parent configuration. If this happens, the
57   * {@code SubnodeConfiguration} stays functional; however, it now operates on a separate node model than its parent
58   * configuration. Changes made by one configuration are no longer visible for the other one (as the node models have no
59   * longer overlapping nodes, there is no way to have a synchronization here).
60   * </p>
61   * <p>
62   * When a subnode configuration is created, it inherits the settings of its parent configuration, e.g. some flags like
63   * the {@code throwExceptionOnMissing} flag or the settings for handling list delimiters) or the expression engine. If
64   * these settings are changed later in either the subnode or the parent configuration, the changes are not visible for
65   * each other. So you could create a subnode configuration, and change its expression engine without affecting the
66   * parent configuration.
67   * </p>
68   * <p>
69   * Because the {@code SubnodeConfiguration} operates on the same nodes structure as its parent it uses the same
70   * {@code Synchronizer} instance per default. This means that locks held on one {@code SubnodeConfiguration} also impact
71   * the parent configuration and all of its other {@code SubnodeConfiguration} objects. You should not change this
72   * without a good reason! Otherwise, there is the risk of data corruption when multiple threads access these
73   * configuration concurrently.
74   * </p>
75   * <p>
76   * From its purpose this class is quite similar to {@link SubsetConfiguration}. The difference is that a subset
77   * configuration of a hierarchical configuration may combine multiple configuration nodes from different sub trees of
78   * the configuration, while all nodes in a subnode configuration belong to the same sub tree. If an application can live
79   * with this limitation, it is recommended to use this class instead of {@code SubsetConfiguration} because creating a
80   * subset configuration is more expensive than creating a subnode configuration.
81   * </p>
82   * <p>
83   * It is strongly recommended to create {@code SubnodeConfiguration} instances only through the
84   * {@code configurationAt()} methods of a hierarchical configuration. These methods ensure that all necessary
85   * initializations are done. Creating instances manually without doing proper initialization may break some of the
86   * functionality provided by this class.
87   * </p>
88   *
89   * @since 1.3
90   */
91  public class SubnodeConfiguration extends BaseHierarchicalConfiguration {
92      /** Stores the parent configuration. */
93      private final BaseHierarchicalConfiguration parent;
94  
95      /** The node selector selecting the root node of this configuration. */
96      private final NodeSelector rootSelector;
97  
98      /**
99       * Creates a new instance of {@code SubnodeConfiguration} and initializes it with all relevant properties.
100      *
101      * @param parent the parent configuration
102      * @param model the {@code TrackedNodeModel} to be used for this configuration
103      * @throws IllegalArgumentException if a required argument is missing
104      */
105     public SubnodeConfiguration(final BaseHierarchicalConfiguration parent, final TrackedNodeModel model) {
106         super(model);
107         if (parent == null) {
108             throw new IllegalArgumentException("Parent configuration must not be null!");
109         }
110         if (model == null) {
111             throw new IllegalArgumentException("Node model must not be null!");
112         }
113 
114         this.parent = parent;
115         rootSelector = model.getSelector();
116     }
117 
118     /**
119      * Gets the parent configuration of this subnode configuration.
120      *
121      * @return the parent configuration
122      */
123     public BaseHierarchicalConfiguration getParent() {
124         return parent;
125     }
126 
127     /**
128      * Gets the selector to the root node of this configuration.
129      *
130      * @return the {@code NodeSelector} to the root node
131      */
132     public NodeSelector getRootSelector() {
133         return rootSelector;
134     }
135 
136     /**
137      * Closes this sub configuration. This method closes the underlying {@link TrackedNodeModel}, thus causing the tracked
138      * node acting as root node to be released. Per default, this happens automatically when the model is claimed by the
139      * garbage collector. By calling this method explicitly, it can be indicated that this configuration is no longer used
140      * and that resources used by it can be freed immediately.
141      */
142     public void close() {
143         getTrackedModel().close();
144     }
145 
146     /**
147      * {@inheritDoc} This implementation returns a newly created node model with the correct root node set. Note that this
148      * model is not used for property access, but only made available to clients that need to operate on the node structure
149      * of this {@code SubnodeConfiguration}. Be aware that the implementation of this method is not very efficient.
150      */
151     @Override
152     public InMemoryNodeModel getNodeModel() {
153         final ImmutableNode root = getParent().getNodeModel().getTrackedNode(getRootSelector());
154         return new InMemoryNodeModel(root);
155     }
156 
157     /**
158      * Gets the node model of the root configuration. {@code SubnodeConfiguration} instances created from a hierarchical
159      * configuration operate on the same node model, using different nodes as their local root nodes. With this method the
160      * top-level node model can be obtained. It works even in constellations where a {@code SubnodeConfiguration} has been
161      * created from another {@code SubnodeConfiguration}.
162      *
163      * @return the root node model
164      * @since 2.2
165      */
166     public InMemoryNodeModel getRootNodeModel() {
167         if (getParent() instanceof SubnodeConfiguration) {
168             return ((SubnodeConfiguration) getParent()).getRootNodeModel();
169         }
170         return getParent().getNodeModel();
171     }
172 
173     /**
174      * {@inheritDoc} This implementation returns a copy of the current node model with the same settings. However, it has to
175      * be ensured that the track count for the node selector is increased.
176      *
177      * @return the node model for the clone
178      */
179     @Override
180     protected NodeModel<ImmutableNode> cloneNodeModel() {
181         final InMemoryNodeModel parentModel = (InMemoryNodeModel) getParent().getModel();
182         parentModel.trackNode(getRootSelector(), getParent());
183         return new TrackedNodeModel(getParent(), getRootSelector(), true);
184     }
185 
186     /**
187      * {@inheritDoc} This implementation returns a sub selector of the selector of this configuration.
188      */
189     @Override
190     protected NodeSelector getSubConfigurationNodeSelector(final String key) {
191         return getRootSelector().subSelector(key);
192     }
193 
194     /**
195      * {@inheritDoc} This implementation returns the parent model of the {@link TrackedNodeModel} used by this
196      * configuration.
197      */
198     @Override
199     protected InMemoryNodeModel getSubConfigurationParentModel() {
200         return getTrackedModel().getParentModel();
201     }
202 
203     /**
204      * {@inheritDoc} This implementation makes sure that the correct node model (the one of the parent) is used for the new
205      * sub configuration.
206      */
207     @Override
208     protected SubnodeConfiguration createSubConfigurationForTrackedNode(final NodeSelector selector, final InMemoryNodeModelSupport parentModelSupport) {
209         return super.createSubConfigurationForTrackedNode(selector, getParent());
210     }
211 
212     /**
213      * Convenience method that returns the tracked model used by this sub configuration.
214      *
215      * @return the {@code TrackedNodeModel}
216      */
217     private TrackedNodeModel getTrackedModel() {
218         return (TrackedNodeModel) getModel();
219     }
220 }