This project has retired. For details please refer to its Attic page.
DigesterDefinitionsReader xref
View Javadoc

1   /*
2    * $Id: DigesterDefinitionsReader.java 788032 2009-06-24 14:08:32Z apetrelli $
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   * http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  package org.apache.tiles.definition.digester;
23  
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.net.URL;
27  import java.util.LinkedHashMap;
28  import java.util.Map;
29  
30  import org.apache.commons.digester.Digester;
31  import org.apache.commons.digester.Rule;
32  import org.apache.tiles.Attribute;
33  import org.apache.tiles.Definition;
34  import org.apache.tiles.Expression;
35  import org.apache.tiles.ListAttribute;
36  import org.apache.tiles.definition.DefinitionsFactoryException;
37  import org.apache.tiles.definition.DefinitionsReader;
38  import org.xml.sax.Attributes;
39  import org.xml.sax.ErrorHandler;
40  import org.xml.sax.SAXException;
41  import org.xml.sax.SAXParseException;
42  
43  /***
44   * Reads {@link Definition} objects from
45   * an XML InputStream using Digester. <p/>
46   * <p>
47   * This <code>DefinitionsReader</code> implementation expects the source to be
48   * passed as an <code>InputStream</code>. It parses XML data from the source
49   * and builds a Map of Definition objects.
50   * </p>
51   * <p/>
52   * <p>
53   * The Digester object can be configured by passing in initialization
54   * parameters. Currently the only parameter that is supported is the
55   * <code>validating</code> parameter. This value is set to <code>false</code>
56   * by default. To enable DTD validation for XML Definition files, give the init
57   * method a parameter with a key of
58   * <code>org.apache.tiles.definition.digester.DigesterDefinitionsReader.PARSER_VALIDATE</code>
59   * and a value of <code>&quot;true&quot;</code>. <p/>
60   * <p>
61   * The Definition objects are stored internally in a Map. The Map is stored as
62   * an instance variable rather than a local variable in the <code>read</code>
63   * method. This means that instances of this class are <strong>not</strong>
64   * thread-safe and access by multiple threads must be synchronized.
65   * </p>
66   *
67   * @version $Rev: 788032 $ $Date: 2009-06-24 16:08:32 +0200 (mer, 24 giu 2009) $
68   */
69  public class DigesterDefinitionsReader implements DefinitionsReader {
70  
71      /***
72       * Digester validation parameter name.
73       */
74      public static final String PARSER_VALIDATE_PARAMETER_NAME =
75          "org.apache.tiles.definition.digester.DigesterDefinitionsReader.PARSER_VALIDATE";
76  
77      // Digester rules constants for tag interception.
78  
79      /***
80       * Intercepts a &lt;definition&gt; tag.
81       */
82      private static final String DEFINITION_TAG = "tiles-definitions/definition";
83  
84      /***
85       * Intercepts a &lt;put-attribute&gt; tag.
86       */
87      private static final String PUT_TAG = "*/definition/put-attribute";
88  
89      /***
90       * Intercepts a &lt;definition&gt; inside a  &lt;put-attribute&gt; tag.
91       */
92      private static final String PUT_DEFINITION_TAG = "*/put-attribute/definition";
93  
94      /***
95       * Intercepts a &lt;definition&gt; inside an &lt;add-attribute&gt; tag.
96       */
97      private static final String ADD_DEFINITION_TAG = "*/add-attribute/definition";
98  
99      /***
100      * Intercepts a &lt;put-list-attribute&gt; tag inside a %lt;definition&gt;
101      * tag.
102      */
103     private static final String DEF_LIST_TAG = "*/definition/put-list-attribute";
104 
105     /***
106      * Intercepts a &lt;add-attribute&gt; tag.
107      */
108     private static final String ADD_LIST_ELE_TAG = "*/add-attribute";
109 
110     /***
111      * Intercepts a &lt;add-list-attribute&gt; tag.
112      */
113     private static final String NESTED_LIST = "*/add-list-attribute";
114 
115     /***
116      * Intercepts a &lt;item&gt; tag.
117      */
118     private static final String ADD_WILDCARD = "*/item";
119 
120     /***
121      * Intercepts a &lt;bean&gt; tag.
122      */
123     private static final String BEAN_TAG = "*/bean";
124 
125     // Handler class names.
126 
127     /***
128      * The handler to create definitions.
129      *
130      * @since 2.1.0
131      */
132     protected static final String DEFINITION_HANDLER_CLASS =
133         Definition.class.getName();
134 
135     /***
136      * The handler to create attributes.
137      *
138      * @since 2.1.0
139      */
140     protected static final String PUT_ATTRIBUTE_HANDLER_CLASS =
141         Attribute.class.getName();
142 
143     /***
144      * The handler to create list attributes.
145      *
146      * @since 2.1.0
147      */
148     protected static final String LIST_HANDLER_CLASS =
149         ListAttribute.class.getName();
150 
151     /***
152      * Digester rule to manage definition filling.
153      *
154      * @since 2.1.2
155      */
156     public static class FillDefinitionRule extends Rule {
157 
158         /*** {@inheritDoc} */
159         @Override
160         public void begin(String namespace, String name, Attributes attributes)
161                 throws Exception {
162             Definition definition = (Definition) digester.peek();
163             definition.setName(attributes.getValue("name"));
164             definition.setPreparer(attributes.getValue("preparer"));
165             definition.setExtends(attributes.getValue("extends"));
166 
167             String template = attributes.getValue("template");
168             Attribute attribute = Attribute.createTemplateAttribute(template);
169             attribute.setExpressionObject(Expression
170                     .createExpressionFromDescribedExpression(attributes
171                             .getValue("templateExpression")));
172             attribute.setRole(attributes.getValue("role"));
173             String templateType = attributes.getValue("templateType");
174             if (templateType != null) {
175                 attribute.setRenderer(templateType);
176             }
177             definition.setTemplateAttribute(attribute);
178         }
179     }
180 
181     /***
182      * Digester rule to manage attribute filling.
183      *
184      * @since 2.1.0
185      */
186     public static class FillAttributeRule extends Rule {
187 
188         /*** {@inheritDoc} */
189         @Override
190         public void begin(String namespace, String name, Attributes attributes)
191                 throws Exception {
192             Attribute attribute = (Attribute) digester.peek();
193             attribute.setValue(attributes.getValue("value"));
194             String expression = attributes.getValue("expression");
195             attribute.setExpressionObject(Expression
196                     .createExpressionFromDescribedExpression(expression));
197             attribute.setRole(attributes.getValue("role"));
198             attribute.setRenderer(attributes.getValue("type"));
199         }
200     }
201 
202     /***
203      * Digester rule to manage assignment of the attribute to the parent
204      * element.
205      *
206      * @since 2.1.0
207      */
208     public static class PutAttributeRule extends Rule {
209 
210         /*** {@inheritDoc} */
211         @Override
212         public void begin(String namespace, String name, Attributes attributes)
213                 throws Exception {
214             Attribute attribute = (Attribute) digester.peek(0);
215             Definition definition = (Definition) digester.peek(1);
216             definition.putAttribute(attributes.getValue("name"), attribute,
217                     "true".equals(attributes.getValue("cascade")));
218         }
219     }
220 
221     /***
222      * Digester rule to manage assignment of a nested definition in an attribute
223      * value.
224      *
225      * @since 2.1.0
226      */
227     public class AddNestedDefinitionRule extends Rule {
228 
229         /*** {@inheritDoc} */
230         @Override
231         public void begin(String namespace, String name, Attributes attributes)
232                 throws Exception {
233             Definition definition = (Definition) digester.peek(0);
234             if (definition.getName() == null) {
235                 definition.setName(getNextUniqueDefinitionName(definitions));
236             }
237             Attribute attribute = (Attribute) digester.peek(1);
238             attribute.setValue(definition.getName());
239             attribute.setRenderer("definition");
240         }
241     }
242 
243     /***
244      * <code>Digester</code> object used to read Definition data
245      * from the source.
246      */
247     protected Digester digester;
248     /***
249      * Stores Definition objects.
250      */
251     private Map<String, Definition> definitions;
252     /***
253      * Should we use a validating XML parser to read the configuration file.
254      * Default is <code>true</code>.
255      */
256     protected boolean validating = true;
257     /***
258      * The set of public identifiers, and corresponding resource names for
259      * the versions of the configuration file DTDs we know about.  There
260      * <strong>MUST</strong> be an even number of Strings in this list!
261      */
262     protected String[] registrations;
263 
264     /***
265      * Index to be used to create unique definition names for anonymous
266      * (nested) definitions.
267      */
268     private int anonymousDefinitionIndex = 1;
269 
270     /***
271      * Creates a new instance of DigesterDefinitionsReader.
272      */
273     public DigesterDefinitionsReader() {
274         digester = new Digester();
275         digester.setValidating(validating);
276         digester.setNamespaceAware(true);
277         digester.setUseContextClassLoader(true);
278         digester.setErrorHandler(new ThrowingErrorHandler());
279 
280         // Register our local copy of the DTDs that we can find
281         String[] registrations = getRegistrations();
282         for (int i = 0; i < registrations.length; i += 2) {
283             URL url = this.getClass().getResource(
284                 registrations[i + 1]);
285             if (url != null) {
286                 digester.register(registrations[i], url.toString());
287             }
288         }
289 
290         initSyntax(digester);
291     }
292 
293     /***
294      * Reads <code>{@link Definition}</code> objects from a source.
295      * <p/>
296      * Implementations should publish what type of source object is expected.
297      *
298      * @param source The <code>InputStream</code> source from which definitions
299      *               will be read.
300      * @return a Map of <code>Definition</code> objects read from
301      *         the source.
302      * @throws DefinitionsFactoryException If the source is invalid or
303      *          an error occurs when reading definitions.
304      */
305     public Map<String, Definition> read(Object source) {
306         // This is an instance variable instead of a local variable because
307         // we want to be able to call the addDefinition method to populate it.
308         // But we reset the Map here, which, of course, has threading implications.
309         definitions = new LinkedHashMap<String, Definition>();
310 
311         if (source == null) {
312             // Perhaps we should throw an exception here.
313             return null;
314         }
315 
316         InputStream input;
317         try {
318             input = (InputStream) source;
319         } catch (ClassCastException e) {
320             throw new DefinitionsFactoryException(
321                 "Invalid source type.  Requires java.io.InputStream.", e);
322         }
323 
324         try {
325             // set first object in stack
326             //digester.clear();
327             digester.push(this);
328             // parse
329             digester.parse(input);
330 
331         } catch (SAXException e) {
332             throw new DefinitionsFactoryException(
333                 "XML error reading definitions.", e);
334         } catch (IOException e) {
335             throw new DefinitionsFactoryException(
336                 "I/O Error reading definitions.", e);
337         } finally {
338             digester.clear();
339         }
340 
341         return definitions;
342     }
343 
344     /***
345      * Initializes the <code>DefinitionsReader</code> object.
346      * <p/>
347      * This method must be called before the {@link #read} method is called.
348      *
349      * @param params A map of properties used to set up the reader.
350      * @throws DefinitionsFactoryException if required properties are not passed
351      * in or the initialization fails.
352      */
353     public void init(Map<String, String> params) {
354         if (params != null) {
355             String value = params.get(PARSER_VALIDATE_PARAMETER_NAME);
356             if (value != null) {
357                 digester.setValidating(Boolean.valueOf(value));
358             }
359         }
360     }
361 
362     /***
363      * Initialised the syntax for reading XML files containing Tiles
364      * definitions.
365      *
366      * @param digester The digester to initialize.
367      */
368     protected void initSyntax(Digester digester) {
369         initDigesterForTilesDefinitionsSyntax(digester);
370     }
371 
372 
373     /***
374      * Init digester for Tiles syntax with first element = tiles-definitions.
375      *
376      * @param digester Digester instance to use.
377      */
378     private void initDigesterForTilesDefinitionsSyntax(Digester digester) {
379         // syntax rules
380         digester.addObjectCreate(DEFINITION_TAG, DEFINITION_HANDLER_CLASS);
381         digester.addRule(DEFINITION_TAG, new FillDefinitionRule());
382         digester.addSetNext(DEFINITION_TAG, "addDefinition", DEFINITION_HANDLER_CLASS);
383 
384         // nested definition rules
385         digester.addObjectCreate(PUT_DEFINITION_TAG, DEFINITION_HANDLER_CLASS);
386         digester.addRule(PUT_DEFINITION_TAG, new FillDefinitionRule());
387         digester.addSetRoot(PUT_DEFINITION_TAG, "addDefinition");
388         digester.addRule(PUT_DEFINITION_TAG, new AddNestedDefinitionRule());
389         digester.addObjectCreate(ADD_DEFINITION_TAG, DEFINITION_HANDLER_CLASS);
390         digester.addRule(ADD_DEFINITION_TAG, new FillDefinitionRule());
391         digester.addSetRoot(ADD_DEFINITION_TAG, "addDefinition");
392         digester.addRule(ADD_DEFINITION_TAG, new AddNestedDefinitionRule());
393 
394         // put / putAttribute rules
395         // Rules for a same pattern are called in order, but rule.end() are called
396         // in reverse order.
397         // SetNext and CallMethod use rule.end() method. So, placing SetNext in
398         // first position ensure it will be called last (sic).
399         digester.addObjectCreate(PUT_TAG, PUT_ATTRIBUTE_HANDLER_CLASS);
400         digester.addRule(PUT_TAG, new FillAttributeRule());
401         digester.addRule(PUT_TAG, new PutAttributeRule());
402         digester.addCallMethod(PUT_TAG, "setBody", 0);
403         // Definition level list rules
404         // This is rules for lists nested in a definition
405         digester.addObjectCreate(DEF_LIST_TAG, LIST_HANDLER_CLASS);
406         digester.addSetProperties(DEF_LIST_TAG);
407         digester.addRule(DEF_LIST_TAG, new PutAttributeRule());
408         // list elements rules
409         // We use Attribute class to avoid rewriting a new class.
410         // Name part can't be used in listElement attribute.
411         digester.addObjectCreate(ADD_LIST_ELE_TAG, PUT_ATTRIBUTE_HANDLER_CLASS);
412         digester.addRule(ADD_LIST_ELE_TAG, new FillAttributeRule());
413         digester.addSetNext(ADD_LIST_ELE_TAG, "add", PUT_ATTRIBUTE_HANDLER_CLASS);
414         digester.addCallMethod(ADD_LIST_ELE_TAG, "setBody", 0);
415 
416         // nested list elements rules
417         // Create a list handler, and add it to parent list
418         digester.addObjectCreate(NESTED_LIST, LIST_HANDLER_CLASS);
419         digester.addSetProperties(NESTED_LIST);
420         digester.addSetNext(NESTED_LIST, "add", PUT_ATTRIBUTE_HANDLER_CLASS);
421 
422         // item elements rules
423         // We use Attribute class to avoid rewriting a new class.
424         // Name part can't be used in listElement attribute.
425         //String ADD_WILDCARD = LIST_TAG + "/addItem";
426         // non String ADD_WILDCARD = LIST_TAG + "/addx*";
427         String menuItemDefaultClass = "org.apache.tiles.beans.SimpleMenuItem";
428         digester.addObjectCreate(ADD_WILDCARD, menuItemDefaultClass, "classtype");
429         digester.addSetNext(ADD_WILDCARD, "add", "java.lang.Object");
430         digester.addSetProperties(ADD_WILDCARD);
431 
432         // bean elements rules
433         String beanDefaultClass = "org.apache.tiles.beans.SimpleMenuItem";
434         digester.addObjectCreate(BEAN_TAG, beanDefaultClass, "classtype");
435         digester.addSetProperties(BEAN_TAG);
436         digester.addSetNext(BEAN_TAG, "add", "java.lang.Object");
437 
438         // Set properties to surrounding element
439         digester.addSetProperty(BEAN_TAG + "/set-property", "property", "value");
440     }
441 
442     /***
443      * Adds a new <code>Definition</code> to the internal Map or replaces
444      * an existing one.
445      *
446      * @param definition The Definition object to be added.
447      */
448     public void addDefinition(Definition definition) {
449         String name = definition.getName();
450         if (name == null) {
451             throw new DigesterDefinitionsReaderException(
452                     "A root definition has been defined with no name");
453         }
454 
455         definitions.put(name, definition);
456     }
457 
458     /***
459      * Error Handler that throws every exception it receives.
460      */
461     private static class ThrowingErrorHandler implements ErrorHandler {
462 
463         /*** {@inheritDoc} */
464         public void warning(SAXParseException exception) throws SAXException {
465             throw exception;
466         }
467 
468         /*** {@inheritDoc} */
469         public void error(SAXParseException exception) throws SAXException {
470             throw exception;
471         }
472 
473         /*** {@inheritDoc} */
474         public void fatalError(SAXParseException exception) throws SAXException {
475             throw exception;
476         }
477     }
478 
479     /***
480      * Returns the registrations for local DTDs.
481      *
482      * @return An array containing the locations for registrations of local
483      * DTDs.
484      * @since 2.1.0
485      */
486     protected String[] getRegistrations() {
487         if (registrations == null) {
488             registrations = new String[] {
489                 "-//Apache Software Foundation//DTD Tiles Configuration 2.0//EN",
490                 "/org/apache/tiles/resources/tiles-config_2_0.dtd",
491                 "-//Apache Software Foundation//DTD Tiles Configuration 2.1//EN",
492                 "/org/apache/tiles/resources/tiles-config_2_1.dtd"};
493         }
494         return registrations;
495     }
496 
497     /***
498      * Create a unique definition name usable to store anonymous definitions.
499      *
500      * @param definitions The already created definitions.
501      * @return The unique definition name to be used to store the definition.
502      * @since 2.1.0
503      */
504     protected String getNextUniqueDefinitionName(
505             Map<String, Definition> definitions) {
506         String candidate;
507 
508         do {
509             candidate = "$anonymousDefinition" + anonymousDefinitionIndex;
510             anonymousDefinitionIndex++;
511         } while (definitions.containsKey(candidate));
512 
513         return candidate;
514     }
515 }