001 /* $Id: PluginRules.java 992104 2010-09-02 20:24:31Z simonetripodi $
002 *
003 * Licensed to the Apache Software Foundation (ASF) under one or more
004 * contributor license agreements. See the NOTICE file distributed with
005 * this work for additional information regarding copyright ownership.
006 * The ASF licenses this file to You under the Apache License, Version 2.0
007 * (the "License"); you may not use this file except in compliance with
008 * the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018 package org.apache.commons.digester.plugins;
019
020 import java.util.List;
021
022 import org.apache.commons.digester.Digester;
023 import org.apache.commons.digester.Rule;
024 import org.apache.commons.digester.Rules;
025 import org.apache.commons.digester.RulesBase;
026 import org.apache.commons.logging.Log;
027
028 /**
029 * A custom digester Rules manager which must be used as the Rules object
030 * when using the plugins module functionality.
031 * <p>
032 * During parsing, a linked list of PluginCreateRule instances develop, and
033 * this list also acts like a stack. The original instance that was set before
034 * the Digester started parsing is always at the tail of the list, and the
035 * Digester always holds a reference to the instance at the head of the list
036 * in the rules member. Initially, this list/stack holds just one instance,
037 * ie head and tail are the same object.
038 * <p>
039 * When the start of an xml element causes a PluginCreateRule to fire, a new
040 * PluginRules instance is created and inserted at the head of the list (ie
041 * pushed onto the stack of Rules objects). Digester.getRules() therefore
042 * returns this new Rules object, and any custom rules associated with that
043 * plugin are added to that instance.
044 * <p>
045 * When the end of the xml element is encountered (and therefore the
046 * PluginCreateRule end method fires), the stack of Rules objects is popped,
047 * so that Digester.getRules returns the previous Rules object.
048 *
049 * @since 1.6
050 */
051
052 public class PluginRules implements Rules {
053
054 /**
055 * The Digester instance with which this Rules instance is associated.
056 */
057 protected Digester digester = null;
058
059 /**
060 * The (optional) object which generates new rules instances.
061 */
062 private RulesFactory rulesFactory;
063
064 /**
065 * The rules implementation that we are "enhancing" with plugins
066 * functionality, as per the Decorator pattern.
067 */
068 private Rules decoratedRules;
069
070 /** Object which contains information about all known plugins. */
071 private PluginManager pluginManager;
072
073 /**
074 * The path below which this rules object has responsibility.
075 * For paths shorter than or equal the mountpoint, the parent's
076 * match is called.
077 */
078 private String mountPoint = null;
079
080 /**
081 * The Rules object that holds rules applying "above" the mountpoint,
082 * ie the next Rules object down in the stack.
083 */
084 private PluginRules parent = null;
085
086 /**
087 * A reference to the object that holds all data which should only
088 * exist once per digester instance.
089 */
090 private PluginContext pluginContext = null;
091
092 // ------------------------------------------------------------- Constructor
093
094 /**
095 * Constructor for top-level Rules objects. Exactly one of these must
096 * be created and installed into the Digester instance as the Rules
097 * object before parsing starts.
098 */
099 public PluginRules() {
100 this(new RulesBase());
101 }
102
103 /**
104 * Constructor for top-level Rules object which handles rule-matching
105 * using the specified implementation.
106 */
107 public PluginRules(Rules decoratedRules) {
108 this.decoratedRules = decoratedRules;
109
110 pluginContext = new PluginContext();
111 pluginManager = new PluginManager(pluginContext);
112 }
113
114 /**
115 * Constructs a Rules instance which has a parent Rules object
116 * (which is different from having a delegate rules object).
117 * <p>
118 * One of these is created each time a PluginCreateRule's begin method
119 * fires, in order to manage the custom rules associated with whatever
120 * concrete plugin class the user has specified.
121 *
122 * @param digester is the object this rules will be associated with.
123 * @param mountPoint is the digester match path for the element
124 * matching a PluginCreateRule which caused this "nested parsing scope"
125 * to begin. This is expected to be equal to digester.getMatch().
126 * @param parent must be non-null.
127 * @param pluginClass is the plugin class whose custom rules will be
128 * loaded into this new PluginRules object.
129 */
130 PluginRules(
131 Digester digester,
132 String mountPoint,
133 PluginRules parent,
134 Class<?> pluginClass)
135 throws PluginException {
136 // no need to set digester or decoratedRules.digester,
137 // because when Digester.setRules is called, the setDigester
138 // method on this object will be called.
139
140 this.digester = digester;
141 this.mountPoint = mountPoint;
142 this.parent = parent;
143 this.rulesFactory = parent.rulesFactory;
144
145 if (rulesFactory == null) {
146 decoratedRules = new RulesBase();
147 } else {
148 decoratedRules = rulesFactory.newRules(digester, pluginClass);
149 }
150
151 pluginContext = parent.pluginContext;
152 pluginManager = new PluginManager(parent.pluginManager);
153 }
154
155 // ------------------------------------------------------------- Properties
156
157 /**
158 * Return the parent Rules object.
159 */
160 public Rules getParent() {
161 return parent;
162 }
163
164 /**
165 * Return the Digester instance with which this instance is associated.
166 */
167 public Digester getDigester() {
168 return digester;
169 }
170
171 /**
172 * Set the Digester instance with which this Rules instance is associated.
173 *
174 * @param digester The newly associated Digester instance
175 */
176 public void setDigester(Digester digester) {
177 this.digester = digester;
178 decoratedRules.setDigester(digester);
179 }
180
181 /**
182 * Return the namespace URI that will be applied to all subsequently
183 * added <code>Rule</code> objects.
184 */
185 public String getNamespaceURI() {
186 return decoratedRules.getNamespaceURI();
187 }
188
189 /**
190 * Set the namespace URI that will be applied to all subsequently
191 * added <code>Rule</code> objects.
192 *
193 * @param namespaceURI Namespace URI that must match on all
194 * subsequently added rules, or <code>null</code> for matching
195 * regardless of the current namespace URI
196 */
197 public void setNamespaceURI(String namespaceURI) {
198 decoratedRules.setNamespaceURI(namespaceURI);
199 }
200
201 /**
202 * Return the object which "knows" about all declared plugins.
203 *
204 * @return The pluginManager value
205 */
206 public PluginManager getPluginManager() {
207 return pluginManager;
208 }
209
210 /**
211 * See {@link PluginContext#getRuleFinders}.
212 */
213 public List<RuleFinder> getRuleFinders() {
214 return pluginContext.getRuleFinders();
215 }
216
217 /**
218 * See {@link PluginContext#setRuleFinders}.
219 */
220 public void setRuleFinders(List<RuleFinder> ruleFinders) {
221 pluginContext.setRuleFinders(ruleFinders);
222 }
223
224 /**
225 * Return the rules factory object (or null if one has not been specified).
226 */
227 public RulesFactory getRulesFactory() {
228 return rulesFactory;
229 }
230
231 /**
232 * Set the object which is used to generate the new Rules instances created
233 * to hold and process the rules associated with each plugged-in class.
234 */
235 public void setRulesFactory(RulesFactory factory) {
236 rulesFactory = factory;
237 }
238
239 // --------------------------------------------------------- Public Methods
240
241 /**
242 * This package-scope method is used by the PluginCreateRule class to
243 * get direct access to the rules that were dynamically added by the
244 * plugin. No other class should need access to this object.
245 */
246 Rules getDecoratedRules() {
247 return decoratedRules;
248 }
249
250 /**
251 * Return the list of rules registered with this object, in the order
252 * they were registered with this object.
253 * <p>
254 * Note that Rule objects stored in parent Rules objects are not
255 * returned by this method.
256 *
257 * @return list of all Rule objects known to this Rules instance.
258 */
259 public List<Rule> rules() {
260 return decoratedRules.rules();
261 }
262
263 /**
264 * Register a new Rule instance matching the specified pattern.
265 *
266 * @param pattern Nesting pattern to be matched for this Rule.
267 * This parameter treats equally patterns that begin with and without
268 * a leading slash ('/').
269 * @param rule Rule instance to be registered
270 */
271 public void add(String pattern, Rule rule) {
272 Log log = LogUtils.getLogger(digester);
273 boolean debug = log.isDebugEnabled();
274
275 if (debug) {
276 log.debug("add entry" + ": mapping pattern [" + pattern + "]" +
277 " to rule of type [" + rule.getClass().getName() + "]");
278 }
279
280 // allow patterns with a leading slash character
281 if (pattern.startsWith("/"))
282 {
283 pattern = pattern.substring(1);
284 }
285
286 if (mountPoint != null
287 && !pattern.equals(mountPoint)
288 && !pattern.startsWith(mountPoint + "/")) {
289 // This can only occur if a plugin attempts to add a
290 // rule with a pattern that doesn't start with the
291 // prefix passed to the addRules method. Plugins mustn't
292 // add rules outside the scope of the tag they were specified
293 // on, so refuse this.
294
295 // alas, can't throw exception
296 log.warn(
297 "An attempt was made to add a rule with a pattern that"
298 + "is not at or below the mountpoint of the current"
299 + " PluginRules object."
300 + " Rule pattern: " + pattern
301 + ", mountpoint: " + mountPoint
302 + ", rule type: " + rule.getClass().getName());
303 return;
304 }
305
306 decoratedRules.add(pattern, rule);
307
308 if (rule instanceof InitializableRule) {
309 try {
310 ((InitializableRule)rule).postRegisterInit(pattern);
311 } catch (PluginConfigurationException e) {
312 // Currently, Digester doesn't handle exceptions well
313 // from the add method. The workaround is for the
314 // initialisable rule to remember that its initialisation
315 // failed, and to throw the exception when begin is
316 // called for the first time.
317 if (debug) {
318 log.debug("Rule initialisation failed", e);
319 }
320 // throw e; -- alas, can't do this
321 return;
322 }
323 }
324
325 if (debug) {
326 log.debug("add exit" + ": mapped pattern [" + pattern + "]" +
327 " to rule of type [" + rule.getClass().getName() + "]");
328 }
329 }
330
331 /**
332 * Clear all rules.
333 */
334 public void clear() {
335 decoratedRules.clear();
336 }
337
338 /**
339 * Return a List of all registered Rule instances that match the specified
340 * nesting pattern, or a zero-length List if there are no matches. If more
341 * than one Rule instance matches, they <strong>must</strong> be returned
342 * in the order originally registered through the <code>add()</code>
343 * method.
344 *
345 * @param path the path to the xml nodes to be matched.
346 *
347 * @deprecated Call match(namespaceURI,pattern) instead.
348 */
349 @Deprecated
350 public List<Rule> match(String path) {
351 return (match(null, path));
352 }
353
354 /**
355 * Return a List of all registered Rule instances that match the specified
356 * nodepath, or a zero-length List if there are no matches. If more
357 * than one Rule instance matches, they <strong>must</strong> be returned
358 * in the order originally registered through the <code>add()</code>
359 * method.
360 * <p>
361 * @param namespaceURI Namespace URI for which to select matching rules,
362 * or <code>null</code> to match regardless of namespace URI
363 * @param path the path to the xml nodes to be matched.
364 */
365 public List<Rule> match(String namespaceURI, String path) {
366 Log log = LogUtils.getLogger(digester);
367 boolean debug = log.isDebugEnabled();
368
369 if (debug) {
370 log.debug(
371 "Matching path [" + path +
372 "] on rules object " + this.toString());
373 }
374
375 List<Rule> matches;
376 if ((mountPoint != null) &&
377 (path.length() <= mountPoint.length())) {
378 if (debug) {
379 log.debug(
380 "Path [" + path + "] delegated to parent.");
381 }
382
383 matches = parent.match(namespaceURI, path);
384
385 // Note that in the case where path equals mountPoint,
386 // we deliberately return only the rules from the parent,
387 // even though this object may hold some rules matching
388 // this same path. See PluginCreateRule's begin, body and end
389 // methods for the reason.
390 } else {
391 log.debug("delegating to decorated rules.");
392 matches = decoratedRules.match(namespaceURI, path);
393 }
394
395 return matches;
396 }
397
398 /** See {@link PluginContext#setPluginClassAttribute}. */
399 public void setPluginClassAttribute(String namespaceUri,
400 String attrName) {
401 pluginContext.setPluginClassAttribute(namespaceUri, attrName);
402 }
403
404 /** See {@link PluginContext#setPluginIdAttribute}. */
405 public void setPluginIdAttribute(String namespaceUri,
406 String attrName) {
407 pluginContext.setPluginIdAttribute(namespaceUri, attrName);
408 }
409
410 /** See {@link PluginContext#getPluginClassAttrNs}. */
411 public String getPluginClassAttrNs() {
412 return pluginContext.getPluginClassAttrNs();
413 }
414
415 /** See {@link PluginContext#getPluginClassAttr}. */
416 public String getPluginClassAttr() {
417 return pluginContext.getPluginClassAttr();
418 }
419
420 /** See {@link PluginContext#getPluginIdAttrNs}. */
421 public String getPluginIdAttrNs() {
422 return pluginContext.getPluginIdAttrNs();
423 }
424
425 /** See {@link PluginContext#getPluginIdAttr}. */
426 public String getPluginIdAttr() {
427 return pluginContext.getPluginIdAttr();
428 }
429 }