Package de.odysseus.calyxo.base.conf.impl

Source Code of de.odysseus.calyxo.base.conf.impl.RootConfigImplParser

/*
* Copyright 2004, 2005, 2006 Odysseus Software GmbH
*
* Licensed 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 de.odysseus.calyxo.base.conf.impl;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.apache.commons.digester.Digester;
import org.apache.commons.digester.Rule;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.xml.sax.Attributes;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;

import de.odysseus.calyxo.base.ModuleContext;
import de.odysseus.calyxo.base.conf.ConfigException;
import de.odysseus.calyxo.base.conf.ImportConfig;
import de.odysseus.calyxo.base.util.PropertyUtils;

/**
* A parser base class.
* <p/>
* The parser parses from xml configuration files using the
* commons digester. The files are merged together into the final
* configuration. Variables are resolved before merging trees, so
* local scope is limited to a file.
* <p/>
* Subclasses have to implement methods for root and digester creation.
* The parser parses from several inputs.
*
* @author Christoph Beck
*/
public abstract class RootConfigImplParser {
  private static final Log log = LogFactory.getLog(RootConfigImplParser.class);

  /**
   * Resolve schema location.
   * The schema definition systemId is matched against the
   * file part of the resolver's path. That is, a schema resolver
   * with path <code>/org/my/app/myschema.xsd</code> will match
   * all systemIds ending with <code>myschema.xsd</code>.
   */
  public static class SchemaResolver implements EntityResolver {
    private static final String basePath =
      "/de/odysseus/calyxo/base/conf/impl/calyxo-base-config.xsd";
    private static final String baseSuffix =
      basePath.substring(basePath.lastIndexOf('/') + 1);
    private final String suffix;
    private final String uri;
    public SchemaResolver(String path) {
      suffix = path.substring(path.lastIndexOf('/') + 1);
      uri = getClass().getResource(path).toString();
    }
    public InputSource resolveEntity(String publicId, String systemId)
      throws SAXException, IOException {
      if (publicId == null && systemId != null) {
        if (systemId.endsWith(suffix)) {
          return new InputSource(uri);
        }
        if (systemId.endsWith(baseSuffix)) {
          return new InputSource(getClass().getResource(basePath).toString());
        }
      }
      return null;
    }
    public String getURI() {
      return uri;
    }
  }

  /**
   * Resolve schema location by associating a public id with an uri.
   * If the passed publicId is equal to the resolver's publicId,
   * resolve it to the uri.
   */
  public static class DTDResolver implements EntityResolver {
    private static final String baseDTDPublicId =
      "-//Odysseus Software GmbH//DTD Calyxo Base 0.9//EN";
    private static final String baseDTDPath =
      "/de/odysseus/calyxo/base/conf/impl/calyxo-base-config.dtd";
    private final String publicId;
    private final String path;
    public DTDResolver(String publicId, String path) {
      this.publicId = publicId;
      this.path = path;
    }
    public InputSource resolveEntity(String publicId, String systemId)
      throws SAXException, IOException {
      if (publicId != null) {
        if (publicId.equals(this.publicId)) {
          return new InputSource(getClass().getResource(path).toString());
        }
        if (publicId.equals(baseDTDPublicId)) {
          return new InputSource(getClass().getResource(baseDTDPath).toString());
        }
      }
      return null;
    }   
  }

 
  /**
   * This rule assigns a generic unique id to the top object.
   */
  protected class UniqueIdRule extends Rule {
    private String prefix;
    private String property;
    private int index = 1;

    /**
     * Constructor
     */
    public UniqueIdRule(String prefix, String property) {
      super();
   
      this.prefix = prefix;
      this.property = property;
    }

    /**
     * Assign new id to top object.
     */
    public void begin(String namespace, String name, Attributes attributes) throws Exception {

      String id = prefix + index++;

      if (digester.getLogger().isDebugEnabled()) {
        digester.getLogger().debug(
            "[UniqueIdRule]{" + digester.getMatch() +
            "} Set " + property + " to " + id);
      }
      PropertyUtils.setProperty(digester.peek(), property, id);
    }
  }

  private SAXParserFactory factory;
  private ModuleContext context;
  private DTDResolver dtdResolver;
  private SchemaResolver schemaResolver;

  /**
   * Constructor.
   * Since no context is provided, context-relative files cannot
   * be imported using absolute paths like <code>/WEB-INF/foo.xml</code>
   * when using this constructor.
   */
  public RootConfigImplParser() {
    this(null);
  }

  /**
   * Constructor.
   * @param context Used to lookup absolute import paths.
   */
  public RootConfigImplParser(ModuleContext context) {
    this.context = context;

    dtdResolver = getDtdResolver();
    schemaResolver = getSchemaResolver();

    factory = SAXParserFactory.newInstance();
    factory.setNamespaceAware(true);
    factory.setValidating(dtdResolver != null || schemaResolver != null);
  }

  protected void addBaseRuleInstances(Digester digester) {
    digester.setRuleNamespaceURI("http://calyxo.odysseus.de/xml/ns/base");
   
    // Handle import elements
    digester.addObjectCreate("*/import", ImportConfigImpl.class.getName());
    digester.addSetProperties("*/import");
    digester.addSetNext("*/import", "add", ImportConfigImpl.class.getName());

    // Handle functions elements
    digester.addObjectCreate("*/functions", FunctionsConfigImpl.class.getName());
    digester.addSetProperties("*/functions", "class", "className");
    digester.addSetNext("*/functions", "add", FunctionsConfigImpl.class.getName());

    // Handle set elements
    digester.addObjectCreate("*/set", SetConfigImpl.class.getName());
    digester.addSetProperties("*/set", "scope", "scopeName");
    digester.addSetNext("*/set", "add", SetConfigImpl.class.getName());

    // Handle use elements
    digester.addObjectCreate("*/use", UseConfigImpl.class.getName());
    digester.addSetProperties("*/use");
    digester.addSetNext("*/use", "add", UseConfigImpl.class.getName());

    // Handle property elements
    digester.addObjectCreate("*/property", PropertyConfigImpl.class.getName());
    digester.addSetProperties("*/property");
    digester.addSetNext("*/property", "add", PropertyConfigImpl.class.getName());

    // Handle method elements
    digester.addObjectCreate("*/use/method", MethodConfigImpl.class.getName());
    digester.addSetNext("*/use/method", "add", MethodConfigImpl.class.getName());
    digester.addSetProperties("*/use/method");

    digester.addObjectCreate("*/object/method", MethodConfigImpl.class.getName());
    digester.addSetNext("*/object/method", "add", MethodConfigImpl.class.getName());
    digester.addSetProperties("*/object/method");

    digester.addObjectCreate("*/member/method", MethodConfigImpl.class.getName());
    digester.addSetNext("*/member/method", "setMethodConfig", MethodConfigImpl.class.getName());
    digester.addSetProperties("*/member/method");

    // Handle field elements
    digester.addObjectCreate("*/member/field", FieldConfigImpl.class.getName());
    digester.addSetNext("*/member/field", "setFieldConfig", FieldConfigImpl.class.getName());
    digester.addSetProperties("*/member/field");

    // Handle object elements
    digester.addObjectCreate("*/object", ObjectConfigImpl.class.getName());
    digester.addSetProperties("*/object", "class", "className");
    digester.addSetNext("*/object", "setObjectConfig", ObjectConfigImpl.class.getName());

    // Handle constructor elements
    digester.addObjectCreate("*/constructor", ConstructorConfigImpl.class.getName());
    digester.addSetNext("*/constructor", "setConstructorConfig", ConstructorConfigImpl.class.getName());
    digester.addSetProperties("*/constructor");

    // Handle member elements
    digester.addObjectCreate("*/member", MemberConfigImpl.class.getName());
    digester.addSetProperties("*/member", "class", "className");
    digester.addSetNext("*/member", "setMemberConfig", MemberConfigImpl.class.getName());

    // Handle arg elements
    digester.addObjectCreate("*/arg", ArgConfigImpl.class.getName());
    digester.addSetProperties("*/arg", "class", "className");
    digester.addSetNext("*/arg", "add", ArgConfigImpl.class.getName());
  }
 
  /**
   * Create digester with validating, namespace aware parser.
   * This method has been added, since configuration for
   * schema validation seems to be broken in digester 1.6.
   * Therefore, we directly set the JAXP properties.
   */
  protected Digester createDigester() {
    SAXParser parser = null;
    try {
      parser = factory.newSAXParser();
    } catch (Exception e) {
      // should not happen...
    }
    if (schemaResolver != null) {
      try {
        parser.setProperty(
          "http://java.sun.com/xml/jaxp/properties/schemaLanguage",
          "http://www.w3.org/2001/XMLSchema"
        );
        parser.setProperty(
          "http://java.sun.com/xml/jaxp/properties/schemaSource",
          getSchemaResolver().getURI()
        );
      } catch (Exception e) {
        log.info("Parser doesn't support XML Schema validation, using DTD...");
        log.debug(e);
      }
    }
    final Digester digester = new Digester(parser);
    digester.setLogger(getLog());
    digester.setClassLoader(context.getClassLoader());
    digester.setErrorHandler(new DefaultHandler() {
      public void error(SAXParseException e) throws SAXException {
        throw e;
      }
      public void warning(SAXParseException e) throws SAXException {
        getLog().warn(e.getMessage());
      }
    });
    digester.setEntityResolver(new EntityResolver() {
      public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
        InputSource result = null;
        if (schemaResolver != null) {
          result = schemaResolver.resolveEntity(publicId, systemId);
        }
        if (result == null && dtdResolver != null) {
          result = dtdResolver.resolveEntity(publicId, systemId);
        }
        if (result != null) {
          return result;
        }
        return digester.resolveEntity(publicId, systemId);
      }
    });

    addBaseRuleInstances(digester);
    addRuleInstances(digester);

    return digester;
  }

  /**
   * Create root element
   */
  protected abstract RootConfigImpl createRoot();

  /**
   * Get the dtd resolver. If there's no dtd support, return null.
   * To be implemented by subclasses.
   */
  protected abstract DTDResolver getDtdResolver();

  /**
   * Get the schema resolver. If there's no schema support, return null.
   * To be implemented by subclasses.
   */
  protected abstract SchemaResolver getSchemaResolver();

  /**
   * Add rules
   */
  protected abstract void addRuleInstances(Digester digester);

  /**
   * Get the log instance to be passed to the digester.
   */
  protected Log getLog() {
    return log;
  }

  private RootConfigImpl parse(URL url) throws ConfigException {
    Digester digester = createDigester();   
    RootConfigImpl root = createRoot();
    digester.push(root);
    log.debug("Parsing: " + url);
    try {
      InputSource input = new InputSource(url.openStream());
      input.setSystemId(url.toString());
      digester.parse(input);
    } catch (Exception e) {
      throw new ConfigException("Error parsing " + url, e);
    }

    // Parse imported files
    Iterator imports = root.getImportConfigs();
    ArrayList children = null;
    if (imports.hasNext()) {
      children = new ArrayList();
      while (imports.hasNext()) {
        String file = ((ImportConfig)imports.next()).getFile();
        URL childURL = null;
        try {
          if (file.startsWith("/")) {
            if (context != null) {
              childURL = context.getServletContext().getResource(file);
            }
            if (childURL == null) {
              childURL = getClass().getResource(file);
            }
          } else {
            childURL = new URL(url, file);
          }
          if (childURL == null) {
            throw new ConfigException("Could not find file: '" + file + "'");
          }
        } catch (MalformedURLException e) {
          throw new ConfigException("Bad file path: '" + file + "'", e);
        }
        children.add(parse(childURL));
      }
    }

    root.resolve(context);

    if (children != null) {
      for (int i = 0; i < children.size(); i++) {
        root.merge((RootConfigImpl)children.get(i));
      }
    }

    return root;
  }

  /**
   * Parse specified urls merge and initialize resulting trees
   * @param urls pointing to xml configuration files
   * @throws ConfigException on error during parse or initialization
   */
  protected RootConfigImpl parseAll(URL[] urls) throws ConfigException {
    RootConfigImpl root = parse(urls[0]);
    for (int i = 1; i < urls.length; i++) {
      root.merge(parse(urls[i]));
    }
    root.initialize();
    if (log.isDebugEnabled()) {
      log.debug(root.toXML());
    }
    return root;
  }
}
TOP

Related Classes of de.odysseus.calyxo.base.conf.impl.RootConfigImplParser

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.