AbstractTask.java

/**
 * Copyright (c) 2013-2017 Polago AB
 * All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

package org.polago.deployconf.task;

import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

import org.jdom2.CDATA;
import org.jdom2.Element;
import org.jdom2.Text;
import org.polago.deployconf.group.ConfigGroup;
import org.polago.deployconf.group.ConfigGroupManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Common implementation of a deployment Task.
 */
public abstract class AbstractTask implements Task {

    private static Logger logger = LoggerFactory.getLogger(AbstractTask.class);

    // Matches a property expression like ${propertyName}
    private static final Pattern EXPANSION_PATTERN = Pattern.compile("(\\$\\{([^}]+?)\\})");

    protected static final String DOM_ATTRIBUTE_PATH = "path";

    protected static final String DOM_ATTRIBUTE_GROUP = "group";

    protected static final String DOM_ATTRIBUTE_IF = "if";

    protected static final String DOM_ELEMENT_DESCRIPTION = "description";

    protected static final String DOM_ELEMENT_DEFAULT = "default";

    protected static final String DOM_ELEMENT_VALUE = "value";

    protected static final String DOM_ELEMENT_CONDITION = "condition";

    private String path;

    private final ScriptEngine scriptEngine;

    private final ConfigGroupManager groupManager;

    /**
     * Public Constructor.
     *
     * @param groupManager the groupManager to use
     */
    public AbstractTask(ConfigGroupManager groupManager) {
        super();

        this.groupManager = groupManager;

        ScriptEngineManager factory = new ScriptEngineManager();
        scriptEngine = factory.getEngineByName("JavaScript");
    }

    /**
     * Gets the groupManager property value.
     *
     * @return the current value of the groupManager property
     */
    public ConfigGroupManager getGroupManager() {
        return groupManager;
    }

    /**
     * Gets the path property value.
     *
     * @return the current value of the path property
     */
    @Override
    public String getPath() {
        return path;
    }

    /**
     * Sets the path property.
     *
     * @param path the new property value
     */
    public void setPath(String path) {
        this.path = path;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void deserialize(Element root) throws IOException {
        String attribute = root.getAttributeValue(DOM_ATTRIBUTE_PATH);
        if (attribute == null) {
            throw new IllegalStateException("path attribute is required");
        }
        setPath(attribute);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void serialize(Element node) throws IOException {
        node.setAttribute(DOM_ATTRIBUTE_PATH, getPath());
    }

    /**
     * Replace matching properties from the given groupManager in the given text.
     *
     * @param text the text to expand
     * @param group the ConfigGroup to use as source for expanding property expressions
     * @return the expanded value, if possible
     */
    protected String expandPropertyExpression(String text, ConfigGroup group) {
        String result = text;

        if (text != null) {
            Matcher matcher = EXPANSION_PATTERN.matcher(text);
            StringBuffer expanded = new StringBuffer(text.length());
            while (matcher.find()) {
                String name = matcher.group(2);
                String value = group.getProperty(name);
                if (value == null) {
                    value = matcher.group(0);
                }
                matcher.appendReplacement(expanded, Matcher.quoteReplacement(value));
            }
            matcher.appendTail(expanded);
            result = expanded.toString();
        }

        return result;
    }

    /**
     * Evaluate the task condition.
     *
     * @param text the condition to evaluate
     * @param group the ConfigGroup to use as source for expanding property expressions before evaluation
     * @return the evaluation result
     */
    public boolean evaluateCondition(String text, ConfigGroup group) {
        boolean result = true;
        try {
            if (text != null) {
                Object resultObject = scriptEngine.eval(expandPropertyExpression(text, group));
                if (resultObject instanceof Boolean) {
                    Boolean boolResult = (Boolean) resultObject;
                    return boolResult.booleanValue();
                }
            }
        } catch (ScriptException e) {
            logger.info("Unable to evaluate condition: " + text, e);
        }

        return result;
    }

    /**
     * Create a JDOM Element with the given name.
     *
     * @param name the element name
     * @return a JDOM Element instance
     */
    protected Element createJDOMElement(String name) {
        Element result = new Element(name);

        return result;
    }

    /**
     * Create a JDOM Text Element with the given name and possibly text.
     *
     * @param name the element name
     * @param text the element text or null
     * @return a JDOM Element instance
     */
    protected Element createJDOMTextElement(String name, String text) {
        Element result = new Element(name);
        if (text != null) {
            result.setContent(new Text(text));
        }

        return result;
    }

    /**
     * Create a JDOM CDATA Element with the given name and possibly text.
     *
     * @param name the element name
     * @param text the element text or null
     * @return a JDOM Element instance
     */
    protected Element createJDOMCDATAElement(String name, String text) {
        Element result = new Element(name);
        if (text != null) {
            result.addContent(new CDATA(text));
        }

        return result;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int hashCode() {
        return getPath().hashCode();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean equals(Object other) {
        boolean result = false;
        if (other instanceof AbstractTask) {
            AbstractTask otherTask = (AbstractTask) other;
            result = getPath().equals(otherTask.getPath());
        }

        return result;
    }
}