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

1   /*
2    * $Id: DigesterDefinitionsReader.java 990237 2010-08-27 19:33:35Z 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: 990237 $ $Date: 2010-08-28 05:33:35 +1000 (Sat, 28 Aug 2010) $
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     // Handler class names.
116 
117     /**
118      * The handler to create definitions.
119      *
120      * @since 2.1.0
121      */
122     protected static final String DEFINITION_HANDLER_CLASS =
123         Definition.class.getName();
124 
125     /**
126      * The handler to create attributes.
127      *
128      * @since 2.1.0
129      */
130     protected static final String PUT_ATTRIBUTE_HANDLER_CLASS =
131         Attribute.class.getName();
132 
133     /**
134      * The handler to create list attributes.
135      *
136      * @since 2.1.0
137      */
138     protected static final String LIST_HANDLER_CLASS =
139         ListAttribute.class.getName();
140 
141     /**
142      * Digester rule to manage definition filling.
143      *
144      * @since 2.1.2
145      */
146     public static class FillDefinitionRule extends Rule {
147 
148         /** {@inheritDoc} */
149         @Override
150         public void begin(String namespace, String name, Attributes attributes) {
151             Definition definition = (Definition) digester.peek();
152             definition.setName(attributes.getValue("name"));
153             definition.setPreparer(attributes.getValue("preparer"));
154             String extendsAttribute = attributes.getValue("extends");
155             definition.setExtends(extendsAttribute);
156 
157             String template = attributes.getValue("template");
158             Attribute attribute = Attribute.createTemplateAttribute(template);
159             attribute.setExpressionObject(Expression
160                     .createExpressionFromDescribedExpression(attributes
161                             .getValue("templateExpression")));
162             attribute.setRole(attributes.getValue("role"));
163             String templateType = attributes.getValue("templateType");
164             if (templateType != null) {
165                 attribute.setRenderer(templateType);
166             } else if (extendsAttribute != null && templateType == null) {
167                 attribute.setRenderer(null);
168             }
169             definition.setTemplateAttribute(attribute);
170         }
171     }
172 
173     /**
174      * Digester rule to manage attribute filling.
175      *
176      * @since 2.1.0
177      */
178     public static class FillAttributeRule extends Rule {
179 
180         /** {@inheritDoc} */
181         @Override
182         public void begin(String namespace, String name, Attributes attributes) {
183             Attribute attribute = (Attribute) digester.peek();
184             attribute.setValue(attributes.getValue("value"));
185             String expression = attributes.getValue("expression");
186             attribute.setExpressionObject(Expression
187                     .createExpressionFromDescribedExpression(expression));
188             attribute.setRole(attributes.getValue("role"));
189             attribute.setRenderer(attributes.getValue("type"));
190         }
191     }
192 
193     /**
194      * Digester rule to manage assignment of the attribute to the parent
195      * element.
196      *
197      * @since 2.1.0
198      */
199     public static class PutAttributeRule extends Rule {
200 
201         /** {@inheritDoc} */
202         @Override
203         public void begin(String namespace, String name, Attributes attributes) {
204             Attribute attribute = (Attribute) digester.peek(0);
205             Definition definition = (Definition) digester.peek(1);
206             definition.putAttribute(attributes.getValue("name"), attribute,
207                     "true".equals(attributes.getValue("cascade")));
208         }
209     }
210 
211     /**
212      * Digester rule to manage assignment of a nested definition in an attribute
213      * value.
214      *
215      * @since 2.1.0
216      */
217     public class AddNestedDefinitionRule extends Rule {
218 
219         /** {@inheritDoc} */
220         @Override
221         public void begin(String namespace, String name, Attributes attributes) {
222             Definition definition = (Definition) digester.peek(0);
223             if (definition.getName() == null) {
224                 definition.setName(getNextUniqueDefinitionName(definitions));
225             }
226             Attribute attribute = (Attribute) digester.peek(1);
227             attribute.setValue(definition.getName());
228             attribute.setRenderer("definition");
229         }
230     }
231 
232     /**
233      * <code>Digester</code> object used to read Definition data
234      * from the source.
235      */
236     protected Digester digester;
237 
238     /**
239      * The set of public identifiers, and corresponding resource names for
240      * the versions of the configuration file DTDs we know about.  There
241      * <strong>MUST</strong> be an even number of Strings in this list!
242      */
243     protected String[] registrations;
244 
245     /**
246      * Stores Definition objects.
247      */
248     private Map<String, Definition> definitions;
249 
250     /**
251      * Index to be used to create unique definition names for anonymous
252      * (nested) definitions.
253      */
254     private int anonymousDefinitionIndex = 1;
255 
256     /**
257      * Creates a new instance of DigesterDefinitionsReader.
258      */
259     public DigesterDefinitionsReader() {
260         digester = new Digester();
261         digester.setNamespaceAware(true);
262         digester.setUseContextClassLoader(true);
263         digester.setErrorHandler(new ThrowingErrorHandler());
264 
265         // Register our local copy of the DTDs that we can find
266         String[] registrations = getRegistrations();
267         for (int i = 0; i < registrations.length; i += 2) {
268             URL url = this.getClass().getResource(
269                 registrations[i + 1]);
270             if (url != null) {
271                 digester.register(registrations[i], url.toString());
272             }
273         }
274 
275         initSyntax(digester);
276     }
277 
278     /**
279      * Sets the validation of XML files.
280      *
281      * @param validating <code>true</code> means that XML validation is turned
282      * on. <code>false</code> otherwise.
283      * @since 3.3.0
284      */
285     public void setValidating(boolean validating) {
286         digester.setValidating(validating);
287     }
288 
289     /**
290      * Reads <code>{@link Definition}</code> objects from a source.
291      * <p/>
292      * Implementations should publish what type of source object is expected.
293      *
294      * @param source The <code>InputStream</code> source from which definitions
295      *               will be read.
296      * @return a Map of <code>Definition</code> objects read from
297      *         the source.
298      * @throws DefinitionsFactoryException If the source is invalid or
299      *          an error occurs when reading definitions.
300      */
301     public Map<String, Definition> read(Object source) {
302         // This is an instance variable instead of a local variable because
303         // we want to be able to call the addDefinition method to populate it.
304         // But we reset the Map here, which, of course, has threading implications.
305         definitions = new LinkedHashMap<String, Definition>();
306 
307         if (source == null) {
308             // Perhaps we should throw an exception here.
309             return null;
310         }
311 
312         InputStream input;
313         try {
314             input = (InputStream) source;
315         } catch (ClassCastException e) {
316             throw new DefinitionsFactoryException(
317                 "Invalid source type.  Requires java.io.InputStream.", e);
318         }
319 
320         try {
321             // set first object in stack
322             //digester.clear();
323             digester.push(this);
324             // parse
325             digester.parse(input);
326 
327         } catch (SAXException e) {
328             throw new DefinitionsFactoryException(
329                 "XML error reading definitions.", e);
330         } catch (IOException e) {
331             throw new DefinitionsFactoryException(
332                 "I/O Error reading definitions.", e);
333         } finally {
334             digester.clear();
335         }
336 
337         return definitions;
338     }
339 
340     /**
341      * Initialised the syntax for reading XML files containing Tiles
342      * definitions.
343      *
344      * @param digester The digester to initialize.
345      */
346     protected void initSyntax(Digester digester) {
347         initDigesterForTilesDefinitionsSyntax(digester);
348     }
349 
350 
351     /**
352      * Init digester for Tiles syntax with first element = tiles-definitions.
353      *
354      * @param digester Digester instance to use.
355      */
356     private void initDigesterForTilesDefinitionsSyntax(Digester digester) {
357         // syntax rules
358         digester.addObjectCreate(DEFINITION_TAG, DEFINITION_HANDLER_CLASS);
359         digester.addRule(DEFINITION_TAG, new FillDefinitionRule());
360         digester.addSetNext(DEFINITION_TAG, "addDefinition", DEFINITION_HANDLER_CLASS);
361 
362         // nested definition rules
363         digester.addObjectCreate(PUT_DEFINITION_TAG, DEFINITION_HANDLER_CLASS);
364         digester.addRule(PUT_DEFINITION_TAG, new FillDefinitionRule());
365         digester.addSetRoot(PUT_DEFINITION_TAG, "addDefinition");
366         digester.addRule(PUT_DEFINITION_TAG, new AddNestedDefinitionRule());
367         digester.addObjectCreate(ADD_DEFINITION_TAG, DEFINITION_HANDLER_CLASS);
368         digester.addRule(ADD_DEFINITION_TAG, new FillDefinitionRule());
369         digester.addSetRoot(ADD_DEFINITION_TAG, "addDefinition");
370         digester.addRule(ADD_DEFINITION_TAG, new AddNestedDefinitionRule());
371 
372         // put / putAttribute rules
373         // Rules for a same pattern are called in order, but rule.end() are called
374         // in reverse order.
375         // SetNext and CallMethod use rule.end() method. So, placing SetNext in
376         // first position ensure it will be called last (sic).
377         digester.addObjectCreate(PUT_TAG, PUT_ATTRIBUTE_HANDLER_CLASS);
378         digester.addRule(PUT_TAG, new FillAttributeRule());
379         digester.addRule(PUT_TAG, new PutAttributeRule());
380         // Definition level list rules
381         // This is rules for lists nested in a definition
382         digester.addObjectCreate(DEF_LIST_TAG, LIST_HANDLER_CLASS);
383         digester.addSetProperties(DEF_LIST_TAG);
384         digester.addRule(DEF_LIST_TAG, new PutAttributeRule());
385         // list elements rules
386         // We use Attribute class to avoid rewriting a new class.
387         // Name part can't be used in listElement attribute.
388         digester.addObjectCreate(ADD_LIST_ELE_TAG, PUT_ATTRIBUTE_HANDLER_CLASS);
389         digester.addRule(ADD_LIST_ELE_TAG, new FillAttributeRule());
390         digester.addSetNext(ADD_LIST_ELE_TAG, "add", PUT_ATTRIBUTE_HANDLER_CLASS);
391 
392         // nested list elements rules
393         // Create a list handler, and add it to parent list
394         digester.addObjectCreate(NESTED_LIST, LIST_HANDLER_CLASS);
395         digester.addSetProperties(NESTED_LIST);
396         digester.addSetNext(NESTED_LIST, "add", PUT_ATTRIBUTE_HANDLER_CLASS);
397     }
398 
399     /**
400      * Adds a new <code>Definition</code> to the internal Map or replaces
401      * an existing one.
402      *
403      * @param definition The Definition object to be added.
404      */
405     public void addDefinition(Definition definition) {
406         String name = definition.getName();
407         if (name == null) {
408             throw new DigesterDefinitionsReaderException(
409                     "A root definition has been defined with no name");
410         }
411 
412         definitions.put(name, definition);
413     }
414 
415     /**
416      * Error Handler that throws every exception it receives.
417      */
418     private static class ThrowingErrorHandler implements ErrorHandler {
419 
420         /** {@inheritDoc} */
421         public void warning(SAXParseException exception) throws SAXException {
422             throw exception;
423         }
424 
425         /** {@inheritDoc} */
426         public void error(SAXParseException exception) throws SAXException {
427             throw exception;
428         }
429 
430         /** {@inheritDoc} */
431         public void fatalError(SAXParseException exception) throws SAXException {
432             throw exception;
433         }
434     }
435 
436     /**
437      * Returns the registrations for local DTDs.
438      *
439      * @return An array containing the locations for registrations of local
440      * DTDs.
441      * @since 2.1.0
442      */
443     protected String[] getRegistrations() {
444         if (registrations == null) {
445             registrations = new String[] {
446                 "-//Apache Software Foundation//DTD Tiles Configuration 3.0//EN",
447                 "/org/apache/tiles/resources/tiles-config_3_0.dtd"};
448         }
449         return registrations;
450     }
451 
452     /**
453      * Create a unique definition name usable to store anonymous definitions.
454      *
455      * @param definitions The already created definitions.
456      * @return The unique definition name to be used to store the definition.
457      * @since 2.1.0
458      */
459     protected String getNextUniqueDefinitionName(
460             Map<String, Definition> definitions) {
461         String candidate;
462 
463         do {
464             candidate = "$anonymousDefinition" + anonymousDefinitionIndex;
465             anonymousDefinitionIndex++;
466         } while (definitions.containsKey(candidate));
467 
468         return candidate;
469     }
470 }