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 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>"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: 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
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_TAG + "/put-attribute";
88
89 /***
90 * Intercepts a <put-list-attribute> tag.
91 */
92 private static final String LIST_TAG = "put-list-attribute";
93
94 /***
95 * Intercepts a <put-list-attribute> tag inside a %lt;definition>
96 * tag.
97 */
98 private static final String DEF_LIST_TAG = DEFINITION_TAG + "/" + LIST_TAG;
99
100 /***
101 * Intercepts a <add-attribute> tag.
102 */
103 private static final String ADD_LIST_ELE_TAG = "*/add-attribute";
104
105 /***
106 * Intercepts a <add-list-attribute> tag.
107 */
108 private static final String NESTED_LIST = "*/add-list-attribute";
109
110 /***
111 * Intercepts a <item> tag.
112 */
113 private static final String ADD_WILDCARD = "*/item";
114
115 /***
116 * Intercepts a <bean> tag.
117 */
118 private static final String BEAN_TAG = "*/bean";
119
120
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
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
239 if (!inited) {
240 throw new DefinitionsFactoryException(
241 "Definitions reader has not been initialized.");
242 }
243
244
245
246
247 definitions = new HashMap<String, Definition>();
248
249 if (source == null) {
250
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
264
265 digester.push(this);
266
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
312 digester.addObjectCreate(DEFINITION_TAG, DEFINITION_HANDLER_CLASS);
313 digester.addSetProperties(DEFINITION_TAG);
314 digester.addSetNext(DEFINITION_TAG, "addDefinition", DEFINITION_HANDLER_CLASS);
315
316
317
318
319
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
325
326 digester.addObjectCreate(DEF_LIST_TAG, LIST_HANDLER_CLASS);
327 digester.addSetProperties(DEF_LIST_TAG);
328 digester.addRule(DEF_LIST_TAG, new PutAttributeRule());
329
330
331
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
338
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
344
345
346
347
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
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
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 }