Coverage Report - org.apache.tiles.definition.digester.DigesterDefinitionsReader
 
Classes in this File Line Coverage Branch Coverage Complexity
DigesterDefinitionsReader
97%
71/73
75%
9/12
2.4
DigesterDefinitionsReader$1
N/A
N/A
2.4
DigesterDefinitionsReader$AddNestedDefinitionRule
100%
8/8
50%
1/2
2.4
DigesterDefinitionsReader$FillAttributeRule
100%
8/8
N/A
2.4
DigesterDefinitionsReader$FillDefinitionRule
100%
17/17
83%
5/6
2.4
DigesterDefinitionsReader$PutAttributeRule
100%
5/5
N/A
2.4
DigesterDefinitionsReader$ThrowingErrorHandler
75%
3/4
N/A
2.4
 
 1  
 /*
 2  
  * $Id: DigesterDefinitionsReader.java 990237 2010-08-27 19:33:35Z 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>&quot;true&quot;</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: 990237 $ $Date: 2010-08-28 05:33:35 +1000 (Sat, 28 Aug 2010) $
 68  
  */
 69  7
 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 &lt;definition&gt; tag.
 81  
      */
 82  
     private static final String DEFINITION_TAG = "tiles-definitions/definition";
 83  
 
 84  
     /**
 85  
      * Intercepts a &lt;put-attribute&gt; tag.
 86  
      */
 87  
     private static final String PUT_TAG = "*/definition/put-attribute";
 88  
 
 89  
     /**
 90  
      * Intercepts a &lt;definition&gt; inside a  &lt;put-attribute&gt; tag.
 91  
      */
 92  
     private static final String PUT_DEFINITION_TAG = "*/put-attribute/definition";
 93  
 
 94  
     /**
 95  
      * Intercepts a &lt;definition&gt; inside an &lt;add-attribute&gt; tag.
 96  
      */
 97  
     private static final String ADD_DEFINITION_TAG = "*/add-attribute/definition";
 98  
 
 99  
     /**
 100  
      * Intercepts a &lt;put-list-attribute&gt; tag inside a %lt;definition&gt;
 101  
      * tag.
 102  
      */
 103  
     private static final String DEF_LIST_TAG = "*/definition/put-list-attribute";
 104  
 
 105  
     /**
 106  
      * Intercepts a &lt;add-attribute&gt; tag.
 107  
      */
 108  
     private static final String ADD_LIST_ELE_TAG = "*/add-attribute";
 109  
 
 110  
     /**
 111  
      * Intercepts a &lt;add-list-attribute&gt; tag.
 112  
      */
 113  
     private static final String NESTED_LIST = "*/add-list-attribute";
 114  
 
 115  
     // Handler class names.
 116  
 
 117  
     /**
 118  
      * The handler to create definitions.
 119  
      *
 120  
      * @since 2.1.0
 121  
      */
 122  1
     protected static final String DEFINITION_HANDLER_CLASS =
 123  
         Definition.class.getName();
 124  
 
 125  
     /**
 126  
      * The handler to create attributes.
 127  
      *
 128  
      * @since 2.1.0
 129  
      */
 130  1
     protected static final String PUT_ATTRIBUTE_HANDLER_CLASS =
 131  
         Attribute.class.getName();
 132  
 
 133  
     /**
 134  
      * The handler to create list attributes.
 135  
      *
 136  
      * @since 2.1.0
 137  
      */
 138  1
     protected static final String LIST_HANDLER_CLASS =
 139  
         ListAttribute.class.getName();
 140  
 
 141  
     /**
 142  
      * Digester rule to manage definition filling.
 143  
      *
 144  
      * @since 2.1.2
 145  
      */
 146  93
     public static class FillDefinitionRule extends Rule {
 147  
 
 148  
         /** {@inheritDoc} */
 149  
         @Override
 150  
         public void begin(String namespace, String name, Attributes attributes) {
 151  412
             Definition definition = (Definition) digester.peek();
 152  412
             definition.setName(attributes.getValue("name"));
 153  412
             definition.setPreparer(attributes.getValue("preparer"));
 154  412
             String extendsAttribute = attributes.getValue("extends");
 155  412
             definition.setExtends(extendsAttribute);
 156  
 
 157  412
             String template = attributes.getValue("template");
 158  412
             Attribute attribute = Attribute.createTemplateAttribute(template);
 159  412
             attribute.setExpressionObject(Expression
 160  
                     .createExpressionFromDescribedExpression(attributes
 161  
                             .getValue("templateExpression")));
 162  412
             attribute.setRole(attributes.getValue("role"));
 163  412
             String templateType = attributes.getValue("templateType");
 164  412
             if (templateType != null) {
 165  6
                 attribute.setRenderer(templateType);
 166  406
             } else if (extendsAttribute != null && templateType == null) {
 167  32
                 attribute.setRenderer(null);
 168  
             }
 169  412
             definition.setTemplateAttribute(attribute);
 170  412
         }
 171  
     }
 172  
 
 173  
     /**
 174  
      * Digester rule to manage attribute filling.
 175  
      *
 176  
      * @since 2.1.0
 177  
      */
 178  62
     public static class FillAttributeRule extends Rule {
 179  
 
 180  
         /** {@inheritDoc} */
 181  
         @Override
 182  
         public void begin(String namespace, String name, Attributes attributes) {
 183  2011
             Attribute attribute = (Attribute) digester.peek();
 184  2011
             attribute.setValue(attributes.getValue("value"));
 185  2011
             String expression = attributes.getValue("expression");
 186  2011
             attribute.setExpressionObject(Expression
 187  
                     .createExpressionFromDescribedExpression(expression));
 188  2011
             attribute.setRole(attributes.getValue("role"));
 189  2011
             attribute.setRenderer(attributes.getValue("type"));
 190  2011
         }
 191  
     }
 192  
 
 193  
     /**
 194  
      * Digester rule to manage assignment of the attribute to the parent
 195  
      * element.
 196  
      *
 197  
      * @since 2.1.0
 198  
      */
 199  62
     public static class PutAttributeRule extends Rule {
 200  
 
 201  
         /** {@inheritDoc} */
 202  
         @Override
 203  
         public void begin(String namespace, String name, Attributes attributes) {
 204  2006
             Attribute attribute = (Attribute) digester.peek(0);
 205  2006
             Definition definition = (Definition) digester.peek(1);
 206  2006
             definition.putAttribute(attributes.getValue("name"), attribute,
 207  
                     "true".equals(attributes.getValue("cascade")));
 208  2006
         }
 209  
     }
 210  
 
 211  
     /**
 212  
      * Digester rule to manage assignment of a nested definition in an attribute
 213  
      * value.
 214  
      *
 215  
      * @since 2.1.0
 216  
      */
 217  62
     public class AddNestedDefinitionRule extends Rule {
 218  
 
 219  
         /** {@inheritDoc} */
 220  
         @Override
 221  
         public void begin(String namespace, String name, Attributes attributes) {
 222  7
             Definition definition = (Definition) digester.peek(0);
 223  7
             if (definition.getName() == null) {
 224  7
                 definition.setName(getNextUniqueDefinitionName(definitions));
 225  
             }
 226  7
             Attribute attribute = (Attribute) digester.peek(1);
 227  7
             attribute.setValue(definition.getName());
 228  7
             attribute.setRenderer("definition");
 229  7
         }
 230  
     }
 231  
 
 232  
     /**
 233  
      * <code>Digester</code> object used to read Definition data
 234  
      * from the source.
 235  
      */
 236  
     protected Digester digester;
 237  
 
 238  
     /**
 239  
      * The set of public identifiers, and corresponding resource names for
 240  
      * the versions of the configuration file DTDs we know about.  There
 241  
      * <strong>MUST</strong> be an even number of Strings in this list!
 242  
      */
 243  
     protected String[] registrations;
 244  
 
 245  
     /**
 246  
      * Stores Definition objects.
 247  
      */
 248  
     private Map<String, Definition> definitions;
 249  
 
 250  
     /**
 251  
      * Index to be used to create unique definition names for anonymous
 252  
      * (nested) definitions.
 253  
      */
 254  31
     private int anonymousDefinitionIndex = 1;
 255  
 
 256  
     /**
 257  
      * Creates a new instance of DigesterDefinitionsReader.
 258  
      */
 259  31
     public DigesterDefinitionsReader() {
 260  31
         digester = new Digester();
 261  31
         digester.setNamespaceAware(true);
 262  31
         digester.setUseContextClassLoader(true);
 263  31
         digester.setErrorHandler(new ThrowingErrorHandler());
 264  
 
 265  
         // Register our local copy of the DTDs that we can find
 266  31
         String[] registrations = getRegistrations();
 267  62
         for (int i = 0; i < registrations.length; i += 2) {
 268  31
             URL url = this.getClass().getResource(
 269  
                 registrations[i + 1]);
 270  31
             if (url != null) {
 271  31
                 digester.register(registrations[i], url.toString());
 272  
             }
 273  
         }
 274  
 
 275  31
         initSyntax(digester);
 276  31
     }
 277  
 
 278  
     /**
 279  
      * Sets the validation of XML files.
 280  
      *
 281  
      * @param validating <code>true</code> means that XML validation is turned
 282  
      * on. <code>false</code> otherwise.
 283  
      * @since 3.3.0
 284  
      */
 285  
     public void setValidating(boolean validating) {
 286  1
         digester.setValidating(validating);
 287  1
     }
 288  
 
 289  
     /**
 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 definitions
 295  
      *               will be read.
 296  
      * @return a Map of <code>Definition</code> objects read from
 297  
      *         the source.
 298  
      * @throws DefinitionsFactoryException If the source is invalid or
 299  
      *          an error occurs when reading definitions.
 300  
      */
 301  
     public Map<String, Definition> read(Object source) {
 302  
         // This is an instance variable instead of a local variable because
 303  
         // 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  184
         definitions = new LinkedHashMap<String, Definition>();
 306  
 
 307  184
         if (source == null) {
 308  
             // Perhaps we should throw an exception here.
 309  1
             return null;
 310  
         }
 311  
 
 312  
         InputStream input;
 313  
         try {
 314  183
             input = (InputStream) source;
 315  1
         } catch (ClassCastException e) {
 316  1
             throw new DefinitionsFactoryException(
 317  
                 "Invalid source type.  Requires java.io.InputStream.", e);
 318  182
         }
 319  
 
 320  
         try {
 321  
             // set first object in stack
 322  
             //digester.clear();
 323  182
             digester.push(this);
 324  
             // parse
 325  182
             digester.parse(input);
 326  
 
 327  2
         } catch (SAXException e) {
 328  2
             throw new DefinitionsFactoryException(
 329  
                 "XML error reading definitions.", e);
 330  0
         } catch (IOException e) {
 331  0
             throw new DefinitionsFactoryException(
 332  
                 "I/O Error reading definitions.", e);
 333  
         } finally {
 334  182
             digester.clear();
 335  180
         }
 336  
 
 337  180
         return definitions;
 338  
     }
 339  
 
 340  
     /**
 341  
      * Initialised the syntax for reading XML files containing Tiles
 342  
      * definitions.
 343  
      *
 344  
      * @param digester The digester to initialize.
 345  
      */
 346  
     protected void initSyntax(Digester digester) {
 347  31
         initDigesterForTilesDefinitionsSyntax(digester);
 348  31
     }
 349  
 
 350  
 
 351  
     /**
 352  
      * Init digester for Tiles syntax with first element = tiles-definitions.
 353  
      *
 354  
      * @param digester Digester instance to use.
 355  
      */
 356  
     private void initDigesterForTilesDefinitionsSyntax(Digester digester) {
 357  
         // syntax rules
 358  31
         digester.addObjectCreate(DEFINITION_TAG, DEFINITION_HANDLER_CLASS);
 359  31
         digester.addRule(DEFINITION_TAG, new FillDefinitionRule());
 360  31
         digester.addSetNext(DEFINITION_TAG, "addDefinition", DEFINITION_HANDLER_CLASS);
 361  
 
 362  
         // nested definition rules
 363  31
         digester.addObjectCreate(PUT_DEFINITION_TAG, DEFINITION_HANDLER_CLASS);
 364  31
         digester.addRule(PUT_DEFINITION_TAG, new FillDefinitionRule());
 365  31
         digester.addSetRoot(PUT_DEFINITION_TAG, "addDefinition");
 366  31
         digester.addRule(PUT_DEFINITION_TAG, new AddNestedDefinitionRule());
 367  31
         digester.addObjectCreate(ADD_DEFINITION_TAG, DEFINITION_HANDLER_CLASS);
 368  31
         digester.addRule(ADD_DEFINITION_TAG, new FillDefinitionRule());
 369  31
         digester.addSetRoot(ADD_DEFINITION_TAG, "addDefinition");
 370  31
         digester.addRule(ADD_DEFINITION_TAG, new AddNestedDefinitionRule());
 371  
 
 372  
         // put / putAttribute rules
 373  
         // Rules for a same pattern are called in order, but rule.end() are called
 374  
         // in reverse order.
 375  
         // SetNext and CallMethod use rule.end() method. So, placing SetNext in
 376  
         // first position ensure it will be called last (sic).
 377  31
         digester.addObjectCreate(PUT_TAG, PUT_ATTRIBUTE_HANDLER_CLASS);
 378  31
         digester.addRule(PUT_TAG, new FillAttributeRule());
 379  31
         digester.addRule(PUT_TAG, new PutAttributeRule());
 380  
         // Definition level list rules
 381  
         // This is rules for lists nested in a definition
 382  31
         digester.addObjectCreate(DEF_LIST_TAG, LIST_HANDLER_CLASS);
 383  31
         digester.addSetProperties(DEF_LIST_TAG);
 384  31
         digester.addRule(DEF_LIST_TAG, new PutAttributeRule());
 385  
         // list elements rules
 386  
         // We use Attribute class to avoid rewriting a new class.
 387  
         // Name part can't be used in listElement attribute.
 388  31
         digester.addObjectCreate(ADD_LIST_ELE_TAG, PUT_ATTRIBUTE_HANDLER_CLASS);
 389  31
         digester.addRule(ADD_LIST_ELE_TAG, new FillAttributeRule());
 390  31
         digester.addSetNext(ADD_LIST_ELE_TAG, "add", PUT_ATTRIBUTE_HANDLER_CLASS);
 391  
 
 392  
         // nested list elements rules
 393  
         // Create a list handler, and add it to parent list
 394  31
         digester.addObjectCreate(NESTED_LIST, LIST_HANDLER_CLASS);
 395  31
         digester.addSetProperties(NESTED_LIST);
 396  31
         digester.addSetNext(NESTED_LIST, "add", PUT_ATTRIBUTE_HANDLER_CLASS);
 397  31
     }
 398  
 
 399  
     /**
 400  
      * Adds a new <code>Definition</code> to the internal Map or replaces
 401  
      * an existing one.
 402  
      *
 403  
      * @param definition The Definition object to be added.
 404  
      */
 405  
     public void addDefinition(Definition definition) {
 406  413
         String name = definition.getName();
 407  413
         if (name == null) {
 408  1
             throw new DigesterDefinitionsReaderException(
 409  
                     "A root definition has been defined with no name");
 410  
         }
 411  
 
 412  412
         definitions.put(name, definition);
 413  412
     }
 414  
 
 415  
     /**
 416  
      * Error Handler that throws every exception it receives.
 417  
      */
 418  62
     private static class ThrowingErrorHandler implements ErrorHandler {
 419  
 
 420  
         /** {@inheritDoc} */
 421  
         public void warning(SAXParseException exception) throws SAXException {
 422  0
             throw exception;
 423  
         }
 424  
 
 425  
         /** {@inheritDoc} */
 426  
         public void error(SAXParseException exception) throws SAXException {
 427  1
             throw exception;
 428  
         }
 429  
 
 430  
         /** {@inheritDoc} */
 431  
         public void fatalError(SAXParseException exception) throws SAXException {
 432  1
             throw exception;
 433  
         }
 434  
     }
 435  
 
 436  
     /**
 437  
      * Returns the registrations for local DTDs.
 438  
      *
 439  
      * @return An array containing the locations for registrations of local
 440  
      * DTDs.
 441  
      * @since 2.1.0
 442  
      */
 443  
     protected String[] getRegistrations() {
 444  31
         if (registrations == null) {
 445  31
             registrations = new String[] {
 446  
                 "-//Apache Software Foundation//DTD Tiles Configuration 3.0//EN",
 447  
                 "/org/apache/tiles/resources/tiles-config_3_0.dtd"};
 448  
         }
 449  31
         return registrations;
 450  
     }
 451  
 
 452  
     /**
 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.0
 458  
      */
 459  
     protected String getNextUniqueDefinitionName(
 460  
             Map<String, Definition> definitions) {
 461  
         String candidate;
 462  
 
 463  
         do {
 464  7
             candidate = "$anonymousDefinition" + anonymousDefinitionIndex;
 465  7
             anonymousDefinitionIndex++;
 466  7
         } while (definitions.containsKey(candidate));
 467  
 
 468  7
         return candidate;
 469  
     }
 470  
 }