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 * https://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, for example 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 * {@inheritDoc} This implementation returns a copy of the current node model with the same settings. However, it has to
120 * be ensured that the track count for the node selector is increased.
121 *
122 * @return the node model for the clone
123 */
124 @Override
125 protected NodeModel<ImmutableNode> cloneNodeModel() {
126 final InMemoryNodeModel parentModel = (InMemoryNodeModel) getParent().getModel();
127 parentModel.trackNode(getRootSelector(), getParent());
128 return new TrackedNodeModel(getParent(), getRootSelector(), true);
129 }
130
131 /**
132 * Closes this sub configuration. This method closes the underlying {@link TrackedNodeModel}, thus causing the tracked
133 * node acting as root node to be released. Per default, this happens automatically when the model is claimed by the
134 * garbage collector. By calling this method explicitly, it can be indicated that this configuration is no longer used
135 * and that resources used by it can be freed immediately.
136 */
137 public void close() {
138 getTrackedModel().close();
139 }
140
141 /**
142 * {@inheritDoc} This implementation makes sure that the correct node model (the one of the parent) is used for the new
143 * sub configuration.
144 */
145 @Override
146 protected SubnodeConfiguration createSubConfigurationForTrackedNode(final NodeSelector selector, final InMemoryNodeModelSupport parentModelSupport) {
147 return super.createSubConfigurationForTrackedNode(selector, getParent());
148 }
149
150 /**
151 * {@inheritDoc} This implementation returns a newly created node model with the correct root node set. Note that this
152 * model is not used for property access, but only made available to clients that need to operate on the node structure
153 * of this {@code SubnodeConfiguration}. Be aware that the implementation of this method is not very efficient.
154 */
155 @Override
156 public InMemoryNodeModel getNodeModel() {
157 final ImmutableNode root = getParent().getNodeModel().getTrackedNode(getRootSelector());
158 return new InMemoryNodeModel(root);
159 }
160
161 /**
162 * Gets the parent configuration of this subnode configuration.
163 *
164 * @return the parent configuration
165 */
166 public BaseHierarchicalConfiguration getParent() {
167 return parent;
168 }
169
170 /**
171 * Gets the node model of the root configuration. {@code SubnodeConfiguration} instances created from a hierarchical
172 * configuration operate on the same node model, using different nodes as their local root nodes. With this method the
173 * top-level node model can be obtained. It works even in constellations where a {@code SubnodeConfiguration} has been
174 * created from another {@code SubnodeConfiguration}.
175 *
176 * @return the root node model
177 * @since 2.2
178 */
179 public InMemoryNodeModel getRootNodeModel() {
180 if (getParent() instanceof SubnodeConfiguration) {
181 return ((SubnodeConfiguration) getParent()).getRootNodeModel();
182 }
183 return getParent().getNodeModel();
184 }
185
186 /**
187 * Gets the selector to the root node of this configuration.
188 *
189 * @return the {@code NodeSelector} to the root node
190 */
191 public NodeSelector getRootSelector() {
192 return rootSelector;
193 }
194
195 /**
196 * {@inheritDoc} This implementation returns a sub selector of the selector of this configuration.
197 */
198 @Override
199 protected NodeSelector getSubConfigurationNodeSelector(final String key) {
200 return getRootSelector().subSelector(key);
201 }
202
203 /**
204 * {@inheritDoc} This implementation returns the parent model of the {@link TrackedNodeModel} used by this
205 * configuration.
206 */
207 @Override
208 protected InMemoryNodeModel getSubConfigurationParentModel() {
209 return getTrackedModel().getParentModel();
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 }