All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.servicerocket.confluence.randombits.conveyor.xwork.ConveyorConfigurationProvider Maven / Gradle / Ivy

There is a newer version: 2.5.12
Show newest version
package com.servicerocket.confluence.randombits.conveyor.xwork;

import com.atlassian.confluence.plugin.descriptor.PluginAwareActionConfig;
import com.atlassian.plugin.Plugin;
import com.atlassian.plugin.PluginParseException;
import com.atlassian.plugin.web.Condition;
import com.opensymphony.util.ClassLoaderUtil;
import com.opensymphony.util.TextUtils;
import com.opensymphony.xwork.ActionSupport;
import com.opensymphony.xwork.ObjectFactory;
import com.opensymphony.xwork.config.*;
import com.opensymphony.xwork.config.entities.*;
import com.opensymphony.xwork.config.providers.XmlConfigurationProvider;
import com.opensymphony.xwork.config.providers.XmlHelper;
import com.opensymphony.xwork.interceptor.Interceptor;
import com.servicerocket.confluence.randombits.conveyor.OverrideManager;
import com.servicerocket.confluence.randombits.conveyor.Receipt;
import org.apache.commons.lang.StringUtils;
import org.jfree.base.modules.PackageManager.PackageConfiguration;
import com.servicerocket.confluence.randombits.conveyor.ConveyorException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;

public class ConveyorConfigurationProvider extends XmlConfigurationProvider {

    private static final Logger LOG = LoggerFactory.getLogger(ConveyorConfigurationProvider.class);

    private static final String DEFAULT_RESOURCE_NAME = "conveyor-context.xml";

    private final OverrideManager overrideManager;

    private final Plugin plugin;

    private String resourceName;

    private Configuration configuration;

    private Set includedFileNames = new java.util.TreeSet();

    private Exception failureException;

    private final ConditionElementParser conditionElementParser;

    private List receipts;

    public ConveyorConfigurationProvider(OverrideManager overrideManager, Plugin plugin, String resourceName) {
        super(resourceName);
        this.overrideManager = overrideManager;
        this.resourceName = resourceName;
        this.plugin = plugin;

        conditionElementParser = new ConditionElementParser(plugin);
        receipts = new ArrayList();
    }

    public ConveyorConfigurationProvider(OverrideManager overrideManager, Plugin plugin) {
        this(overrideManager, plugin, DEFAULT_RESOURCE_NAME);
    }

    public Plugin getPlugin() {
        return plugin;
    }

    // DAN COPIED
    @Override
    protected void addResultTypes(PackageConfig packageContext, Element element) {
        NodeList resultTypeList = element.getElementsByTagName("result-type");

        for (int i = 0; i < resultTypeList.getLength(); i++) {
            Element resultTypeElement = (Element) resultTypeList.item(i);
            String name = resultTypeElement.getAttribute("name");
            String className = resultTypeElement.getAttribute("class");
            String def = resultTypeElement.getAttribute("default");

            try {
                Class clazz = ClassLoaderUtil.loadClass(className, getClass());
                ResultTypeConfig resultType = new ResultTypeConfig(name, clazz);
                packageContext.addResultTypeConfig(resultType);

                // set the default result type
                if ("true".equals(def)) {
                    packageContext.setDefaultResultType(name);
                }
            } catch (ClassNotFoundException e) {
                LOG.error("Result class [" + className + "] doesn't exist, ignoring");
            }
        }
    }

    private void checkElementName(Element element, String name) throws ConveyorException {
        if (!name.equals(element.getNodeName()))
            throw new ConveyorException("Expected element named '" + name + "' but got '" + element.getNodeName()
                    + "'.");
    }

    public static Map copyParams(final Map params) {
        if (params != null) {
            final Map copy = new java.util.HashMap();
            copy.putAll(params);
            return copy;
        } else {
            return null;
        }
    }

    /**
     * Copies the specified result config.
     *
     * @param config The config to copy.
     * @return the copy.
     */
    public static ResultConfig copyResultConfig(final ResultConfig config) {
        return new ResultConfig(config.getName(), config.getClassName(), copyParams(config.getParams()));
    }

    public static Map copyResults(final Map results) {
        if (results != null) {
            Map copy = new java.util.HashMap();

            for (Map.Entry e : results.entrySet()) {
                copy.put(e.getKey(), copyResultConfig(e.getValue()));
            }

            return copy;
        } else {
            return null;
        }
    }

    public static List copyInterceptors(final List interceptors) {
        if (interceptors != null) {
            // Note: Copies the list, but not the actual interceptors.
            return new java.util.ArrayList(interceptors);
        }
        return null;
    }

    public static ExternalReference copyExternalRef(final ExternalReference reference) {
        return new ExternalReference(reference.getName(), reference.getExternalRef(), reference.isRequired());
    }

    public static List copyExternalRefs(final List externalRefs) {
        if (externalRefs != null) {
            final List copy = new java.util.ArrayList(externalRefs.size());
            for (ExternalReference externalRef : externalRefs) {
                copy.add(copyExternalRef(externalRef));
            }
            return copy;
        }
        return null;
    }

    // // ConfigurationProvider methods ////

    @Override
    public void destroy() {
        overrideManager.returnReceipts(receipts);
        receipts.clear();
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }

        if (!(o instanceof ConveyorConfigurationProvider)) {
            return false;
        }

        final ConveyorConfigurationProvider configProvider = (ConveyorConfigurationProvider) o;

        if ((resourceName != null) ? (!resourceName.equals(configProvider.resourceName))
                : (configProvider.resourceName != null)) {
            return false;
        }

        return true;
    }

    @Override
    public int hashCode() {
        return ((resourceName != null) ? resourceName.hashCode() : 0);
    }

    @Override
    public void init(Configuration configuration) {
        this.configuration = configuration;

        // Destroy any lingering references. Plugin XWork actions don't always
        // clean up after themselves.
        destroy();

        DocumentBuilder db;

        try {
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            dbf.setValidating(false);
            dbf.setNamespaceAware(true);

            db = dbf.newDocumentBuilder();

            // TODO: Define an actual DTD
            /*
            db.setEntityResolver( new EntityResolver() {
                public InputSource resolveEntity( String publicId, String systemId ) throws SAXException,
                        IOException {
                    if ( "-//randombits.org//Confluence Conveyor 0.2//EN".equals( publicId ) ) {
                        return new InputSource( ClassLoaderUtil.getResourceAsStream(
                                "confluence-conveyor-0.2.dtd", ConveyorConfigurationProvider.class ) );
                    }

                    return null;
                }
            } );
            */

            db.setErrorHandler(new ErrorHandler() {
                public void warning(SAXParseException exception) throws SAXException {
                }

                public void error(SAXParseException exception) throws SAXException {
                    LOG.error(exception.getMessage() + " at (" + exception.getLineNumber() + ":"
                            + exception.getColumnNumber() + ")");
                    throw exception;
                }

                public void fatalError(SAXParseException exception) throws SAXException {
                    LOG.error(exception.getMessage() + " at (" + exception.getLineNumber() + ":"
                            + exception.getColumnNumber() + ")");
                    throw exception;
                }
            });
            // Clear any old inclusions.
            includedFileNames.clear();
            // Load the file.
            loadConfigurationFile(resourceName, db);
        } catch (RuntimeException e) {
            fail(e);
        } catch (ConveyorException e) {
            fail(e);
        } catch (ParserConfigurationException e) {
            fail(e);
        }
    }

    private void fail(Exception e) {
        LOG.error(e.getMessage(), e);
        this.failureException = e;
    }

    public Exception getFailureException() {
        return failureException;
    }

    public boolean isFailed() {
        return failureException != null;
    }

    private void loadConfigurationFile(String fileName, DocumentBuilder db) throws ConveyorException {
        if (!includedFileNames.contains(fileName)) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("Loading xwork configuration from: " + fileName);
            }

            includedFileNames.add(fileName);

            Document doc = null;
            InputStream is = null;

            try {
                is = getInputStream(fileName);

                if (is == null) {
                    throw new ConveyorException("Could not open file " + fileName);
                }

                doc = db.parse(is);
            } catch (Exception e) {
                final String s = "Caught exception while loading file " + fileName;
                throw new ConveyorException(s, e);
            } finally {
                if (is != null) {
                    try {
                        is.close();
                    } catch (IOException e) {
                        throw new ConveyorException("Unable to close input stream", e);
                    }
                }
            }

            Element rootElement = doc.getDocumentElement();
            checkElementName(rootElement, "conveyor-config");

            NodeList children = rootElement.getChildNodes();
            int childSize = children.getLength();

            for (int i = 0; i < childSize; i++) {
                Node childNode = children.item(i);

                if (childNode instanceof Element) {
                    Element child = (Element) childNode;

                    final String nodeName = child.getNodeName();

                    if (nodeName.equals("package-override")) {
                        addPackageOverride(child);
                    }
                    if (nodeName.equals("package")) {
                        addPackage(child);
                    } else if (nodeName.equals("include")) {
                        String includeFileName = child.getAttribute("file");
                        loadConfigurationFile(includeFileName, db);
                    }
                }
            }

            if (LOG.isDebugEnabled()) {
                LOG.debug("Loaded xwork configuration from: " + fileName);
            }
        }
    }

    @Override
    protected InputStream getInputStream(String fileName) {
        return plugin.getResourceAsStream(fileName);
    }

    /**
     * Tells whether the ConfigurationProvider should reload its configuration.
     * This method should only be called if
     * ConfigurationManager.isReloadingConfigs() is true.
     *
     * @return true if the file has been changed since the last time we read it
     */
    @Override
    public boolean needsReload() {
        return true;
    }

    public String getResourceName() {
        return resourceName;
    }

    public void setResourceName(String resourceName) {
        this.resourceName = resourceName;
    }

    /**
     * Create a PackageConfig from an XML element representing it.
     *
     * @param packageOverrideElement XML of package config.
     * @throws ConveyorException conveyor exception.
     */
    protected void addPackageOverride(Element packageOverrideElement) throws ConveyorException {

        OverridingPackageConfig packageConfig = new OverridingPackageConfig(plugin);

        if (LOG.isDebugEnabled()) {
            LOG.debug("Overridden: " + packageConfig);
        }

        packageConfig.setName(packageOverrideElement.getAttribute("name"));
        packageConfig.setNamespace(packageOverrideElement.getAttribute("namespace"));

        // even though this package will not be added directly to the XWork configuration,
        // we need to set the parent as the 'overridden' package so that result configs, etc can
        // be built correctly.
        packageConfig.addParent(findPackageConfig(packageConfig.getName(), packageConfig.getNamespace()));

        // add result types (and default result) to this package
        addResultTypes(packageConfig, packageOverrideElement);

        // load the interceptors and interceptor stacks for this package
        loadInterceptors(packageConfig, packageOverrideElement);

        // load the global result list for this package
        loadGlobalResults(packageConfig, packageOverrideElement);

        // get action overrides
        NodeList actionOverrideList = packageOverrideElement.getElementsByTagName("action-override");

        for (int i = 0; i < actionOverrideList.getLength(); i++) {
            Element actionOverrideElement = (Element) actionOverrideList.item(i);
            addActionOverride(actionOverrideElement, packageConfig);
        }

        // get actions
        NodeList actionList = packageOverrideElement.getElementsByTagName("action");

        for (int i = 0; i < actionList.getLength(); i++) {
            Element actionElement = (Element) actionList.item(i);
            // Check the action doesn't already exist.
            String name = actionElement.getAttribute("name");

            ActionConfig existing = (ActionConfig) packageConfig.getAllActionConfigs().get(name);
            if (existing != null)
                LOG.error("An action with the specified name already exists in the '" + packageConfig.getName()
                        + "' package: " + name + "; " + existing.getClassName());
            else
                addAction(actionElement, packageConfig);
        }


        // Reset the parent list so we don't get infinite recursion...
        packageConfig.getParents().clear();

        keepReceipt(overrideManager.overridePackage(packageConfig));
    }

    private PackageConfig findPackageConfig(String name, String namespace) throws ConveyorException {
        PackageConfig packageConfig = ConfigurationManager.getConfiguration().getPackageConfig(name);
        if (packageConfig == null) {
            throw new ConveyorException("Unable to find the package being overridden named '" + name + "'.");
        }
        if (!StringUtils.equals(namespace, packageConfig.getNamespace())) {
            throw new ConveyorException("The namespace for the '" + name + "' package is '" + packageConfig.getNamespace() + "' but the override specified '" + namespace + "'");
        }
        return packageConfig;
    }

    protected void addActionOverride(Element actionOverrideElement, OverridingPackageConfig packageConfig)
            throws ConveyorException {

        String name = actionOverrideElement.getAttribute("name");
        String className = actionOverrideElement.getAttribute("class");
        String methodName = actionOverrideElement.getAttribute("method");

        // Extra elements for override
        String inheritAttr = actionOverrideElement.getAttribute("inherit");
        boolean inherit = "true".equals(inheritAttr);
        String key = actionOverrideElement.getAttribute("key");
        int weight = getIntValue(actionOverrideElement.getAttribute("weight"), 0);

        // classname/methodName should be null if not set
        className = (className.trim().length() > 0) ? className.trim() : null;
        methodName = (methodName.trim().length() > 0) ? methodName.trim() : null;

        if (TextUtils.stringSet(className)) {
            try {
                ObjectFactory.getObjectFactory().getClassInstance(className);
            } catch (Exception e) {
                fail("Action class [" + className + "] not found, skipping action [" + name + "]", e);
                return;
            }
        } else if (!inherit) {
            throw new ConveyorException("No class specified for action override: " + name);
        }

        Map actionParams = XmlHelper.getParams(actionOverrideElement);

        Map results;

        try {
            results = buildResults(actionOverrideElement, packageConfig);
        } catch (ConfigurationException e) {
            throw new ConveyorException("Error building results for action " + name + " in '"
                    + packageConfig.getName() + "' package.", e);
        }

        List resultOverrides;
        try {
            resultOverrides = buildResultOverrides(actionOverrideElement, packageConfig);
        } catch (ConfigurationException e) {
            throw new ConveyorException("Error building result overrides for action " + name + " in '"
                    + packageConfig.getName() + "' package.", e);
        }

        List interceptorList = buildInterceptorList(actionOverrideElement, packageConfig);

        List externalRefs = buildExternalRefs(actionOverrideElement, packageConfig);

        Condition condition = makeConditions(actionOverrideElement);

        OverridingActionConfig actionConfig = new OverridingActionConfig(methodName, className,
                actionParams, results, interceptorList, externalRefs, packageConfig.getName(), plugin, inherit, key, weight, condition);

        // Add the overriding results.
        actionConfig.setOverridingResults(resultOverrides);

        // Do the override and add it to the cache of actions created by this provider.
        packageConfig.addOverridingAction(name, actionConfig);

        if (LOG.isDebugEnabled()) {
            LOG
                    .debug("Loaded "
                            + (TextUtils.stringSet(packageConfig.getNamespace()) ? (packageConfig
                            .getNamespace() + "/") : "") + name + " in '" + packageConfig.getName()
                            + "' package:" + actionConfig);
        }
    }

    /**
     * Build a map of ResultConfig objects from below a given XML element.
     *
     * @param element        XML representation of ResultConfig
     * @param packageContext opensymphony packageconfig
     * @return List of OverridingResultConfig
     */
    protected List buildResultOverrides(Element element, PackageConfig packageContext) {
        NodeList resultEls = element.getElementsByTagName("result-override");

        List results = new ArrayList();

        for (int i = 0; i < resultEls.getLength(); i++) {
            Element resultElement = (Element) resultEls.item(i);

            if (resultElement.getParentNode().equals(element)) {
                String resultName = resultElement.getAttribute("name");
                String resultType = resultElement.getAttribute("type");

                if (!TextUtils.stringSet(resultType)) {
                    resultType = packageContext.getFullDefaultResultType();
                }

                ResultTypeConfig config = (ResultTypeConfig) packageContext.getAllResultTypeConfigs().get(resultType);

                if (config == null) {
                    throw new ConfigurationException("There is no result type defined for type '" + resultType + "' mapped with name '" + resultName + "'");
                }

                Class resultClass = config.getClazz();

                // invalid result type specified in result definition
                if (resultClass == null) {
                    LOG.error("Result type '" + resultType + "' is invalid. Modify your xwork.xml file.");
                }

                HashMap params = XmlHelper.getParams(resultElement);

                if (params.size() == 0) // maybe we just have a body - therefore a default parameter
                {
                    // if something then we add a parameter of 'something' as this is the most used result param
                    if ((resultElement.getChildNodes().getLength() == 1) && (resultElement.getChildNodes().item(0).getNodeType() == Node.TEXT_NODE)) {
                        params = new HashMap();

                        try {
                            String paramName = (String) resultClass.getField("DEFAULT_PARAM").get(null);
                            params.put(paramName, resultElement.getChildNodes().item(0).getNodeValue());
                        } catch (Throwable t) {
                        }
                    }
                }


                Condition condition = makeConditions(resultElement);

                OverridingResultConfig resultConfig = new OverridingResultConfig(resultName, resultClass, params, condition);

                results.add(resultConfig);
            }
        }

        return results;
    }


    /**
     * Create a condition for when this web fragment should be displayed
     *
     * @param element Element containing condition definitions.
     * @return Condition when this web fragment should be displayed.
     * @throws com.atlassian.plugin.PluginParseException PluginParseException
     */
    protected Condition makeConditions(final Element element) throws PluginParseException {
        return conditionElementParser.makeConditions(plugin, element, ConditionElementParser.CompositeType.AND);
    }

    private int getIntValue(String stringValue, int defaultValue) {
        int value = defaultValue;
        if (StringUtils.isNotBlank(stringValue)) {
            try {
                value = Integer.parseInt(stringValue);
            } catch (NumberFormatException e) {
                // Do nothing.
            }
        }
        return value;
    }

    /**
     * Create a PackageConfig from an XML element representing it.
     * 

* Note: Copied verbatim from the XmlConfigurationProvider class so that the * configuration object will be populated. */ @Override protected void addPackage(Element packageElement) { PackageConfig newPackage = buildPackageContext(packageElement); if (LOG.isDebugEnabled()) { LOG.debug("Loaded " + newPackage); } // add result types (and default result) to this package addResultTypes(newPackage, packageElement); // load the interceptors and interceptor stacks for this package loadInterceptors(newPackage, packageElement); // load the default interceptor reference for this package loadDefaultInterceptorRef(newPackage, packageElement); // load the global result list for this package loadGlobalResults(newPackage, packageElement); try { // get actions NodeList actionList = packageElement.getElementsByTagName("action"); for (int i = 0; i < actionList.getLength(); i++) { Element actionElement = (Element) actionList.item(i); addAction(actionElement, newPackage); } keepReceipt(overrideManager.createPackage(newPackage)); } catch (ConveyorException e) { throw new ConfigurationException(e); } } private void keepReceipt(Receipt receipt) { receipts.add(receipt); } /** * This method builds a package context by looking for the parents of this * new package. *

* If no parents are found, it will return a root package. *

* Note: Copied verbatim from the XmlConfigurationProvider class so that the * configuration object will be populated. */ @Override protected PackageConfig buildPackageContext(Element packageElement) { String parent = packageElement.getAttribute("extends"); String abstractVal = packageElement.getAttribute("abstract"); boolean isAbstract = Boolean.valueOf(abstractVal).booleanValue(); String name = TextUtils.noNull(packageElement.getAttribute("name")); String namespace = TextUtils.noNull(packageElement.getAttribute("namespace")); // RM* Load the ExternalReferenceResolver if one has been set ExternalReferenceResolver erResolver = null; String externalReferenceResolver = TextUtils.noNull(packageElement .getAttribute("externalReferenceResolver")); if (!("".equals(externalReferenceResolver))) { try { Class erResolverClazz = ClassLoaderUtil.loadClass(externalReferenceResolver, ExternalReferenceResolver.class); erResolver = (ExternalReferenceResolver) erResolverClazz.newInstance(); } catch (ClassNotFoundException e) { // TODO this should be localized String msg = "Could not find External Reference Resolver: " + externalReferenceResolver + ". " + e.getMessage(); fail(msg, e); return null; } catch (Exception e) { // TODO this should be localized String msg = "Could not create External Reference Resolver: " + externalReferenceResolver + ". " + e.getMessage(); fail(msg, e); return null; } } if (!TextUtils.stringSet(TextUtils.noNull(parent))) { // no // parents return new PluginAwarePackageConfig(name, namespace, isAbstract, erResolver, plugin); } else { // has parents, let's look it up List parents = ConfigurationUtil.buildParentsFromString(configuration, parent); if (parents.size() <= 0) { LOG.error("Unable to find parent packages " + parent); return new PluginAwarePackageConfig(name, namespace, isAbstract, erResolver, plugin); } else { return new PluginAwarePackageConfig(name, namespace, isAbstract, erResolver, parents, plugin); } } } private void fail(String message, Exception e) { fail(new ConveyorException(message, e)); } @Override protected void addAction(Element actionElement, PackageConfig packageContext) throws ConfigurationException { String name = actionElement.getAttribute("name"); String className = actionElement.getAttribute("class"); String methodName = actionElement.getAttribute("method"); // CONF-11593 According to the XWork people, missing attribute should default to the ActionSupport class if (StringUtils.isBlank(className)) { className = ActionSupport.class.getName(); } methodName = (methodName == null || methodName.trim().length() <= 0) ? null : methodName.trim(); try { PluginAwareActionConfig actionConfig = new PluginAwareActionConfig(null, className, null, null, null, plugin); ObjectFactory.getObjectFactory().buildAction(actionConfig); } catch (Exception e) { throw new ConfigurationException("Action class [" + className + "] not found, skipping action [" + name + "]", e); } catch (NoClassDefFoundError e) { throw new ConfigurationException("Unable to load Action class [" + className + "], skipping action [" + name + "]", e); } HashMap actionParams = getParams(actionElement); Map results; try { results = buildResults(actionElement, packageContext); } catch (ConfigurationException e) { throw new ConfigurationException("Error building results for action " + name + " in namespace " + packageContext.getNamespace(), e); } List interceptorList = buildInterceptorList(actionElement, packageContext); List externalrefs = buildExternalRefs(actionElement, packageContext); // Create the action PluginAwareActionConfig actionConfig = new PluginAwareActionConfig(methodName, className, actionParams, results, interceptorList, externalrefs, packageContext.getName(), plugin); packageContext.addActionConfig(name, actionConfig); if (LOG.isDebugEnabled()) { LOG.debug("Loaded " + (TextUtils.stringSet( packageContext.getNamespace()) ? packageContext.getNamespace() + "/" : "") + name + " in '" + packageContext.getName() + "' package:" + actionConfig); } } public static HashMap getParams(Element paramsElement) { HashMap params = new HashMap(); if (paramsElement == null) { return params; } NodeList childNodes = paramsElement.getElementsByTagName("param"); for (int i = 0; i < childNodes.getLength(); i++) { Element childNode = (Element) childNodes.item(i); String paramName = childNode.getAttribute("name"); if (childNode.getNodeValue() != null) { String paramValue = childNode.getNodeValue(); params.put(paramName, paramValue); } } return params; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy