Home » tiles-2.2.1-src » org.apache » tiles » definition » digester » [javadoc | source]

    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>&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: 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 &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       /**
  116        * Intercepts a &lt;item&gt; tag.
  117        */
  118       private static final String ADD_WILDCARD = "*/item";
  119   
  120       /**
  121        * Intercepts a &lt;bean&gt; 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   }

Home » tiles-2.2.1-src » org.apache » tiles » definition » digester » [javadoc | source]