Package org.apache.tiles.definition.digester

Source Code of org.apache.tiles.definition.digester.DigesterDefinitionsReader$PutAttributeRule

/*
* $Id: DigesterDefinitionsReader.java 672635 2008-06-29 16:24:19Z apetrelli $
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.  See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership.  The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License.  You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied.  See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package org.apache.tiles.definition.digester;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.digester.Digester;
import org.apache.commons.digester.Rule;
import org.apache.tiles.Attribute;
import org.apache.tiles.Definition;
import org.apache.tiles.ListAttribute;
import org.apache.tiles.definition.DefinitionsFactoryException;
import org.apache.tiles.definition.DefinitionsReader;
import org.xml.sax.Attributes;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

/**
* Reads {@link Definition} objects from
* an XML InputStream using Digester. <p/>
* <p>
* This <code>DefinitionsReader</code> implementation expects the source to be
* passed as an <code>InputStream</code>. It parses XML data from the source
* and builds a Map of Definition objects.
* </p>
* <p/>
* <p>
* The Digester object can be configured by passing in initialization
* parameters. Currently the only parameter that is supported is the
* <code>validating</code> parameter. This value is set to <code>false</code>
* by default. To enable DTD validation for XML Definition files, give the init
* method a parameter with a key of
* <code>org.apache.tiles.definition.digester.DigesterDefinitionsReader.PARSER_VALIDATE</code>
* and a value of <code>&quot;true&quot;</code>. <p/>
* <p>
* The Definition objects are stored internally in a Map. The Map is stored as
* an instance variable rather than a local variable in the <code>read</code>
* method. This means that instances of this class are <strong>not</strong>
* thread-safe and access by multiple threads must be synchronized.
* </p>
*
* @version $Rev: 672635 $ $Date: 2008-06-29 18:24:19 +0200 (Sun, 29 Jun 2008) $
*/
public class DigesterDefinitionsReader implements DefinitionsReader {

    /**
     * Digester validation parameter name.
     */
    public static final String PARSER_VALIDATE_PARAMETER_NAME =
        "org.apache.tiles.definition.digester.DigesterDefinitionsReader.PARSER_VALIDATE";

    // Digester rules constants for tag interception.

    /**
     * Intercepts a &lt;definition&gt; tag.
     */
    private static final String DEFINITION_TAG = "tiles-definitions/definition";

    /**
     * Intercepts a &lt;put-attribute&gt; tag.
     */
    private static final String PUT_TAG = "*/definition/put-attribute";

    /**
     * Intercepts a &lt;definition&gt; inside a  &lt;put-attribute&gt; tag.
     */
    private static final String PUT_DEFINITION_TAG = "*/put-attribute/definition";

    /**
     * Intercepts a &lt;definition&gt; inside an &lt;add-attribute&gt; tag.
     */
    private static final String ADD_DEFINITION_TAG = "*/add-attribute/definition";

    /**
     * Intercepts a &lt;put-list-attribute&gt; tag.
     */
    private static final String LIST_TAG = "put-list-attribute";

    /**
     * Intercepts a &lt;put-list-attribute&gt; tag inside a %lt;definition&gt;
     * tag.
     */
    private static final String DEF_LIST_TAG = DEFINITION_TAG + "/" + LIST_TAG;

    /**
     * Intercepts a &lt;add-attribute&gt; tag.
     */
    private static final String ADD_LIST_ELE_TAG = "*/add-attribute";

    /**
     * Intercepts a &lt;add-list-attribute&gt; tag.
     */
    private static final String NESTED_LIST = "*/add-list-attribute";

    /**
     * Intercepts a &lt;item&gt; tag.
     */
    private static final String ADD_WILDCARD = "*/item";

    /**
     * Intercepts a &lt;bean&gt; tag.
     */
    private static final String BEAN_TAG = "*/bean";

    // Handler class names.

    /**
     * The handler to create definitions.
     *
     * @since 2.1.0
     */
    protected static final String DEFINITION_HANDLER_CLASS =
        Definition.class.getName();

    /**
     * The handler to create attributes.
     *
     * @since 2.1.0
     */
    protected static final String PUT_ATTRIBUTE_HANDLER_CLASS =
        Attribute.class.getName();

    /**
     * The handler to create list attributes.
     *
     * @since 2.1.0
     */
    protected static final String LIST_HANDLER_CLASS =
        ListAttribute.class.getName();

    /**
     * Digester rule to manage attribute filling.
     *
     * @since 2.1.0
     */
    public static class FillAttributeRule extends Rule {

        /** {@inheritDoc} */
        @Override
        public void begin(String namespace, String name, Attributes attributes)
                throws Exception {
            Attribute attribute = (Attribute) digester.peek();
            attribute.setValue(attributes.getValue("value"));
            attribute.setRole(attributes.getValue("role"));
            attribute.setRenderer(attributes.getValue("type"));
        }
    }

    /**
     * Digester rule to manage assignment of the attribute to the parent
     * element.
     *
     * @since 2.1.0
     */
    public static class PutAttributeRule extends Rule {

        /** {@inheritDoc} */
        @Override
        public void begin(String namespace, String name, Attributes attributes)
                throws Exception {
            Attribute attribute = (Attribute) digester.peek(0);
            Definition definition = (Definition) digester.peek(1);
            definition.putAttribute(attributes.getValue("name"), attribute,
                    "true".equals(attributes.getValue("cascade")));
        }
    }

    /**
     * Digester rule to manage assignment of a nested definition in an attribute
     * value.
     *
     * @since 2.1.0
     */
    public class AddNestedDefinitionRule extends Rule {

        /** {@inheritDoc} */
        @Override
        public void begin(String namespace, String name, Attributes attributes)
                throws Exception {
            Definition definition = (Definition) digester.peek(0);
            if (definition.getName() == null) {
                definition.setName(getNextUniqueDefinitionName(definitions));
            }
            Attribute attribute = (Attribute) digester.peek(1);
            attribute.setValue(definition.getName());
            attribute.setRenderer("definition");
        }
    }

    /**
     * <code>Digester</code> object used to read Definition data
     * from the source.
     */
    protected Digester digester;
    /**
     * Stores Definition objects.
     */
    private Map<String, Definition> definitions;
    /**
     * Should we use a validating XML parser to read the configuration file.
     * Default is <code>true</code>.
     */
    protected boolean validating = true;
    /**
     * The set of public identifiers, and corresponding resource names for
     * the versions of the configuration file DTDs we know about.  There
     * <strong>MUST</strong> be an even number of Strings in this list!
     */
    protected String[] registrations;

    /**
     * Index to be used to create unique definition names for anonymous
     * (nested) definitions.
     */
    private int anonymousDefinitionIndex = 1;

    /**
     * Creates a new instance of DigesterDefinitionsReader.
     */
    public DigesterDefinitionsReader() {
        digester = new Digester();
        digester.setValidating(validating);
        digester.setNamespaceAware(true);
        digester.setUseContextClassLoader(true);
        digester.setErrorHandler(new ThrowingErrorHandler());

        // Register our local copy of the DTDs that we can find
        String[] registrations = getRegistrations();
        for (int i = 0; i < registrations.length; i += 2) {
            URL url = this.getClass().getResource(
                registrations[i + 1]);
            if (url != null) {
                digester.register(registrations[i], url.toString());
            }
        }

        initSyntax(digester);
    }

    /**
     * Reads <code>{@link Definition}</code> objects from a source.
     * <p/>
     * Implementations should publish what type of source object is expected.
     *
     * @param source The <code>InputStream</code> source from which definitions
     *               will be read.
     * @return a Map of <code>Definition</code> objects read from
     *         the source.
     * @throws org.apache.tiles.definition.DefinitionsFactoryException
     *          if the source is invalid or
     *          an error occurs when reading definitions.
     */
    public Map<String, Definition> read(Object source) {
        // This is an instance variable instead of a local variable because
        // we want to be able to call the addDefinition method to populate it.
        // But we reset the Map here, which, of course, has threading implications.
        definitions = new HashMap<String, Definition>();

        if (source == null) {
            // Perhaps we should throw an exception here.
            return null;
        }

        InputStream input;
        try {
            input = (InputStream) source;
        } catch (ClassCastException e) {
            throw new DefinitionsFactoryException(
                "Invalid source type.  Requires java.io.InputStream.", e);
        }

        try {
            // set first object in stack
            //digester.clear();
            digester.push(this);
            // parse
            digester.parse(input);

        } catch (SAXException e) {
            throw new DefinitionsFactoryException(
                "XML error reading definitions.", e);
        } catch (IOException e) {
            throw new DefinitionsFactoryException(
                "I/O Error reading definitions.", e);
        } finally {
            digester.clear();
        }

        return definitions;
    }

    /**
     * Initializes the <code>DefinitionsReader</code> object.
     * <p/>
     * This method must be called before the {@link #read} method is called.
     *
     * @param params A map of properties used to set up the reader.
     * @throws org.apache.tiles.definition.DefinitionsFactoryException
     *          if required properties are not
     *          passed in or the initialization fails.
     */
    public void init(Map<String, String> params) {
        if (params != null) {
            String value = params.get(PARSER_VALIDATE_PARAMETER_NAME);
            if (value != null) {
                digester.setValidating(Boolean.valueOf(value));
            }
        }
    }

    /**
     * Initialised the syntax for reading XML files containing Tiles
     * definitions.
     *
     * @param digester The digester to initialize.
     */
    protected void initSyntax(Digester digester) {
        initDigesterForTilesDefinitionsSyntax(digester);
    }


    /**
     * Init digester for Tiles syntax with first element = tiles-definitions.
     *
     * @param digester Digester instance to use.
     */
    private void initDigesterForTilesDefinitionsSyntax(Digester digester) {
        // syntax rules
        digester.addObjectCreate(DEFINITION_TAG, DEFINITION_HANDLER_CLASS);
        digester.addSetProperties(DEFINITION_TAG);
        digester.addSetNext(DEFINITION_TAG, "addDefinition", DEFINITION_HANDLER_CLASS);

        // nested definition rules
        digester.addObjectCreate(PUT_DEFINITION_TAG, DEFINITION_HANDLER_CLASS);
        digester.addSetProperties(PUT_DEFINITION_TAG);
        digester.addSetRoot(PUT_DEFINITION_TAG, "addDefinition");
        digester.addRule(PUT_DEFINITION_TAG, new AddNestedDefinitionRule());
        digester.addObjectCreate(ADD_DEFINITION_TAG, DEFINITION_HANDLER_CLASS);
        digester.addSetProperties(ADD_DEFINITION_TAG);
        digester.addSetRoot(ADD_DEFINITION_TAG, "addDefinition");
        digester.addRule(ADD_DEFINITION_TAG, new AddNestedDefinitionRule());

        // put / putAttribute rules
        // Rules for a same pattern are called in order, but rule.end() are called
        // in reverse order.
        // SetNext and CallMethod use rule.end() method. So, placing SetNext in
        // first position ensure it will be called last (sic).
        digester.addObjectCreate(PUT_TAG, PUT_ATTRIBUTE_HANDLER_CLASS);
        digester.addRule(PUT_TAG, new FillAttributeRule());
        digester.addRule(PUT_TAG, new PutAttributeRule());
        digester.addCallMethod(PUT_TAG, "setBody", 0);
        // Definition level list rules
        // This is rules for lists nested in a definition
        digester.addObjectCreate(DEF_LIST_TAG, LIST_HANDLER_CLASS);
        digester.addSetProperties(DEF_LIST_TAG);
        digester.addRule(DEF_LIST_TAG, new PutAttributeRule());
        // list elements rules
        // We use Attribute class to avoid rewriting a new class.
        // Name part can't be used in listElement attribute.
        digester.addObjectCreate(ADD_LIST_ELE_TAG, PUT_ATTRIBUTE_HANDLER_CLASS);
        digester.addRule(ADD_LIST_ELE_TAG, new FillAttributeRule());
        digester.addSetNext(ADD_LIST_ELE_TAG, "add", PUT_ATTRIBUTE_HANDLER_CLASS);
        digester.addCallMethod(ADD_LIST_ELE_TAG, "setBody", 0);

        // nested list elements rules
        // Create a list handler, and add it to parent list
        digester.addObjectCreate(NESTED_LIST, LIST_HANDLER_CLASS);
        digester.addSetProperties(NESTED_LIST);
        digester.addSetNext(NESTED_LIST, "add", PUT_ATTRIBUTE_HANDLER_CLASS);

        // item elements rules
        // We use Attribute class to avoid rewriting a new class.
        // Name part can't be used in listElement attribute.
        //String ADD_WILDCARD = LIST_TAG + "/addItem";
        // non String ADD_WILDCARD = LIST_TAG + "/addx*";
        String menuItemDefaultClass = "org.apache.tiles.beans.SimpleMenuItem";
        digester.addObjectCreate(ADD_WILDCARD, menuItemDefaultClass, "classtype");
        digester.addSetNext(ADD_WILDCARD, "add", "java.lang.Object");
        digester.addSetProperties(ADD_WILDCARD);

        // bean elements rules
        String beanDefaultClass = "org.apache.tiles.beans.SimpleMenuItem";
        digester.addObjectCreate(BEAN_TAG, beanDefaultClass, "classtype");
        digester.addSetProperties(BEAN_TAG);
        digester.addSetNext(BEAN_TAG, "add", "java.lang.Object");

        // Set properties to surrounding element
        digester.addSetProperty(BEAN_TAG + "/set-property", "property", "value");
    }

    /**
     * Adds a new <code>Definition</code> to the internal Map or replaces
     * an existing one.
     *
     * @param definition The Definition object to be added.
     */
    public void addDefinition(Definition definition) {
        String name = definition.getName();
        if (name == null) {
            throw new DigesterDefinitionsReaderException(
                    "A root definition has been defined with no name");
        }

        definitions.put(name, definition);
    }

    /**
     * Error Handler that throws every exception it receives.
     */
    private static class ThrowingErrorHandler implements ErrorHandler {

        /** {@inheritDoc} */
        public void warning(SAXParseException exception) throws SAXException {
            throw exception;
        }

        /** {@inheritDoc} */
        public void error(SAXParseException exception) throws SAXException {
            throw exception;
        }

        /** {@inheritDoc} */
        public void fatalError(SAXParseException exception) throws SAXException {
            throw exception;
        }
    }

    /**
     * Returns the registrations for local DTDs.
     *
     * @return An array containing the locations for registrations of local
     * DTDs.
     * @since 2.1.0
     */
    protected String[] getRegistrations() {
        if (registrations == null) {
            registrations = new String[] {
                "-//Apache Software Foundation//DTD Tiles Configuration 2.0//EN",
                "/org/apache/tiles/resources/tiles-config_2_0.dtd",
                "-//Apache Software Foundation//DTD Tiles Configuration 2.1//EN",
                "/org/apache/tiles/resources/tiles-config_2_1.dtd"};
        }
        return registrations;
    }

    /**
     * Create a unique definition name usable to store anonymous definitions.
     *
     * @param definitions The already created definitions.
     * @return The unique definition name to be used to store the definition.
     * @since 2.1.0
     */
    protected String getNextUniqueDefinitionName(
            Map<String, Definition> definitions) {
        String candidate;

        do {
            candidate = "$anonymousDefinition" + anonymousDefinitionIndex;
            anonymousDefinitionIndex++;
        } while (definitions.containsKey(candidate));

        return candidate;
    }
}
TOP

Related Classes of org.apache.tiles.definition.digester.DigesterDefinitionsReader$PutAttributeRule

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.