org.rhq.modules.plugins.wildfly10.BaseComponent.java Source code

Java tutorial

Introduction

Here is the source code for org.rhq.modules.plugins.wildfly10.BaseComponent.java

Source

/*
 * RHQ Management Platform
 * Copyright (C) 2005-2014 Red Hat, Inc.
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation version 2 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
 */

package org.rhq.modules.plugins.wildfly10;

import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.concurrent.TimeoutException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.jackson.JsonNode;

import org.rhq.core.domain.configuration.Configuration;
import org.rhq.core.domain.configuration.Property;
import org.rhq.core.domain.configuration.PropertyList;
import org.rhq.core.domain.configuration.PropertySimple;
import org.rhq.core.domain.configuration.definition.ConfigurationDefinition;
import org.rhq.core.domain.configuration.definition.PropertyDefinition;
import org.rhq.core.domain.configuration.definition.PropertyDefinitionList;
import org.rhq.core.domain.configuration.definition.PropertyDefinitionSimple;
import org.rhq.core.domain.configuration.definition.PropertySimpleType;
import org.rhq.core.domain.content.transfer.ResourcePackageDetails;
import org.rhq.core.domain.measurement.AvailabilityType;
import org.rhq.core.domain.measurement.DataType;
import org.rhq.core.domain.measurement.MeasurementDataNumeric;
import org.rhq.core.domain.measurement.MeasurementDataTrait;
import org.rhq.core.domain.measurement.MeasurementReport;
import org.rhq.core.domain.measurement.MeasurementScheduleRequest;
import org.rhq.core.domain.operation.OperationDefinition;
import org.rhq.core.domain.resource.CreateResourceStatus;
import org.rhq.core.domain.resource.ResourceType;
import org.rhq.core.pluginapi.configuration.ConfigurationFacet;
import org.rhq.core.pluginapi.configuration.ConfigurationUpdateReport;
import org.rhq.core.pluginapi.content.ContentContext;
import org.rhq.core.pluginapi.content.ContentServices;
import org.rhq.core.pluginapi.inventory.CreateChildResourceFacet;
import org.rhq.core.pluginapi.inventory.CreateResourceReport;
import org.rhq.core.pluginapi.inventory.DeleteResourceFacet;
import org.rhq.core.pluginapi.inventory.ResourceComponent;
import org.rhq.core.pluginapi.inventory.ResourceContext;
import org.rhq.core.pluginapi.measurement.MeasurementFacet;
import org.rhq.core.pluginapi.operation.OperationFacet;
import org.rhq.core.pluginapi.operation.OperationResult;
import org.rhq.core.pluginapi.util.StartScriptConfiguration;
import org.rhq.modules.plugins.wildfly10.helper.Deployer;
import org.rhq.modules.plugins.wildfly10.json.Address;
import org.rhq.modules.plugins.wildfly10.json.Operation;
import org.rhq.modules.plugins.wildfly10.json.ReadAttribute;
import org.rhq.modules.plugins.wildfly10.json.ReadChildrenNames;
import org.rhq.modules.plugins.wildfly10.json.ReadResource;
import org.rhq.modules.plugins.wildfly10.json.Remove;
import org.rhq.modules.plugins.wildfly10.json.ResolveExpression;
import org.rhq.modules.plugins.wildfly10.json.Result;
import org.rhq.modules.plugins.wildfly10.json.ResultFailedException;
import org.rhq.modules.plugins.wildfly10.json.SecurityRealmNotReadyException;
import org.rhq.modules.plugins.wildfly10.json.UnauthorizedException;

/**
 * The base class for all AS7 resource components.
 *
 * @param <T> the type of the component's parent resource component
 */
public class BaseComponent<T extends ResourceComponent<?>> implements AS7Component<T>, MeasurementFacet,
        ConfigurationFacet, DeleteResourceFacet, CreateChildResourceFacet, OperationFacet {

    private static final Log LOG = LogFactory.getLog(BaseComponent.class);

    static final String INTERNAL = "_internal:";
    static final int INTERNAL_SIZE = INTERNAL.length();
    static final String EXPRESSION = "_expr:";
    static final int EXPRESSION_SIZE = EXPRESSION.length();
    static final String EXPRESSION_VALUE_KEY = "EXPRESSION_VALUE";
    static final int AVAIL_OP_TIMEOUT_SECONDS = 60;

    public static final String MANAGED_SERVER = "Managed Server";

    /**
     * @deprecated as of 4.10. Use your own logger or {@link #getLog()} method.
     */
    @Deprecated
    final Log log = LOG;

    ResourceContext<T> context;
    Configuration pluginConfiguration;
    String myServerName;

    String path;
    Address address;
    String key;
    boolean includeRuntime;

    private BaseServerComponent serverComponent;
    protected ASConnection testConnection;

    /**
     * Start the resource connection
     * @see org.rhq.core.pluginapi.inventory.ResourceComponent#start(org.rhq.core.pluginapi.inventory.ResourceContext)
     */
    @Override
    public void start(ResourceContext<T> context) throws Exception {
        this.context = context;
        pluginConfiguration = context.getPluginConfiguration();
        serverComponent = findServerComponent();
        path = pluginConfiguration.getSimpleValue("path");
        address = new Address(path);
        key = context.getResourceKey();
        myServerName = context.getResourceKey().substring(context.getResourceKey().lastIndexOf("/") + 1);
        includeRuntime = Boolean.parseBoolean(pluginConfiguration.getSimpleValue("includeRuntime", null));
    }

    @Override
    public void stop() {
    }

    /**
     * Return availability of this resource
     *  @see org.rhq.core.pluginapi.inventory.ResourceComponent#getAvailability()
     */
    @Override
    public AvailabilityType getAvailability() {
        ReadResource readResourceOperation = new ReadResource(address);
        /*
         * Make the operation return minimum information. We just want to make sure we can read the resource. There's no
         * need to read the children names, evaluate defaults, and retrieve runtime attributes.
         */
        readResourceOperation.attributesOnly(true);
        readResourceOperation.includeDefaults(false);
        readResourceOperation.includeRuntime(false);

        Result res = getASConnection().execute(readResourceOperation, AVAIL_OP_TIMEOUT_SECONDS);
        if (res != null && res.isSuccess()) {
            return AvailabilityType.UP;
        }
        if (context.getResourceType().isSupportsMissingAvailabilityType()) {
            if (res != null && res.isRolledBack() && res.getFailureDescription().startsWith("JBAS014807")) {
                getLog().info("Reporting MISSING resource: " + getPath());
                return AvailabilityType.MISSING;
            }
        }
        if (res != null && res.isTimedout()) {
            return AvailabilityType.UNKNOWN;
        }
        return AvailabilityType.DOWN;
    }

    protected String getResourceDescription() {
        return context.getResourceType() + " [" + context.getResourceKey() + "]";
    }

    private BaseServerComponent findServerComponent() {
        BaseComponent<?> component = this;
        while ((component != null) && !(component instanceof BaseServerComponent)) {
            component = (BaseComponent<?>) component.context.getParentResourceComponent();
        }
        return (BaseServerComponent) component;
    }

    public BaseServerComponent getServerComponent() {
        return serverComponent;
    }

    /**
     * Gather measurement data
     * @see org.rhq.core.pluginapi.measurement.MeasurementFacet#getValues(org.rhq.core.domain.measurement.MeasurementReport, java.util.Set)
     */
    @Override
    public void getValues(MeasurementReport report, Set<MeasurementScheduleRequest> metrics) throws Exception {

        for (MeasurementScheduleRequest req : metrics) {
            getMetricValue(report, req, null);
        }
    }

    /**
     * gets metric value for given request
     * @param report
     * @param req
     * @param explicitExpressions set of metric names that could be represented by expression instead of value on AS7 (can be null)
     * @return ReadMetricResult value that if different from 'Success' determines why we failed to read metric
     */
    protected ReadMetricResult getMetricValue(MeasurementReport report, MeasurementScheduleRequest req,
            Set<String> explicitExpressions) {
        if (req.getName().startsWith(INTERNAL))
            processPluginStats(req, report);
        else {
            // Metrics from the application server

            String reqName = req.getName();
            boolean resolveExpression = false;
            if (reqName.startsWith(EXPRESSION)) {
                resolveExpression = true;
                reqName = reqName.substring(EXPRESSION_SIZE);
            } else if (explicitExpressions != null && explicitExpressions.contains(reqName)) {
                resolveExpression = true;
            }

            ComplexRequest complexRequest = null;
            Operation op;
            if (reqName.contains(":")) {
                complexRequest = ComplexRequest.create(reqName);
                op = new ReadAttribute(address, complexRequest.getProp());
            } else {
                op = new ReadAttribute(address, reqName);
            }

            Result res = getASConnection().execute(op);
            if (!res.isSuccess()) {
                if (LOG.isDebugEnabled()) {
                    LOG.debug("Getting metric [" + req.getName() + "] at [ " + address + "] failed: "
                            + res.getFailureDescription());
                }
                return ReadMetricResult.RequestFailed;
            }

            Object val = res.getResult();
            if (val == null) // One of the AS7 ways of telling "This is not implemented" See also AS7-1454
                return ReadMetricResult.Null;

            if (req.getDataType() == DataType.MEASUREMENT) {
                if (val instanceof String && ((String) val).startsWith("JBAS018003")) // AS7 way of saying "no value available"
                    return ReadMetricResult.Null;
                try {
                    if (complexRequest != null) {
                        @SuppressWarnings("unchecked")
                        Map<String, Number> myValues = (Map<String, Number>) val;
                        for (String key : myValues.keySet()) {
                            String sub = complexRequest.getSub();
                            if (key.equals(sub)) {
                                addMetric2Report(report, req, myValues.get(key), resolveExpression);
                            }
                        }
                    } else {
                        addMetric2Report(report, req, val, resolveExpression);
                    }
                } catch (NumberFormatException e) {
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("Non numeric input for [" + req.getName() + "] : [" + val + "]");
                    }
                    return ReadMetricResult.ResolveFailed;
                }
            } else if (req.getDataType() == DataType.TRAIT) {

                if (resolveExpression && val instanceof Map && ((Map) val).containsKey(EXPRESSION_VALUE_KEY)) {
                    String expression = (String) ((Map) val).get(EXPRESSION_VALUE_KEY);
                    ResolveExpression resolveExpressionOperation = new ResolveExpression(expression);
                    Result result = getASConnection().execute(resolveExpressionOperation);
                    if (!result.isSuccess()) {
                        if (LOG.isDebugEnabled()) {
                            LOG.debug("Skipping trait [" + req.getName()
                                    + "] in measurement report. Could not resolve expression [" + expression
                                    + "], failureDescription:" + result.getFailureDescription());
                            return ReadMetricResult.ResolveFailed;
                        }
                    }
                    val = result.getResult();
                }

                MeasurementDataTrait data = new MeasurementDataTrait(req, getStringValue(val));
                report.addData(data);
            }
        }
        return ReadMetricResult.Success;

    }

    private void addMetric2Report(MeasurementReport report, MeasurementScheduleRequest req, Object val,
            boolean resolveExpression) {
        if (resolveExpression && val instanceof Map && ((Map) val).containsKey(EXPRESSION_VALUE_KEY)) {
            String expression = (String) ((Map) val).get(EXPRESSION_VALUE_KEY);
            ResolveExpression resolveExpressionOperation = new ResolveExpression(expression);
            Result result = getASConnection().execute(resolveExpressionOperation);
            if (!result.isSuccess()) {
                if (LOG.isDebugEnabled()) {
                    LOG.debug("Skipping metric [" + req.getName()
                            + "] in measurement report. Could not resolve expression [" + expression
                            + "], failureDescription:" + result.getFailureDescription());
                    return;
                }
            }
            val = result.getResult();
        }
        Double d = Double.parseDouble(getStringValue(val));
        MeasurementDataNumeric data = new MeasurementDataNumeric(req, d);
        report.addData(data);
    }

    protected String getStringValue(Object val) {
        String realVal;
        if (val instanceof String)
            realVal = (String) val;
        else
            realVal = String.valueOf(val);
        return realVal;
    }

    /**
     * Return internal statistics data
     * @param req Schedule for the requested data
     * @param report report to add th data to.
     */
    private void processPluginStats(MeasurementScheduleRequest req, MeasurementReport report) {

        String name = req.getName();
        if (!name.startsWith(INTERNAL))
            return;

        name = name.substring(INTERNAL_SIZE);

        PluginStats stats = PluginStats.getInstance();
        MeasurementDataNumeric data;
        Double val;
        if (name.equals("mgmtRequests")) {
            val = (double) stats.getRequestCount();
        } else if (name.equals("requestTime")) {
            val = (double) stats.getRequestTime();
        } else if (name.equals("maxTime")) {
            val = (double) stats.getMaxTime();
        } else
            val = Double.NaN;

        data = new MeasurementDataNumeric(req, val);
        report.addData(data);
    }

    @Override
    public ASConnection getASConnection() {
        return (this.testConnection != null) ? this.testConnection : getServerComponent().getASConnection();
    }

    @Override
    public String getPath() {
        return path;
    }

    @Override
    public Configuration loadResourceConfiguration() throws Exception {

        ConfigurationDefinition configDef = context.getResourceType().getResourceConfigurationDefinition();
        ConfigurationLoadDelegate delegate = new ConfigurationLoadDelegate(configDef, getASConnection(), address,
                includeRuntime);
        Configuration configuration = delegate.loadResourceConfiguration();

        // Read server state
        Operation op = new Operation("whoami", getAddress());
        Result res = getASConnection().execute(op);

        //:whoami might fail host controller resources, in that case use :read-resource operation
        //which is slower due to larger content returned but more reliable since every resource has it.
        if (!res.isSuccess()) {
            op = new Operation("read-resource", getAddress());
            res = getASConnection().execute(op);
        }
        includeOOBMessages(res, configuration);
        return configuration;
    }

    protected static void includeOOBMessages(Result res, Configuration configuration) {
        if (res.isReloadRequired()) {
            PropertySimple oobMessage = new PropertySimple("__OOB",
                    "The server needs a reload for the latest changes to come effective.");
            configuration.put(oobMessage);
        }
        if (res.isRestartRequired()) {
            PropertySimple oobMessage = new PropertySimple("__OOB",
                    "The server needs a restart for the latest changes to come effective.");
            configuration.put(oobMessage);
        }
    }

    @Override
    public void updateResourceConfiguration(ConfigurationUpdateReport report) {

        ConfigurationDefinition configDef = context.getResourceType().getResourceConfigurationDefinition();
        ConfigurationWriteDelegate delegate = new ConfigurationWriteDelegate(configDef, getASConnection(), address);
        delegate.updateResourceConfiguration(report);
    }

    @Override
    public void deleteResource() throws Exception {
        if (LOG.isDebugEnabled()) {
            LOG.debug("Removing AS7 resource [" + path + "]...");
        }

        if (context.getResourceType().getName().equals(MANAGED_SERVER)) {
            // We need to do two steps because of AS7-4032
            Operation stop = new Operation("stop", getAddress());
            Result res = getASConnection().execute(stop);
            if (!res.isSuccess()) {
                throw new IllegalStateException("Managed server [" + path
                        + "] is still running and can't be stopped, so it cannot be removed.");
            }
        }
        Operation op = new Remove(address);
        Result res = getASConnection().execute(op, 120);
        if (!res.isSuccess()) {
            throw new IllegalArgumentException("Delete for [" + path + "] failed: " + res.getFailureDescription());
        }
    }

    @Override
    public CreateResourceReport createResource(CreateResourceReport report) {
        if (report.getPackageDetails() != null) { // Content deployment
            return deployContent(report);
        } else {
            ASConnection connection = getASConnection();
            ConfigurationDefinition configDef = report.getResourceType().getResourceConfigurationDefinition();

            // Check for the Highlander principle
            boolean isSingleton = report.getResourceType().isSingleton();
            if (isSingleton) {
                // check if there is already a child with th desired type is present
                Configuration pluginConfig = report.getPluginConfiguration();
                PropertySimple pathProperty = pluginConfig.getSimple("path");
                if (path == null || path.isEmpty()) {
                    report.setErrorMessage("No path property found in plugin configuration");
                    report.setStatus(CreateResourceStatus.INVALID_CONFIGURATION);
                    return report;
                }

                ReadChildrenNames op = new ReadChildrenNames(address, pathProperty.getStringValue());
                Result res = connection.execute(op);
                if (res.isSuccess()) {
                    @SuppressWarnings("unchecked")
                    List<String> entries = (List<String>) res.getResult();
                    if (!entries.isEmpty()) {
                        report.setErrorMessage("Resource is a singleton, but there are already children " + entries
                                + " please remove them and retry");
                        report.setStatus(CreateResourceStatus.FAILURE);
                        return report;
                    }
                }
            }

            // Allow to modify the configuration coming in from the RHQ server see also BZ 825120
            if (report.getResourceType().getName().equals("Network Interface")) {
                Configuration configuration = report.getResourceConfiguration();
                NetworkInterfaceComponent.preProcessCreateChildConfiguration(configuration);
            }

            CreateResourceDelegate delegate = new CreateResourceDelegate(configDef, connection, address);
            return delegate.createResource(report);
        }
    }

    /**
     * Deploy content to the remote server - this is one half of #createResource
     * @param report Create resource report that tells us what to do
     * @return report that tells us what has been done.
     */
    protected CreateResourceReport deployContent(CreateResourceReport report) {
        ContentContext cctx = context.getContentContext();
        ResourcePackageDetails details = report.getPackageDetails();

        ContentServices contentServices = cctx.getContentServices();
        String resourceTypeName = report.getResourceType().getName();

        ASUploadConnection uploadConnection = new ASUploadConnection(getServerComponent().getASConnection(),
                details.getKey().getName());

        OutputStream out = uploadConnection.getOutputStream();
        if (out == null) {
            report.setStatus(CreateResourceStatus.FAILURE);
            report.setErrorMessage("An error occured while the agent was preparing for content download");
            return report;
        }

        long size;
        try {
            size = contentServices.downloadPackageBitsForChildResource(cctx, resourceTypeName, details.getKey(),
                    out);
        } catch (Exception e) {
            uploadConnection.cancelUpload();
            report.setStatus(CreateResourceStatus.FAILURE);
            LOG.debug("Failed to pull package from server", e);
            report.setErrorMessage(
                    "An error occured while the agent was uploading the content for [" + details.getKey() + "]");
            return report;
        }

        if (0L == size) {
            uploadConnection.cancelUpload();
            report.setStatus(CreateResourceStatus.FAILURE);
            report.setErrorMessage("An error occured (0 bytes) while the agent was uploading the content for ["
                    + details.getKey() + "]");
            return report;

        }

        JsonNode uploadResult = uploadConnection.finishUpload();
        if (ASConnection.verbose) {
            LOG.info(uploadResult);
        }

        if (ASUploadConnection.isErrorReply(uploadResult)) {
            report.setStatus(CreateResourceStatus.FAILURE);
            report.setErrorMessage(ASUploadConnection.getFailureDescription(uploadResult));
            return report;
        }

        // Key is available from UI and CLI
        String fileName = details.getKey().getName();

        String runtimeName = report.getPackageDetails().getDeploymentTimeConfiguration()
                .getSimpleValue("runtimeName");
        if (runtimeName == null || runtimeName.isEmpty()) {
            runtimeName = fileName;
        }

        JsonNode resultNode = uploadResult.get("result");
        String hash = resultNode.get("BYTES_VALUE").getTextValue();

        return runDeploymentMagicOnServer(report, runtimeName, fileName, hash);
    }

    /**
     * Do the actual fumbling with the domain api to deploy the uploaded content
     * @param report CreateResourceReport to report the result
     * @param runtimeName File name to use as runtime name
     * @param deploymentName Name of the deployment
     * @param hash Hash of the content bytes
     * @return the passed report with success or failure settings
     */
    public CreateResourceReport runDeploymentMagicOnServer(CreateResourceReport report, String runtimeName,
            String deploymentName, String hash) {

        LOG.info("Deploying [" + deploymentName + " (runtimeName=" + runtimeName + ")] ...");

        Deployer deployer = new Deployer(deploymentName, runtimeName, hash, getASConnection());

        Result result = deployer.deployToServer(context.getResourceType().getName().contains("Standalone"));
        String resourceKey = deployer.getNewResourceKey();

        if ((!result.isSuccess())) {
            String failureDescription = result.getFailureDescription();
            report.setErrorMessage(failureDescription);
            report.setStatus(CreateResourceStatus.FAILURE);
            LOG.warn("Deploy of [" + runtimeName + "] failed: " + failureDescription);
        } else {
            report.setStatus(CreateResourceStatus.SUCCESS);
            report.setResourceName(runtimeName);
            report.setResourceKey(resourceKey);
            report.getPackageDetails().setSHA256(hash);
            report.getPackageDetails().setInstallationTimestamp(System.currentTimeMillis());
            LOG.info("Deploy of [" + runtimeName + "] succeeded - Resource key is [" + resourceKey + "].");
        }

        return report;
    }

    @Override
    public OperationResult invokeOperation(String name, Configuration parameters) throws Exception {

        String what;
        String op;

        if (name.contains(":")) {
            int colonPos = name.indexOf(':');
            what = name.substring(0, colonPos);
            op = name.substring(colonPos + 1);
        } else {
            what = ""; // dummy value
            op = name;
        }
        Operation operation;

        Address theAddress = new Address();

        if (what.equals("server-group")) {
            String groupName = parameters.getSimpleValue("name", "");
            String profile = parameters.getSimpleValue("profile", "default");

            theAddress.add("server-group", groupName);

            operation = new Operation(op, theAddress);
            operation.addAdditionalProperty("profile", profile);
        } else if (what.equals("destination")) {
            theAddress.add(address);
            String newName = parameters.getSimpleValue("name", "");
            String type = parameters.getSimpleValue("type", "jms-queue").toLowerCase();
            theAddress.add(type, newName);
            PropertyList jndiNamesProp = parameters.getList("entries");
            if (jndiNamesProp == null || jndiNamesProp.getList().isEmpty()) {
                OperationResult fail = new OperationResult();
                fail.setErrorMessage("No jndi bindings given");
                return fail;
            }
            List<String> jndiNames = new ArrayList<String>();
            for (Property p : jndiNamesProp.getList()) {
                PropertySimple ps = (PropertySimple) p;
                jndiNames.add(ps.getStringValue());
            }

            operation = new Operation(op, theAddress);
            operation.addAdditionalProperty("entries", jndiNames);
            if (type.equals("jms-queue")) {
                PropertySimple ps = (PropertySimple) parameters.get("durable");
                if (ps != null) {
                    boolean durable = Boolean.parseBoolean(ps.getStringValue());
                    operation.addAdditionalProperty("durable", durable);
                }
                String selector = parameters.getSimpleValue("selector", "");
                if (!selector.isEmpty())
                    operation.addAdditionalProperty("selector", selector);
            }

        } else if (what.equals("domain")) {
            operation = new Operation(op, new Address());
        } else if (what.equals("subsystem")) {
            operation = new Operation(op, new Address(this.path));
        } else {
            // We have a generic operation so we pass it literally
            // with the parameters it has.
            operation = new Operation(op, new Address((path)));
            for (Property prop : parameters.getProperties()) {
                if (prop instanceof PropertySimple) {
                    PropertySimple ps = (PropertySimple) prop;
                    if (ps.getStringValue() != null) {
                        Object val = getObjectForProperty(ps, op);
                        operation.addAdditionalProperty(ps.getName(), val);
                    }
                } else if (prop instanceof PropertyList) {
                    PropertyList pl = (PropertyList) prop;
                    List<Object> items = new ArrayList<Object>(pl.getList().size());
                    // Loop over the inner elements of the list
                    for (Property p2 : pl.getList()) {
                        if (p2 instanceof PropertySimple) {
                            PropertySimple ps = (PropertySimple) p2;
                            if (ps.getStringValue() != null) {
                                Object val = getObjectForPropertyList(ps, pl, op);
                                items.add(val);
                            }
                        }
                    }
                    operation.addAdditionalProperty(pl.getName(), items);
                } else {
                    LOG.error("PropertyMap for " + prop.getName() + " not yet supported");
                }
            }
        }

        OperationResult operationResult = new OperationResult();
        Result result = getASConnection().execute(operation);

        if (result == null) {
            operationResult.setErrorMessage("Connection was null - is the server running?");
            return operationResult;
        }

        if (!result.isSuccess()) {
            operationResult.setErrorMessage(result.getFailureDescription());
        } else {
            String tmp;
            if (result.getResult() == null)
                tmp = "-none provided by the server-";
            else
                tmp = result.getResult().toString();
            operationResult.setSimpleResult(tmp);
        }
        return operationResult;
    }

    /**
     * Return a value object for the passed property. The type is determined by looking at the operation definition
     * @param prop Property to evaluate
     * @param operationName Name of the operation to look at
     * @return Value or null on failure
     */
    Object getObjectForProperty(PropertySimple prop, String operationName) {
        ConfigurationDefinition parameterDefinitions = getParameterDefinitionsForOperation(operationName);
        if (parameterDefinitions == null)
            return null;

        PropertyDefinition pd = parameterDefinitions.get(prop.getName());
        if (pd instanceof PropertyDefinitionSimple) {
            PropertyDefinitionSimple pds = (PropertyDefinitionSimple) pd;
            return getObjectForProperty(prop, pds);
        } else {
            LOG.warn("Property [" + prop.getName() + "] is not understood yet");
            return null;
        }
    }

    /**
     * Return a value object for the passed property, which is part of a list. The type is determined by
     * looking at the operation definition and PropertyList#getMemberDefinition
     * @param prop Property to evaluate
     * @param propertyList Outer list
     * @param operationName Name of the operation
     * @return Value or null on failure
     */
    Object getObjectForPropertyList(PropertySimple prop, PropertyList propertyList, String operationName) {
        ConfigurationDefinition parameterDefinitions = getParameterDefinitionsForOperation(operationName);
        if (parameterDefinitions == null)
            return null;

        PropertyDefinition def = parameterDefinitions.get(propertyList.getName());
        if (def instanceof PropertyDefinitionList) {
            PropertyDefinitionList definitionList = (PropertyDefinitionList) def;
            PropertyDefinition tmp = definitionList.getMemberDefinition();
            if (tmp instanceof PropertyDefinitionSimple) {
                return getObjectForProperty(prop, (PropertyDefinitionSimple) tmp);
            }
        }
        return null;
    }

    /**
     * Return the parameter definition for the operation with the name passed
     * @param operationName Name of the operation to look for
     * @return A configuration definition or null on failure
     */
    ConfigurationDefinition getParameterDefinitionsForOperation(String operationName) {
        ResourceType type = context.getResourceType();
        Set<OperationDefinition> operationDefinitions = type.getOperationDefinitions();

        for (OperationDefinition definition : operationDefinitions) {
            if (definition.getName().equals(operationName)) {
                return definition.getParametersConfigurationDefinition();
            }
        }
        return null;
    }

    /**
     * Return a value object for the passed property with the passed definition
     * @param prop Property to evaluate
     * @param propDef Definition to determine the type from
     * @return The value object
     */
    Object getObjectForProperty(PropertySimple prop, PropertyDefinitionSimple propDef) {
        PropertySimpleType type = propDef.getType();
        return getObjectForProperty(prop, type);
    }

    /**
     * Return the object representation of the passed PropertySimple for the passed type
     * @param prop Property to evaluate
     * @param type Type to convert into
     * @return Converted object -- if no valid type is found, a String-value is returned.
     */
    private Object getObjectForProperty(PropertySimple prop, PropertySimpleType type) {
        switch (type) {
        case STRING:
            return prop.getStringValue();
        case INTEGER:
            return prop.getIntegerValue();
        case BOOLEAN:
            return prop.getBooleanValue();
        case LONG:
            return prop.getLongValue();
        case FLOAT:
            return prop.getFloatValue();
        case DOUBLE:
            return prop.getDoubleValue();
        default:
            return prop.getStringValue();
        }
    }

    ///// These two are used to 'inject' the connection and the path from tests.
    public void setConnection(ASConnection connection) {
        this.testConnection = connection;
    }

    public void setPath(String path) {
        this.path = path;
        this.address = new Address(path);
    }

    public Address getAddress() {
        return address;
    }

    protected String readAttribute(String name) throws Exception {
        return readAttribute(getAddress(), name);
    }

    protected String readAttribute(String name, int timeoutSec) throws Exception {
        return readAttribute(getAddress(), name, timeoutSec);
    }

    protected String readAttribute(Address address, String name) throws Exception {
        return readAttribute(address, name, String.class);
    }

    protected String readAttribute(Address address, String name, int timeoutSec) throws Exception {
        return readAttribute(address, name, String.class, timeoutSec);
    }

    protected <R> R readAttribute(Address address, String name, Class<R> resultType) throws Exception {
        return readAttribute(address, name, resultType, 10);
    }

    protected <R> R readAttribute(Address address, String name, Class<R> resultType, int timeoutSec)
            throws Exception {
        Operation op = new ReadAttribute(address, name);
        Result res = getASConnection().execute(op, timeoutSec);

        if (!res.isSuccess()) {
            if (res.isTimedout()) {
                throw new TimeoutException("Read attribute operation timed out");
            }
            if (res.isRolledBack() && res.getFailureDescription().startsWith("JBAS013456")) {
                throw new UnauthorizedException("Failed to read attribute [" + name + "] of address ["
                        + getAddress().getPath() + "] - response: " + res);
            }
            if (res.isRolledBack() && !res.getFailureDescription().startsWith("JBAS015135")) {
                // this means we've connected, authenticated, but still failed
                throw new ResultFailedException("Failed to read attribute [" + name + "] of address ["
                        + getAddress().getPath() + "] - response: " + res);
            }
            if (res.isRolledBack() && res.getFailureDescription().startsWith("JBAS015135")) {
                // this means we've connected, authenticated, but still failed
                throw new SecurityRealmNotReadyException("Failed to read attribute [" + name + "] of address ["
                        + getAddress().getPath() + "] - response: " + res);
            }

            throw new Exception("Failed to read attribute [" + name + "] of address [" + getAddress().getPath()
                    + "] - response: " + res);
        }

        return resultType.cast(res.getResult());
    }

    protected Address getServerAddress() {
        throw new UnsupportedOperationException();
    }

    protected String getSocketBindingGroup() {
        throw new UnsupportedOperationException();
    }

    protected void collectMulticastAddressTrait(MeasurementReport report, MeasurementScheduleRequest request) {
        Address jgroupsUdpStackAddress = new Address(getServerAddress());
        jgroupsUdpStackAddress.add("subsystem", "jgroups");
        jgroupsUdpStackAddress.add("stack", "udp");
        jgroupsUdpStackAddress.add("transport", "TRANSPORT");

        String socketBinding;
        try {
            socketBinding = readAttribute(jgroupsUdpStackAddress, "socket-binding");
        } catch (Exception e) {
            socketBinding = null;
        }

        if (socketBinding != null) {
            Address jgroupsSocketBindingAddress = new Address(getServerAddress());
            String socketBindingGroup = getSocketBindingGroup();
            jgroupsSocketBindingAddress.add("socket-binding-group", socketBindingGroup);
            jgroupsSocketBindingAddress.add("socket-binding", socketBinding);
            String multicastHost = null;
            Integer multicastPort;
            try {
                try {
                    @SuppressWarnings("unchecked")
                    Map<String, String> map = readAttribute(jgroupsSocketBindingAddress, "multicast-address",
                            Map.class);
                    String expressionValue = map.get("EXPRESSION_VALUE");
                    int beginIndex = expressionValue.indexOf("${jboss.default.multicast.address");
                    if (beginIndex >= 0) {
                        int endIndex = expressionValue.indexOf('}', beginIndex);
                        if (endIndex >= 0) {
                            String expression = expressionValue.substring(beginIndex + 2, endIndex);
                            StartScriptConfiguration startScriptConfig = getServerComponent()
                                    .getStartScriptConfiguration();
                            List<String> startScriptArgs = startScriptConfig.getStartScriptArgs();
                            for (String startScriptArg : startScriptArgs) {
                                if (startScriptArg.startsWith("-Djboss.default.multicast.address=")) {
                                    multicastHost = startScriptArg
                                            .substring("-Djboss.default.multicast.address=".length());
                                    break;
                                }
                            }
                            if (multicastHost == null) {
                                int colonIndex = expression.indexOf(':');
                                String defaultValue = (colonIndex >= 0) ? expression.substring(colonIndex + 1)
                                        : null;
                                if (defaultValue != null) {
                                    multicastHost = defaultValue;
                                } else {
                                    LOG.error("Failed to resolve expression value [" + expressionValue
                                            + "] of 'multicast-address' attribute.");
                                }
                            }
                        }
                    }
                } catch (ClassCastException cce) {
                    multicastHost = readAttribute(jgroupsSocketBindingAddress, "multicast-address");
                }
                multicastPort = readAttribute(jgroupsSocketBindingAddress, "multicast-port", Integer.class);
            } catch (Exception e) {
                throw new RuntimeException(
                        "Failed to lookup multicast address for socket binding [" + socketBinding + "].");
            }

            if (multicastHost != null && multicastPort != null) {
                String multicastAddress = multicastHost + ":" + multicastPort;
                MeasurementDataTrait data = new MeasurementDataTrait(request, multicastAddress);
                report.addData(data);
            }
        }
    }

    static enum ReadMetricResult {
        Success, RequestFailed, Null, ResolveFailed
    }

    private static class ComplexRequest {
        private String prop;
        private String sub;

        private ComplexRequest(String prop, String sub) {
            this.prop = prop;
            this.sub = sub;
        }

        public String getProp() {
            return prop;
        }

        public String getSub() {
            return sub;
        }

        public static ComplexRequest create(String requestName) {
            StringTokenizer tokenizer = new StringTokenizer(requestName, ":");
            return new ComplexRequest(tokenizer.nextToken(), tokenizer.nextToken());
        }
    }

    /**
     * Get the logger for AS7 plugin resource components.
     *
     * @return
     */
    public static Log getLog() {
        return LOG;
    }
}