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 }