com.zenoss.zenpacks.zenjmx.call.OperationCall.java Source code

Java tutorial

Introduction

Here is the source code for com.zenoss.zenpacks.zenjmx.call.OperationCall.java

Source

/*****************************************************************************
 * 
 * Copyright (C) Zenoss, Inc. 2008, all rights reserved.
 * 
 * This content is made available according to terms specified in
 * License.zenoss under the directory where your Zenoss product is installed.
 * 
 ****************************************************************************/

package com.zenoss.zenpacks.zenjmx.call;

import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.zenoss.jmx.JmxClient;
import com.zenoss.jmx.JmxException;
import com.zenoss.zenpacks.zenjmx.ConfigAdapter;

/**
 * <p> Call for an operation's output.  Operations are difficult from
 * an output-parsing standpoint because the JMX Remote API only
 * returns an Object from the invoke() call.  As a result we have to
 * do a lot of test-casting to see if the result is a Map, List,
 * Object[], or a simple Object.  See the JavaDocs for marshal for
 * additional information. </p>
 *
 * <p>$Author: chris $</p>
 *
 * @author Christopher Blunck
 * @version $Revision: 1.6 $
 */
public class OperationCall extends JmxCall {

    // configuration parameters
    public static final String OPERATION_NAME = "operationName";
    public static final String OPERATION_PARAM_VALUES = "operationParamValues";
    public static final String OPERATION_PARAM_TYPES = "operationParamTypes";

    // demarcates parameter types
    public static final String DELIMITER = ",";

    private static final Set<String> INT_TYPES = new HashSet<String>();
    private static final Set<String> LONG_TYPES = new HashSet<String>();
    private static final Set<String> DOUBLE_TYPES = new HashSet<String>();
    private static final Set<String> FLOAT_TYPES = new HashSet<String>();
    private static final Set<String> STRING_TYPES = new HashSet<String>();
    private static final Set<String> BOOLEAN_TYPES = new HashSet<String>();

    static {
        INT_TYPES.add(Integer.class.getName());
        INT_TYPES.add("int");

        LONG_TYPES.add(Long.class.getName());
        LONG_TYPES.add("long");

        DOUBLE_TYPES.add(Double.class.getName());
        DOUBLE_TYPES.add("double");

        FLOAT_TYPES.add(Float.class.getName());
        FLOAT_TYPES.add("float");

        STRING_TYPES.add(String.class.getName());

        BOOLEAN_TYPES.add(Boolean.class.getName());
        BOOLEAN_TYPES.add("boolean");

    }

    // the name of the operation to invoke
    private String _operationName;

    // parameter values that are passed to the operation
    private Object[] _values;

    // parameter types that are associated with the values
    private String[] _types;

    // the keys we are interested in extracting from the result
    private List<String> _keys;

    // logger
    private static final Log _logger = LogFactory.getLog(OperationCall.class);

    /**
     * Creates a OperationCall
     */
    public OperationCall(String objectName, String operationName, Object[] paramValues, String[] paramTypes,
            List<String> keys, List<String> types) {
        super(objectName);

        _operationName = operationName;
        _values = paramValues;
        _types = paramTypes;
        _keys = keys;

        setTypeMap(buildTypeMap(keys, types));

        _summary.setCallSummary("operation invocation: " + operationName + "(" + paramTypes + ")");
    }

    /**
     * <p>Marshals the results into a Map<String, Object> by iterating
     * over the results and the _keys.  If there are more _keys than
     * results the trailing _keys are NOT set in the Map.</p>
     * @param results the Object[] to marshal
     * @return a Map<String, Object> that contains the results after
     * they have been paired with the _keys
     */
    private Map<String, Object> marshalObjectArray(Object[] results) {
        return marshalList(Arrays.asList(results));
    }

    /**
     * <p>Marshals the results into a Map<String, Object> by iterating
     * over the results and the _keys.  If there are more _keys than
     * results the trailing _keys are NOT set in the Map.</p>
     * @param results the List to marshal
     * @return a Map<String, Object> that contains the results after
     * they have been paired with the _keys
     */
    private Map<String, Object> marshalList(List<Object> results) {
        Map<String, Object> toReturn = new HashMap<String, Object>();

        if (results.size() < _keys.size()) {
            _logger.warn("insufficient result size");
        }

        Iterator<String> keyIter = _keys.iterator();
        for (Object result : results) {
            toReturn.put(keyIter.next(), result);
        }

        return toReturn;
    }

    /**
     * <p>Marshals the results into a Map<String, Object> by iterating
     * over the _keys and extracting the values from the Map provided.
     * </p>
     * @param results the Map to marshal
     * @return a Map<String, Object> that contains the results after
     * they have been paired with the _keys.  Keys that do not appear in
     * the results are returned with a null object.
     */
    private Map<String, Object> marshalMap(Map results) {
        Map<String, Object> toReturn = new HashMap<String, Object>();

        for (String key : _keys) {
            toReturn.put(key, results.get(key));
        }

        return toReturn;
    }

    /**
     * <p> This method encapsulates all of the ugliness associated with
     * extracting data point values from operation invocations.  The API
     * for invocation simply states that the return of invoke() is an
     * Object.  </p>
     *
     * <p> When the Object returned is a Map we simply reduce that Map
     * down using the keys we are interested in using. </p>
     *
     * <p> When the Object returned is an Object[] we iterate over the
     * _keys and use them in the order in which they were defined when
     * populating the results.  For example, if the _keys are:
     * ['gender', 'age', 'location'] we treat Object[0] as 'gender',
     * Object[1] as 'age', and Object[2] as 'location'.  If there are
     * additional Objects in the array we drop them.  If there are more
     * _keys than Objects[] we place null objects in the Map as the
     * value for the keys. </p>
     *
     * <p> When the Object returned is an Object and there is only one
     * value in _keys we simply put the two into the results Map</p>
     * 
     * <p> It's not pretty, but the JMX Remote API doesn't offer us
     * anything more than an Object to deal with so this is the best we
     * can do...  </p>
     *
     * @param response the result from a call to invoke()
     * @return a Map<String, Object> that corresponds to the parsed
     * results
     */
    private Map<String, Object> marshal(Object result) {
        Map<String, Object> toReturn = new HashMap<String, Object>();

        if (result instanceof Object[]) {
            return marshalObjectArray((Object[]) result);
        }

        if (result instanceof List) {
            return marshalList((List) result);
        }

        if (result instanceof Map) {
            return marshalMap((Map) result);
        }

        return marshalObjectArray(new Object[] { result });
    }

    /**
     * @see Callable#call
     */
    public Summary call(JmxClient client) throws JmxException {

        // record when we started
        _startTime = System.currentTimeMillis();
        // issue the query
        Object result = client.invoke(_objectName, _operationName, _values, _types);
        _summary.setResults(marshal(result));

        // record the runtime of the call
        _summary.setRuntime(System.currentTimeMillis() - _startTime);

        // set our id so the processor can remove it from the reactor
        _summary.setCallId(hashCode());

        return _summary;
    }

    /**
     * Creates a OperationCall from the configuration provided
     */
    public static OperationCall fromValue(ConfigAdapter config) throws ConfigurationException {

        String[] paramTypes = config.getOperationParamTypes();

        Object[] paramValues = new Object[] {};
        paramValues = createParamValues(config);

        List<String> keys = config.getDataPoints();
        _logger.debug("keys: " + keys);

        List<String> rrdTypes = config.getDataPointTypes();

        OperationCall call = new OperationCall(config.getOjectName(), config.getOperationName(), paramValues,
                paramTypes, keys, rrdTypes);
        call.setDeviceId(config.getDevice());
        call.setDataSourceId(config.getDatasourceId());

        return call;
    }

    private static Object[] createParamValues(ConfigAdapter config) throws ConfigurationException {
        String[] params = config.getOperationParamValues();
        String[] paramTypes = config.getOperationParamTypes();

        if (params.length != paramTypes.length) {
            throw new ConfigurationException("Datasource " + config.getDatasourceId()
                    + " number of parameter types and " + "parameter values does not match");
        }
        Object[] values = new Object[params.length];
        for (int i = 0; i < params.length; i++) {

            String type = paramTypes[i].trim();
            String valueStr = params[i].trim();
            Object resultValue = null;
            try {
                if (INT_TYPES.contains(type)) {
                    resultValue = Integer.valueOf(valueStr);
                } else if (DOUBLE_TYPES.contains(type)) {
                    resultValue = Double.valueOf(valueStr);
                } else if (LONG_TYPES.contains(type)) {
                    resultValue = Long.valueOf(valueStr);
                } else if (FLOAT_TYPES.contains(type)) {
                    resultValue = Float.valueOf(valueStr);
                } else if (STRING_TYPES.contains(type)) {
                    resultValue = valueStr;
                } else if (BOOLEAN_TYPES.contains(type)) {
                    resultValue = new Boolean(valueStr);
                } else {
                    throw new ConfigurationException("Datasource " + config.getDatasourceId() + " Type " + type
                            + " is not handled for operation calls");
                }
            } catch (NumberFormatException e) {
                throw new ConfigurationException(
                        String.format("Datasource %1$s; value %2$s could not be converted to %3$s",
                                config.getDatasourceId(), valueStr, type));
            }
            values[i] = resultValue;
        }

        return values;
    }

    /**
     * Returns the name of the operation to invoke
     */
    public String getOperationName() {
        return _operationName;
    }

    /**
     * Returns the values of the parameters passed to the operation
     */
    public Object[] getValues() {
        return _values;
    }

    /**
     * Returns the types of the parameters passed to the operation
     */
    public String[] getTypes() {
        return _types;
    }

    /**
     * Returns the keys we are interested in extracting from the result
     */
    public List<String> getKeys() {
        return _keys;
    }

    /**
     * @see JmxCall#hashCode
     */
    public int hashCode() {
        int hc = 0;

        hc += super.hashCode();
        hc += hashCode(_operationName);

        // arrays with the same contents do not return the same hashCode value
        for (String type : _types) {
            hc += hashCode(type);
        }

        // arrays with the same contents do not return the same hashCode value
        for (Object value : _values) {
            hc += hashCode(value);
        }

        // collections with the same contents do return the same hashCode
        hc += hashCode(_keys);

        return hc;
    }

    /**
     * @see Object#equals
     */
    public boolean equals(Object other) {
        if (!(other instanceof OperationCall)) {
            return false;
        }

        boolean toReturn = super.equals(other);

        OperationCall call = (OperationCall) other;

        toReturn &= Utility.equals(call.getOperationName(), getOperationName());
        toReturn &= Utility.equals(call.getValues(), getValues());
        toReturn &= Utility.equals(call.getTypes(), getTypes());
        toReturn &= Utility.equals(call.getKeys(), getKeys());

        return toReturn;
    }
}