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>"true"</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 <definition> tag.
81 */
82 private static final String DEFINITION_TAG = "tiles-definitions/definition";
83
84 /**
85 * Intercepts a <put-attribute> tag.
86 */
87 private static final String PUT_TAG = "*/definition/put-attribute";
88
89 /**
90 * Intercepts a <definition> inside a <put-attribute> tag.
91 */
92 private static final String PUT_DEFINITION_TAG = "*/put-attribute/definition";
93
94 /**
95 * Intercepts a <definition> inside an <add-attribute> tag.
96 */
97 private static final String ADD_DEFINITION_TAG = "*/add-attribute/definition";
98
99 /**
100 * Intercepts a <put-list-attribute> tag inside a %lt;definition>
101 * tag.
102 */
103 private static final String DEF_LIST_TAG = "*/definition/put-list-attribute";
104
105 /**
106 * Intercepts a <add-attribute> tag.
107 */
108 private static final String ADD_LIST_ELE_TAG = "*/add-attribute";
109
110 /**
111 * Intercepts a <add-list-attribute> tag.
112 */
113 private static final String NESTED_LIST = "*/add-list-attribute";
114
115 /**
116 * Intercepts a <item> tag.
117 */
118 private static final String ADD_WILDCARD = "*/item";
119
120 /**
121 * Intercepts a <bean> 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 }