1/*2 * $Id: DigesterDefinitionsReader.java 788032 2009-06-24 14:08:32Z apetrelli $3 *4 * Licensed to the Apache Software Foundation (ASF) under one5 * or more contributor license agreements. See the NOTICE file6 * distributed with this work for additional information7 * regarding copyright ownership. The ASF licenses this file8 * to you under the Apache License, Version 2.0 (the9 * "License"); you may not use this file except in compliance10 * with the License. You may obtain a copy of the License at11 *12 * http://www.apache.org/licenses/LICENSE-2.013 *14 * Unless required by applicable law or agreed to in writing,15 * software distributed under the License is distributed on an16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY17 * KIND, either express or implied. See the License for the18 * specific language governing permissions and limitations19 * under the License.20 */2122package org.apache.tiles.definition.digester;
2324import java.io.IOException;
25import java.io.InputStream;
26import java.net.URL;
27import java.util.LinkedHashMap;
28import java.util.Map;
2930import org.apache.commons.digester.Digester;
31import org.apache.commons.digester.Rule;
32import org.apache.tiles.Attribute;
33import org.apache.tiles.Definition;
34import org.apache.tiles.Expression;
35import org.apache.tiles.ListAttribute;
36import org.apache.tiles.definition.DefinitionsFactoryException;
37import org.apache.tiles.definition.DefinitionsReader;
38import org.xml.sax.Attributes;
39import org.xml.sax.ErrorHandler;
40import org.xml.sax.SAXException;
41import org.xml.sax.SAXParseException;
4243/***44 * Reads {@link Definition} objects from45 * an XML InputStream using Digester. <p/>46 * <p>47 * This <code>DefinitionsReader</code> implementation expects the source to be48 * passed as an <code>InputStream</code>. It parses XML data from the source49 * and builds a Map of Definition objects.50 * </p>51 * <p/>52 * <p>53 * The Digester object can be configured by passing in initialization54 * parameters. Currently the only parameter that is supported is the55 * <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 init57 * method a parameter with a key of58 * <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 as62 * 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 */69publicclassDigesterDefinitionsReader implements DefinitionsReader {
7071/***72 * Digester validation parameter name.73 */74publicstaticfinal String PARSER_VALIDATE_PARAMETER_NAME =
75"org.apache.tiles.definition.digester.DigesterDefinitionsReader.PARSER_VALIDATE";
7677// Digester rules constants for tag interception.7879/***80 * Intercepts a <definition> tag.81 */82privatestaticfinal String DEFINITION_TAG = "tiles-definitions/definition";
8384/***85 * Intercepts a <put-attribute> tag.86 */87privatestaticfinal String PUT_TAG = "*/definition/put-attribute";
8889/***90 * Intercepts a <definition> inside a <put-attribute> tag.91 */92privatestaticfinal String PUT_DEFINITION_TAG = "*/put-attribute/definition";
9394/***95 * Intercepts a <definition> inside an <add-attribute> tag.96 */97privatestaticfinal String ADD_DEFINITION_TAG = "*/add-attribute/definition";
9899/***100 * Intercepts a <put-list-attribute> tag inside a %lt;definition>101 * tag.102 */103privatestaticfinal String DEF_LIST_TAG = "*/definition/put-list-attribute";
104105/***106 * Intercepts a <add-attribute> tag.107 */108privatestaticfinal String ADD_LIST_ELE_TAG = "*/add-attribute";
109110/***111 * Intercepts a <add-list-attribute> tag.112 */113privatestaticfinal String NESTED_LIST = "*/add-list-attribute";
114115/***116 * Intercepts a <item> tag.117 */118privatestaticfinal String ADD_WILDCARD = "*/item";
119120/***121 * Intercepts a <bean> tag.122 */123privatestaticfinal String BEAN_TAG = "*/bean";
124125// Handler class names.126127/***128 * The handler to create definitions.129 *130 * @since 2.1.0131 */132protectedstaticfinal String DEFINITION_HANDLER_CLASS =
133 Definition.class.getName();
134135/***136 * The handler to create attributes.137 *138 * @since 2.1.0139 */140protectedstaticfinal String PUT_ATTRIBUTE_HANDLER_CLASS =
141 Attribute.class.getName();
142143/***144 * The handler to create list attributes.145 *146 * @since 2.1.0147 */148protectedstaticfinal String LIST_HANDLER_CLASS =
149 ListAttribute.class.getName();
150151/***152 * Digester rule to manage definition filling.153 *154 * @since 2.1.2155 */156publicstaticclass FillDefinitionRule extends Rule {
157158/*** {@inheritDoc} */159 @Override
160publicvoid 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"));
166167 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");
174if (templateType != null) {
175 attribute.setRenderer(templateType);
176 }
177 definition.setTemplateAttribute(attribute);
178 }
179 }
180181/***182 * Digester rule to manage attribute filling.183 *184 * @since 2.1.0185 */186publicstaticclass FillAttributeRule extends Rule {
187188/*** {@inheritDoc} */189 @Override
190publicvoid 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 }
201202/***203 * Digester rule to manage assignment of the attribute to the parent204 * element.205 *206 * @since 2.1.0207 */208publicstaticclass PutAttributeRule extends Rule {
209210/*** {@inheritDoc} */211 @Override
212publicvoid 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 }
220221/***222 * Digester rule to manage assignment of a nested definition in an attribute223 * value.224 *225 * @since 2.1.0226 */227publicclass AddNestedDefinitionRule extends Rule {
228229/*** {@inheritDoc} */230 @Override
231publicvoid begin(String namespace, String name, Attributes attributes)
232 throws Exception {
233 Definition definition = (Definition) digester.peek(0);
234if (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 }
242243/***244 * <code>Digester</code> object used to read Definition data245 * from the source.246 */247protected Digester digester;
248/***249 * Stores Definition objects.250 */251private 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 */256protectedboolean validating = true;
257/***258 * The set of public identifiers, and corresponding resource names for259 * the versions of the configuration file DTDs we know about. There260 * <strong>MUST</strong> be an even number of Strings in this list!261 */262protected String[] registrations;
263264/***265 * Index to be used to create unique definition names for anonymous266 * (nested) definitions.267 */268privateint anonymousDefinitionIndex = 1;
269270/***271 * Creates a new instance of DigesterDefinitionsReader.272 */273publicDigesterDefinitionsReader() {
274 digester = new Digester();
275 digester.setValidating(validating);
276 digester.setNamespaceAware(true);
277 digester.setUseContextClassLoader(true);
278 digester.setErrorHandler(new ThrowingErrorHandler());
279280// Register our local copy of the DTDs that we can find281 String[] registrations = getRegistrations();
282for (int i = 0; i < registrations.length; i += 2) {
283 URL url = this.getClass().getResource(
284 registrations[i + 1]);
285if (url != null) {
286 digester.register(registrations[i], url.toString());
287 }
288 }
289290 initSyntax(digester);
291 }
292293/***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 definitions299 * will be read.300 * @return a Map of <code>Definition</code> objects read from301 * the source.302 * @throws DefinitionsFactoryException If the source is invalid or303 * an error occurs when reading definitions.304 */305public Map<String, Definition> read(Object source) {
306// This is an instance variable instead of a local variable because307// 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>();
310311if (source == null) {
312// Perhaps we should throw an exception here.313returnnull;
314 }
315316 InputStream input;
317try {
318 input = (InputStream) source;
319 } catch (ClassCastException e) {
320thrownewDefinitionsFactoryException(
321"Invalid source type. Requires java.io.InputStream.", e);
322 }
323324try {
325// set first object in stack326//digester.clear();327 digester.push(this);
328// parse329 digester.parse(input);
330331 } catch (SAXException e) {
332thrownewDefinitionsFactoryException(
333"XML error reading definitions.", e);
334 } catch (IOException e) {
335thrownewDefinitionsFactoryException(
336"I/O Error reading definitions.", e);
337 } finally {
338 digester.clear();
339 }
340341return definitions;
342 }
343344/***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 passed351 * in or the initialization fails.352 */353publicvoid init(Map<String, String> params) {
354if (params != null) {
355 String value = params.get(PARSER_VALIDATE_PARAMETER_NAME);
356if (value != null) {
357 digester.setValidating(Boolean.valueOf(value));
358 }
359 }
360 }
361362/***363 * Initialised the syntax for reading XML files containing Tiles364 * definitions.365 *366 * @param digester The digester to initialize.367 */368protectedvoid initSyntax(Digester digester) {
369 initDigesterForTilesDefinitionsSyntax(digester);
370 }
371372373/***374 * Init digester for Tiles syntax with first element = tiles-definitions.375 *376 * @param digester Digester instance to use.377 */378privatevoid initDigesterForTilesDefinitionsSyntax(Digester digester) {
379// syntax rules380 digester.addObjectCreate(DEFINITION_TAG, DEFINITION_HANDLER_CLASS);
381 digester.addRule(DEFINITION_TAG, new FillDefinitionRule());
382 digester.addSetNext(DEFINITION_TAG, "addDefinition", DEFINITION_HANDLER_CLASS);
383384// nested definition rules385 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());
393394// put / putAttribute rules395// Rules for a same pattern are called in order, but rule.end() are called396// in reverse order.397// SetNext and CallMethod use rule.end() method. So, placing SetNext in398// 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 rules404// This is rules for lists nested in a definition405 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 rules409// 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);
415416// nested list elements rules417// Create a list handler, and add it to parent list418 digester.addObjectCreate(NESTED_LIST, LIST_HANDLER_CLASS);
419 digester.addSetProperties(NESTED_LIST);
420 digester.addSetNext(NESTED_LIST, "add", PUT_ATTRIBUTE_HANDLER_CLASS);
421422// item elements rules423// 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);
431432// bean elements rules433 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");
437438// Set properties to surrounding element439 digester.addSetProperty(BEAN_TAG + "/set-property", "property", "value");
440 }
441442/***443 * Adds a new <code>Definition</code> to the internal Map or replaces444 * an existing one.445 *446 * @param definition The Definition object to be added.447 */448publicvoid addDefinition(Definition definition) {
449 String name = definition.getName();
450if (name == null) {
451thrownewDigesterDefinitionsReaderException(
452"A root definition has been defined with no name");
453 }
454455 definitions.put(name, definition);
456 }
457458/***459 * Error Handler that throws every exception it receives.460 */461privatestaticclass ThrowingErrorHandler implements ErrorHandler {
462463/*** {@inheritDoc} */464publicvoid warning(SAXParseException exception) throws SAXException {
465throw exception;
466 }
467468/*** {@inheritDoc} */469publicvoid error(SAXParseException exception) throws SAXException {
470throw exception;
471 }
472473/*** {@inheritDoc} */474publicvoid fatalError(SAXParseException exception) throws SAXException {
475throw exception;
476 }
477 }
478479/***480 * Returns the registrations for local DTDs.481 *482 * @return An array containing the locations for registrations of local483 * DTDs.484 * @since 2.1.0485 */486protected String[] getRegistrations() {
487if (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 }
494return registrations;
495 }
496497/***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.0503 */504protected String getNextUniqueDefinitionName(
505 Map<String, Definition> definitions) {
506 String candidate;
507508do {
509 candidate = "$anonymousDefinition" + anonymousDefinitionIndex;
510 anonymousDefinitionIndex++;
511 } while (definitions.containsKey(candidate));
512513return candidate;
514 }
515 }