1/*2 * $Id: DigesterDefinitionsReader.java 990237 2010-08-27 19:33:35Z 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: 990237 $ $Date: 2010-08-28 05:33:35 +1000 (Sat, 28 Aug 2010) $68 */69publicclassDigesterDefinitionsReaderimplementsDefinitionsReader {
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// Handler class names.116117/**118 * The handler to create definitions.119 *120 * @since 2.1.0121 */122protectedstaticfinal String DEFINITION_HANDLER_CLASS =
123 Definition.class.getName();
124125/**126 * The handler to create attributes.127 *128 * @since 2.1.0129 */130protectedstaticfinal String PUT_ATTRIBUTE_HANDLER_CLASS =
131 Attribute.class.getName();
132133/**134 * The handler to create list attributes.135 *136 * @since 2.1.0137 */138protectedstaticfinal String LIST_HANDLER_CLASS =
139 ListAttribute.class.getName();
140141/**142 * Digester rule to manage definition filling.143 *144 * @since 2.1.2145 */146publicstaticclassFillDefinitionRuleextends Rule {
147148/** {@inheritDoc} */149 @Override
150publicvoid begin(String namespace, String name, Attributes attributes) {
151Definition definition = (Definition) digester.peek();
152 definition.setName(attributes.getValue("name"));
153 definition.setPreparer(attributes.getValue("preparer"));
154 String extendsAttribute = attributes.getValue("extends");
155 definition.setExtends(extendsAttribute);
156157 String template = attributes.getValue("template");
158Attribute attribute = Attribute.createTemplateAttribute(template);
159 attribute.setExpressionObject(Expression160 .createExpressionFromDescribedExpression(attributes
161 .getValue("templateExpression")));
162 attribute.setRole(attributes.getValue("role"));
163 String templateType = attributes.getValue("templateType");
164if (templateType != null) {
165 attribute.setRenderer(templateType);
166 } elseif (extendsAttribute != null && templateType == null) {
167 attribute.setRenderer(null);
168 }
169 definition.setTemplateAttribute(attribute);
170 }
171 }
172173/**174 * Digester rule to manage attribute filling.175 *176 * @since 2.1.0177 */178publicstaticclassFillAttributeRuleextends Rule {
179180/** {@inheritDoc} */181 @Override
182publicvoid begin(String namespace, String name, Attributes attributes) {
183Attribute attribute = (Attribute) digester.peek();
184 attribute.setValue(attributes.getValue("value"));
185 String expression = attributes.getValue("expression");
186 attribute.setExpressionObject(Expression187 .createExpressionFromDescribedExpression(expression));
188 attribute.setRole(attributes.getValue("role"));
189 attribute.setRenderer(attributes.getValue("type"));
190 }
191 }
192193/**194 * Digester rule to manage assignment of the attribute to the parent195 * element.196 *197 * @since 2.1.0198 */199publicstaticclassPutAttributeRuleextends Rule {
200201/** {@inheritDoc} */202 @Override
203publicvoid begin(String namespace, String name, Attributes attributes) {
204Attribute attribute = (Attribute) digester.peek(0);
205Definition definition = (Definition) digester.peek(1);
206 definition.putAttribute(attributes.getValue("name"), attribute,
207"true".equals(attributes.getValue("cascade")));
208 }
209 }
210211/**212 * Digester rule to manage assignment of a nested definition in an attribute213 * value.214 *215 * @since 2.1.0216 */217publicclassAddNestedDefinitionRuleextends Rule {
218219/** {@inheritDoc} */220 @Override
221publicvoid begin(String namespace, String name, Attributes attributes) {
222Definition definition = (Definition) digester.peek(0);
223if (definition.getName() == null) {
224 definition.setName(getNextUniqueDefinitionName(definitions));
225 }
226Attribute attribute = (Attribute) digester.peek(1);
227 attribute.setValue(definition.getName());
228 attribute.setRenderer("definition");
229 }
230 }
231232/**233 * <code>Digester</code> object used to read Definition data234 * from the source.235 */236protected Digester digester;
237238/**239 * The set of public identifiers, and corresponding resource names for240 * the versions of the configuration file DTDs we know about. There241 * <strong>MUST</strong> be an even number of Strings in this list!242 */243protected String[] registrations;
244245/**246 * Stores Definition objects.247 */248private Map<String, Definition> definitions;
249250/**251 * Index to be used to create unique definition names for anonymous252 * (nested) definitions.253 */254privateint anonymousDefinitionIndex = 1;
255256/**257 * Creates a new instance of DigesterDefinitionsReader.258 */259publicDigesterDefinitionsReader() {
260 digester = new Digester();
261 digester.setNamespaceAware(true);
262 digester.setUseContextClassLoader(true);
263 digester.setErrorHandler(newThrowingErrorHandler());
264265// Register our local copy of the DTDs that we can find266 String[] registrations = getRegistrations();
267for (int i = 0; i < registrations.length; i += 2) {
268 URL url = this.getClass().getResource(
269 registrations[i + 1]);
270if (url != null) {
271 digester.register(registrations[i], url.toString());
272 }
273 }
274275 initSyntax(digester);
276 }
277278/**279 * Sets the validation of XML files.280 *281 * @param validating <code>true</code> means that XML validation is turned282 * on. <code>false</code> otherwise.283 * @since 3.3.0284 */285publicvoid setValidating(boolean validating) {
286 digester.setValidating(validating);
287 }
288289/**290 * Reads <code>{@link Definition}</code> objects from a source.291 * <p/>292 * Implementations should publish what type of source object is expected.293 *294 * @param source The <code>InputStream</code> source from which definitions295 * will be read.296 * @return a Map of <code>Definition</code> objects read from297 * the source.298 * @throws DefinitionsFactoryException If the source is invalid or299 * an error occurs when reading definitions.300 */301public Map<String, Definition> read(Object source) {
302// This is an instance variable instead of a local variable because303// we want to be able to call the addDefinition method to populate it.304// But we reset the Map here, which, of course, has threading implications.305 definitions = new LinkedHashMap<String, Definition>();
306307if (source == null) {
308// Perhaps we should throw an exception here.309returnnull;
310 }
311312 InputStream input;
313try {
314 input = (InputStream) source;
315 } catch (ClassCastException e) {
316thrownewDefinitionsFactoryException(
317"Invalid source type. Requires java.io.InputStream.", e);
318 }
319320try {
321// set first object in stack322//digester.clear();323 digester.push(this);
324// parse325 digester.parse(input);
326327 } catch (SAXException e) {
328thrownewDefinitionsFactoryException(
329"XML error reading definitions.", e);
330 } catch (IOException e) {
331thrownewDefinitionsFactoryException(
332"I/O Error reading definitions.", e);
333 } finally {
334 digester.clear();
335 }
336337return definitions;
338 }
339340/**341 * Initialised the syntax for reading XML files containing Tiles342 * definitions.343 *344 * @param digester The digester to initialize.345 */346protectedvoid initSyntax(Digester digester) {
347 initDigesterForTilesDefinitionsSyntax(digester);
348 }
349350351/**352 * Init digester for Tiles syntax with first element = tiles-definitions.353 *354 * @param digester Digester instance to use.355 */356privatevoid initDigesterForTilesDefinitionsSyntax(Digester digester) {
357// syntax rules358 digester.addObjectCreate(DEFINITION_TAG, DEFINITION_HANDLER_CLASS);
359 digester.addRule(DEFINITION_TAG, newFillDefinitionRule());
360 digester.addSetNext(DEFINITION_TAG, "addDefinition", DEFINITION_HANDLER_CLASS);
361362// nested definition rules363 digester.addObjectCreate(PUT_DEFINITION_TAG, DEFINITION_HANDLER_CLASS);
364 digester.addRule(PUT_DEFINITION_TAG, newFillDefinitionRule());
365 digester.addSetRoot(PUT_DEFINITION_TAG, "addDefinition");
366 digester.addRule(PUT_DEFINITION_TAG, newAddNestedDefinitionRule());
367 digester.addObjectCreate(ADD_DEFINITION_TAG, DEFINITION_HANDLER_CLASS);
368 digester.addRule(ADD_DEFINITION_TAG, newFillDefinitionRule());
369 digester.addSetRoot(ADD_DEFINITION_TAG, "addDefinition");
370 digester.addRule(ADD_DEFINITION_TAG, newAddNestedDefinitionRule());
371372// put / putAttribute rules373// Rules for a same pattern are called in order, but rule.end() are called374// in reverse order.375// SetNext and CallMethod use rule.end() method. So, placing SetNext in376// first position ensure it will be called last (sic).377 digester.addObjectCreate(PUT_TAG, PUT_ATTRIBUTE_HANDLER_CLASS);
378 digester.addRule(PUT_TAG, newFillAttributeRule());
379 digester.addRule(PUT_TAG, newPutAttributeRule());
380// Definition level list rules381// This is rules for lists nested in a definition382 digester.addObjectCreate(DEF_LIST_TAG, LIST_HANDLER_CLASS);
383 digester.addSetProperties(DEF_LIST_TAG);
384 digester.addRule(DEF_LIST_TAG, newPutAttributeRule());
385// list elements rules386// We use Attribute class to avoid rewriting a new class.387// Name part can't be used in listElement attribute.388 digester.addObjectCreate(ADD_LIST_ELE_TAG, PUT_ATTRIBUTE_HANDLER_CLASS);
389 digester.addRule(ADD_LIST_ELE_TAG, newFillAttributeRule());
390 digester.addSetNext(ADD_LIST_ELE_TAG, "add", PUT_ATTRIBUTE_HANDLER_CLASS);
391392// nested list elements rules393// Create a list handler, and add it to parent list394 digester.addObjectCreate(NESTED_LIST, LIST_HANDLER_CLASS);
395 digester.addSetProperties(NESTED_LIST);
396 digester.addSetNext(NESTED_LIST, "add", PUT_ATTRIBUTE_HANDLER_CLASS);
397 }
398399/**400 * Adds a new <code>Definition</code> to the internal Map or replaces401 * an existing one.402 *403 * @param definition The Definition object to be added.404 */405publicvoid addDefinition(Definition definition) {
406 String name = definition.getName();
407if (name == null) {
408thrownewDigesterDefinitionsReaderException(
409"A root definition has been defined with no name");
410 }
411412 definitions.put(name, definition);
413 }
414415/**416 * Error Handler that throws every exception it receives.417 */418privatestaticclassThrowingErrorHandlerimplements ErrorHandler {
419420/** {@inheritDoc} */421publicvoid warning(SAXParseException exception) throws SAXException {
422throw exception;
423 }
424425/** {@inheritDoc} */426publicvoid error(SAXParseException exception) throws SAXException {
427throw exception;
428 }
429430/** {@inheritDoc} */431publicvoid fatalError(SAXParseException exception) throws SAXException {
432throw exception;
433 }
434 }
435436/**437 * Returns the registrations for local DTDs.438 *439 * @return An array containing the locations for registrations of local440 * DTDs.441 * @since 2.1.0442 */443protected String[] getRegistrations() {
444if (registrations == null) {
445 registrations = new String[] {
446"-//Apache Software Foundation//DTD Tiles Configuration 3.0//EN",
447"/org/apache/tiles/resources/tiles-config_3_0.dtd"};
448 }
449return registrations;
450 }
451452/**453 * Create a unique definition name usable to store anonymous definitions.454 *455 * @param definitions The already created definitions.456 * @return The unique definition name to be used to store the definition.457 * @since 2.1.0458 */459protected String getNextUniqueDefinitionName(
460 Map<String, Definition> definitions) {
461 String candidate;
462463do {
464 candidate = "$anonymousDefinition" + anonymousDefinitionIndex;
465 anonymousDefinitionIndex++;
466 } while (definitions.containsKey(candidate));
467468return candidate;
469 }
470 }