/* * 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.jackrabbit.core.config; import org.w3c.dom.Attr; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.EntityResolver; import org.xml.sax.ErrorHandler; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import org.apache.jackrabbit.util.Text; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Properties; /** * Configuration parser base class. This class provides the basic * functionality for parsing Jackrabbit configuration files. Subclasses * extend this functionality with knowledge of the exact structure of the * different configuration files. Each configuration parser instance * contains a set of parser variables that are used for variable replacement * in the configuration file. */ public class ConfigurationParser { /** Name of the bean parameter configuration element. */ public static final String PARAM_ELEMENT = "param"; /** Name of the bean implementation class configuration attribute. */ public static final String CLASS_ATTRIBUTE = "class"; /** Name of the bean parameter name configuration attribute. */ public static final String NAME_ATTRIBUTE = "name"; /** Name of the bean parameter value configuration attribute. */ public static final String VALUE_ATTRIBUTE = "value"; /** * The configuration parser variables. These name-value pairs * are used to substitute <code>${...}</code> variable references * with context-dependent values in the configuration. * * @see #replaceVariables(String) */ private final Properties variables; /** * Creates a new configuration parser with the given parser variables. * * @param variables parser variables */ public ConfigurationParser(Properties variables) { this.variables = variables; } /** * Returns the variables. * @return the variables. */ public Properties getVariables() { return variables; } /** * Parses a named bean configuration from the given element. * Bean configuration uses the following format: * <pre> * <BeanName class="..."> * <param name="..." value="..."/> * ... * </BeanName> * </pre> * <p> * The returned bean configuration object contains the configured * class name and configuration parameters. Variable replacement * is performed on the parameter values. * * @param parent parent element * @param name name of the bean configuration element * @return bean configuration, * @throws ConfigurationException if the configuration element does not * exist or is broken */ protected BeanConfig parseBeanConfig(Element parent, String name) throws ConfigurationException { // Bean configuration element Element element = getElement(parent, name); return parseBeanConfig(element); } /** * Parses a named bean configuration from the given element. * Bean configuration uses the following format: * <pre> * <BeanName class="..."> * <param name="..." value="..."/> * ... * </BeanName> * </pre> * <p> * The returned bean configuration object contains the configured * class name and configuration parameters. Variable replacement * is performed on the parameter values. * * @param element * @return bean configuration, * @throws ConfigurationException if the configuration element does not * exist or is broken */ protected BeanConfig parseBeanConfig(Element element) throws ConfigurationException { // Bean implementation class String className = getAttribute(element, CLASS_ATTRIBUTE); // Bean properties Properties properties = parseParameters(element); return new BeanConfig(className, properties); } /** * Parses the configuration parameters of the given element. * Parameters are stored as * <code><param name="..." value="..."/></code> * child elements. This method parses all param elements, * performs {@link #replaceVariables(String) variable replacement} * on parameter values, and returns the resulting name-value pairs. * * @param element configuration element * @return configuration parameters * @throws ConfigurationException if a <code>param</code> element does * not contain the <code>name</code> and * <code>value</code> attributes */ protected Properties parseParameters(Element element) throws ConfigurationException { Properties parameters = new Properties(); NodeList children = element.getChildNodes(); for (int i = 0; i < children.getLength(); i++) { Node child = children.item(i); if (child.getNodeType() == Node.ELEMENT_NODE && PARAM_ELEMENT.equals(child.getNodeName())) { Element parameter = (Element) child; Attr name = parameter.getAttributeNode(NAME_ATTRIBUTE); if (name == null) { throw new ConfigurationException("Parameter name not set"); } Attr value = parameter.getAttributeNode(VALUE_ATTRIBUTE); if (value == null) { throw new ConfigurationException("Parameter value not set"); } parameters.put( name.getValue().trim(), replaceVariables(value.getValue())); } } return parameters; } /** * Performs variable replacement on the given string value. * Each <code>${...}</code> sequence within the given value is replaced * with the value of the named parser variable. The replacement is not * done if the named variable does not exist. * * @param value original value * @return value after variable replacements * @throws ConfigurationException if the replacement of a referenced * variable is not found */ protected String replaceVariables(String value) throws ConfigurationException { try { return Text.replaceVariables(variables, value, false); } catch (IllegalArgumentException e) { throw new ConfigurationException(e.getMessage(), e); } } /** * Parses the given XML document and returns the DOM root element. * A custom entity resolver is used to make the included configuration * file DTD available using the specified public identifiers. * This implementation does not validate the XML. * * @see ConfigurationEntityResolver * @param xml xml document * @return root element * @throws ConfigurationException if the configuration document could * not be read or parsed */ protected Element parseXML(InputSource xml) throws ConfigurationException { return parseXML(xml, false); } /** * Returns the error handler to be used when parsing configuration * documents. Subclasses can override this method to provide custom * error handling. * * @since Apache Jackrabbit 2.0 * @return error handler */ protected ErrorHandler getErrorHandler() { return new ConfigurationErrorHandler(); } /** * Returns the entity resolver to be used when parsing configuration * documents. Subclasses can override this method to provide custom * entity resolution rules. * * @since Apache Jackrabbit 2.0 * @return error handler */ protected EntityResolver getEntityResolver() { return ConfigurationEntityResolver.INSTANCE; } /** * A post-processing hook for the parsed repository or workspace * configuration documents. This hook makes it possible to make custom * DOM modifications for backwards-compatibility or other reasons. * * @since Apache Jackrabbit 2.0 * @param document the parsed configuration document * @return the configuration document after any modifications */ protected Document postParseModificationHook(Document document) { return document; } /** * Parses the given XML document and returns the DOM root element. * A custom entity resolver is used to make the included configuration * file DTD available using the specified public identifiers. * * @see ConfigurationEntityResolver * @param xml xml document * @param validate whether the XML should be validated * @return root element * @throws ConfigurationException if the configuration document could * not be read or parsed */ protected Element parseXML(InputSource xml, boolean validate) throws ConfigurationException { try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setValidating(validate); DocumentBuilder builder = factory.newDocumentBuilder(); if (validate) { builder.setErrorHandler(getErrorHandler()); } builder.setEntityResolver(getEntityResolver()); Document document = builder.parse(xml); return postParseModificationHook(document).getDocumentElement(); } catch (ParserConfigurationException e) { throw new ConfigurationException( "Unable to create configuration XML parser", e); } catch (SAXParseException e) { throw new ConfigurationException( "Configuration file syntax error. (Line: " + e.getLineNumber() + " Column: " + e.getColumnNumber() + ")", e); } catch (SAXException e) { throw new ConfigurationException( "Configuration file syntax error. ", e); } catch (IOException e) { throw new ConfigurationException( "Configuration file could not be read.", e); } } /** * Returns the named child of the given parent element. * * @param parent parent element * @param name name of the child element * @return named child element * @throws ConfigurationException * @throws ConfigurationException if the child element is not found */ protected Element getElement(Element parent, String name) throws ConfigurationException { return getElement(parent, name, true); } /** * Returns the named child of the given parent element. * * @param parent parent element * @param name name of the child element * @param required indicates if the child element is required * @return named child element, or <code>null</code> if not found and * <code>required</code> is <code>false</code>. * @throws ConfigurationException if the child element is not found and * <code>required</code> is <code>true</code>; * or if more than one element with this name exists. */ protected Element getElement(Element parent, String name, boolean required) throws ConfigurationException { NodeList children = parent.getChildNodes(); Element found = null; for (int i = 0; i < children.getLength(); i++) { Node child = children.item(i); if (child.getNodeType() == Node.ELEMENT_NODE && name.equals(child.getNodeName())) { if (found != null) { throw new ConfigurationException( "Duplicate configuration element " + name + " in " + parent.getNodeName() + "."); } found = (Element) child; } } if (required && found == null) { throw new ConfigurationException( "Configuration element " + name + " not found in " + parent.getNodeName() + "."); } return found; } /** * Returns the named child of the given parent element. * * @param parent parent element * @param name name of the child element * @param required indicates if the child element is required * @return named child element, or <code>null</code> if not found and * <code>required</code> is <code>false</code>. * @throws ConfigurationException if the child element is not found and * <code>required</code> is <code>true</code>; * or if more than one element with this name exists. */ protected Element[] getElements(Element parent, String name, boolean required) throws ConfigurationException { NodeList children = parent.getChildNodes(); List<Element> found = new ArrayList<Element>(); for (int i = 0; i < children.getLength(); i++) { Node child = children.item(i); if (child.getNodeType() == Node.ELEMENT_NODE && name.equals(child.getNodeName())) { found.add((Element) child); } } if (required && found.isEmpty()) { throw new ConfigurationException( "Configuration element " + name + " not found in " + parent.getNodeName() + "."); } return found.toArray(new Element[found.size()]); } /** * Returns the value of the named attribute of the given element. * * @param element element * @param name attribute name * @return attribute value * @throws ConfigurationException if the attribute is not found */ protected String getAttribute(Element element, String name) throws ConfigurationException { Attr attribute = element.getAttributeNode(name); if (attribute != null) { return attribute.getValue(); } else { throw new ConfigurationException( "Configuration attribute " + name + " not found in " + element.getNodeName() + "."); } } /** * Returns the value of the named attribute of the given element. * If the attribute is not found, then the given default value is returned. * * @param element element * @param name attribute name * @param def default value * @return attribute value, or the default value */ protected String getAttribute(Element element, String name, String def) { Attr attribute = element.getAttributeNode(name); if (attribute != null) { return attribute.getValue(); } else { return def; } } }