1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
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
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
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
307
308
309 definitions = new LinkedHashMap<String, Definition>();
310
311 if (source == null) {
312
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
326
327 digester.push(this);
328
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
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
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
395
396
397
398
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
404
405 digester.addObjectCreate(DEF_LIST_TAG, LIST_HANDLER_CLASS);
406 digester.addSetProperties(DEF_LIST_TAG);
407 digester.addRule(DEF_LIST_TAG, new PutAttributeRule());
408
409
410
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
417
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
423
424
425
426
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
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
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 }