View Javadoc

1   /*
2    * $Id: DigesterDefinitionsReader.java 616890 2008-01-30 20:16:51Z 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 org.apache.commons.digester.Digester;
25  import org.apache.commons.digester.Rule;
26  import org.apache.tiles.Attribute;
27  import org.apache.tiles.Definition;
28  import org.apache.tiles.Attribute.AttributeType;
29  import org.apache.tiles.context.ListAttribute;
30  import org.apache.tiles.definition.DefinitionsFactoryException;
31  import org.apache.tiles.definition.DefinitionsReader;
32  import org.xml.sax.Attributes;
33  import org.xml.sax.ErrorHandler;
34  import org.xml.sax.SAXException;
35  
36  import java.io.IOException;
37  import java.io.InputStream;
38  import java.net.URL;
39  import java.util.HashMap;
40  import java.util.Map;
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: 616890 $ $Date: 2008-01-30 21:16:51 +0100 (Wed, 30 Jan 2008) $
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_TAG + "/put-attribute";
88  
89      /***
90       * Intercepts a &lt;put-list-attribute&gt; tag.
91       */
92      private static final String LIST_TAG = "put-list-attribute";
93  
94      /***
95       * Intercepts a &lt;put-list-attribute&gt; tag inside a %lt;definition&gt;
96       * tag.
97       */
98      private static final String DEF_LIST_TAG = DEFINITION_TAG + "/" + LIST_TAG;
99  
100     /***
101      * Intercepts a &lt;add-attribute&gt; tag.
102      */
103     private static final String ADD_LIST_ELE_TAG = "*/add-attribute";
104 
105     /***
106      * Intercepts a &lt;add-list-attribute&gt; tag.
107      */
108     private static final String NESTED_LIST = "*/add-list-attribute";
109 
110     /***
111      * Intercepts a &lt;item&gt; tag.
112      */
113     private static final String ADD_WILDCARD = "*/item";
114 
115     /***
116      * Intercepts a &lt;bean&gt; tag.
117      */
118     private static final String BEAN_TAG = "*/bean";
119 
120     // Handler class names.
121 
122     /***
123      * The handler to create definitions.
124      */
125     private static final String DEFINITION_HANDLER_CLASS =
126         Definition.class.getName();
127 
128     /***
129      * The handler to create attributes.
130      */
131     private static final String PUT_ATTRIBUTE_HANDLER_CLASS =
132         Attribute.class.getName();
133 
134     /***
135      * The handler to create list attributes.
136      */
137     private static final String LIST_HANDLER_CLASS =
138         ListAttribute.class.getName();
139 
140     /***
141      * Digester rule to manage attribute filling.
142      */
143     private static class FillAttributeRule extends Rule {
144 
145         /*** {@inheritDoc} */
146         @Override
147         public void begin(String namespace, String name, Attributes attributes)
148                 throws Exception {
149             Attribute attribute = (Attribute) digester.peek();
150             attribute.setValue(attributes.getValue("value"));
151             attribute.setRole(attributes.getValue("role"));
152             attribute.setType(AttributeType
153                     .getType(attributes.getValue("type")));
154         }
155     }
156 
157     /***
158      * Digester rule to manage assignment of the attribute to the parent
159      * element.
160      */
161     private static class PutAttributeRule extends Rule {
162 
163         /*** {@inheritDoc} */
164         @Override
165         public void begin(String namespace, String name, Attributes attributes)
166                 throws Exception {
167             Attribute attribute = (Attribute) digester.peek(0);
168             Definition definition = (Definition) digester.peek(1);
169             definition.putAttribute(attributes.getValue("name"), attribute);
170         }
171     }
172 
173     /***
174      * <code>Digester</code> object used to read Definition data
175      * from the source.
176      */
177     protected Digester digester;
178     /***
179      * Stores Definition objects.
180      */
181     private Map<String, Definition> definitions;
182     /***
183      * Should we use a validating XML parser to read the configuration file.
184      * Default is <code>true</code>.
185      */
186     protected boolean validating = true;
187     /***
188      * The set of public identifiers, and corresponding resource names for
189      * the versions of the configuration file DTDs we know about.  There
190      * <strong>MUST</strong> be an even number of Strings in this list!
191      */
192     protected String[] registrations = {
193         "-//Apache Software Foundation//DTD Tiles Configuration 2.0//EN",
194         "/org/apache/tiles/resources/tiles-config_2_0.dtd"
195     };
196 
197     /***
198      * Indicates whether init method has been called.
199      */
200     private boolean inited = false;
201 
202     /***
203      * Creates a new instance of DigesterDefinitionsReader.
204      */
205     public DigesterDefinitionsReader() {
206         digester = new Digester();
207         digester.setValidating(validating);
208         digester.setNamespaceAware(true);
209         digester.setUseContextClassLoader(true);
210         digester.setErrorHandler(new ThrowingErrorHandler());
211 
212         // Register our local copy of the DTDs that we can find
213         for (int i = 0; i < registrations.length; i += 2) {
214             URL url = this.getClass().getResource(
215                 registrations[i + 1]);
216             if (url != null) {
217                 digester.register(registrations[i], url.toString());
218             }
219         }
220     }
221 
222     /***
223      * Reads <code>{@link Definition}</code> objects from a source.
224      * <p/>
225      * Implementations should publish what type of source object is expected.
226      *
227      * @param source The <code>InputStream</code> source from which definitions
228      *               will be read.
229      * @return a Map of <code>Definition</code> objects read from
230      *         the source.
231      * @throws org.apache.tiles.definition.DefinitionsFactoryException
232      *          if the source is invalid or
233      *          an error occurs when reading definitions.
234      */
235     public Map<String, Definition> read(Object source)
236             throws DefinitionsFactoryException {
237 
238         // Get out if we have not been initialized.
239         if (!inited) {
240             throw new DefinitionsFactoryException(
241                 "Definitions reader has not been initialized.");
242         }
243 
244         // This is an instance variable instead of a local variable because
245         // we want to be able to call the addDefinition method to populate it.
246         // But we reset the Map here, which, of course, has threading implications.
247         definitions = new HashMap<String, Definition>();
248 
249         if (source == null) {
250             // Perhaps we should throw an exception here.
251             return null;
252         }
253 
254         InputStream input;
255         try {
256             input = (InputStream) source;
257         } catch (ClassCastException e) {
258             throw new DefinitionsFactoryException(
259                 "Invalid source type.  Requires java.io.InputStream.", e);
260         }
261 
262         try {
263             // set first object in stack
264             //digester.clear();
265             digester.push(this);
266             // parse
267             digester.parse(input);
268 
269         } catch (SAXException e) {
270             throw new DefinitionsFactoryException(
271                 "XML error reading definitions.", e);
272         } catch (IOException e) {
273             throw new DefinitionsFactoryException(
274                 "I/O Error reading definitions.", e);
275         }
276 
277         return definitions;
278     }
279 
280     /***
281      * Initializes the <code>DefinitionsReader</code> object.
282      * <p/>
283      * This method must be called before the {@link #read} method is called.
284      *
285      * @param params A map of properties used to set up the reader.
286      * @throws org.apache.tiles.definition.DefinitionsFactoryException
287      *          if required properties are not
288      *          passed in or the initialization fails.
289      */
290     public void init(Map<String, String> params) throws DefinitionsFactoryException {
291 
292         if (params != null) {
293             String value = params.get(PARSER_VALIDATE_PARAMETER_NAME);
294             if (value != null) {
295                 digester.setValidating(Boolean.valueOf(value));
296             }
297         }
298 
299         initDigesterForTilesDefinitionsSyntax(digester);
300 
301         inited = true;
302     }
303 
304 
305     /***
306      * Init digester for Tiles syntax with first element = tiles-definitions.
307      *
308      * @param digester Digester instance to use.
309      */
310     private void initDigesterForTilesDefinitionsSyntax(Digester digester) {
311         // syntax rules
312         digester.addObjectCreate(DEFINITION_TAG, DEFINITION_HANDLER_CLASS);
313         digester.addSetProperties(DEFINITION_TAG);
314         digester.addSetNext(DEFINITION_TAG, "addDefinition", DEFINITION_HANDLER_CLASS);
315         // put / putAttribute rules
316         // Rules for a same pattern are called in order, but rule.end() are called
317         // in reverse order.
318         // SetNext and CallMethod use rule.end() method. So, placing SetNext in
319         // first position ensure it will be called last (sic).
320         digester.addObjectCreate(PUT_TAG, PUT_ATTRIBUTE_HANDLER_CLASS);
321         digester.addRule(PUT_TAG, new FillAttributeRule());
322         digester.addRule(PUT_TAG, new PutAttributeRule());
323         digester.addCallMethod(PUT_TAG, "setBody", 0);
324         // Definition level list rules
325         // This is rules for lists nested in a definition
326         digester.addObjectCreate(DEF_LIST_TAG, LIST_HANDLER_CLASS);
327         digester.addSetProperties(DEF_LIST_TAG);
328         digester.addRule(DEF_LIST_TAG, new PutAttributeRule());
329         // list elements rules
330         // We use Attribute class to avoid rewriting a new class.
331         // Name part can't be used in listElement attribute.
332         digester.addObjectCreate(ADD_LIST_ELE_TAG, PUT_ATTRIBUTE_HANDLER_CLASS);
333         digester.addRule(ADD_LIST_ELE_TAG, new FillAttributeRule());
334         digester.addSetNext(ADD_LIST_ELE_TAG, "add", PUT_ATTRIBUTE_HANDLER_CLASS);
335         digester.addCallMethod(ADD_LIST_ELE_TAG, "setBody", 0);
336 
337         // nested list elements rules
338         // Create a list handler, and add it to parent list
339         digester.addObjectCreate(NESTED_LIST, LIST_HANDLER_CLASS);
340         digester.addSetProperties(NESTED_LIST);
341         digester.addSetNext(NESTED_LIST, "add", PUT_ATTRIBUTE_HANDLER_CLASS);
342 
343         // item elements rules
344         // We use Attribute class to avoid rewriting a new class.
345         // Name part can't be used in listElement attribute.
346         //String ADD_WILDCARD = LIST_TAG + "/addItem";
347         // non String ADD_WILDCARD = LIST_TAG + "/addx*";
348         String menuItemDefaultClass = "org.apache.tiles.beans.SimpleMenuItem";
349         digester.addObjectCreate(ADD_WILDCARD, menuItemDefaultClass, "classtype");
350         digester.addSetNext(ADD_WILDCARD, "add", "java.lang.Object");
351         digester.addSetProperties(ADD_WILDCARD);
352 
353         // bean elements rules
354         String beanDefaultClass = "org.apache.tiles.beans.SimpleMenuItem";
355         digester.addObjectCreate(BEAN_TAG, beanDefaultClass, "classtype");
356         digester.addSetProperties(BEAN_TAG);
357         digester.addSetNext(BEAN_TAG, "add", "java.lang.Object");
358 
359         // Set properties to surrounding element
360         digester.addSetProperty(BEAN_TAG + "/set-property", "property", "value");
361     }
362 
363     /***
364      * Adds a new <code>Definition</code> to the internal Map or replaces
365      * an existing one.
366      *
367      * @param definition The Definition object to be added.
368      */
369     public void addDefinition(Definition definition) {
370         definitions.put(definition.getName(), definition);
371     }
372 
373     /***
374      * Error Handler that throws every exception it receives.
375      */
376     private static class ThrowingErrorHandler implements ErrorHandler {
377 
378         /*** {@inheritDoc} */
379         public void warning(SAXParseException exception) throws SAXException {
380             throw exception;
381         }
382 
383         /*** {@inheritDoc} */
384         public void error(SAXParseException exception) throws SAXException {
385             throw exception;
386         }
387 
388         /*** {@inheritDoc} */
389         public void fatalError(SAXParseException exception) throws SAXException {
390             throw exception;
391         }
392     }
393 }