de.joerghoh.cq5.healthcheck.impl.providers.MBeanStatusProvider.java Source code

Java tutorial

Introduction

Here is the source code for de.joerghoh.cq5.healthcheck.impl.providers.MBeanStatusProvider.java

Source

/*
 * Copyright 2012 Jrg Hoh, Alexander Saar, Markus Haack
 * 
 *  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.joerghoh.cq5.healthcheck.impl.providers;

import java.lang.management.ManagementFactory;
import java.util.Dictionary;
import java.util.Set;

import javax.jcr.RepositoryException;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.ConfigurationPolicy;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Service;
import org.apache.sling.commons.osgi.PropertiesUtil;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.joerghoh.cq5.healthcheck.Status;
import de.joerghoh.cq5.healthcheck.StatusCode;
import de.joerghoh.cq5.healthcheck.StatusProvider;

@Service
@Component(label = "MBean Status Provider Factory", metatype = true, configurationFactory = true, policy = ConfigurationPolicy.REQUIRE)
public class MBeanStatusProvider implements StatusProvider {

    private static final Logger log = LoggerFactory.getLogger(MBeanStatusProvider.class);

    private MBeanServer server = ManagementFactory.getPlatformMBeanServer();

    @Property
    private static String CATEGORY = "provider.category";
    private String category;

    @Property
    private static String MBEAN_NAME = "mbean.name";
    private String mbeanName;

    @Property
    private static String MBEAN_PROPERTY = "mbean.property";
    private String[] properties;

    @Property
    private static String MBEAN_PROVIDER_HINT = "mbean.providerHint";
    private String providerHint;
    private String providerName;

    private ObjectName mbean;
    private String statusMessage = "";

    /**
     * @see de.joerghoh.cq5.healthcheck.HealthStatusProvider#getHealthStatus()
     */
    public Status getStatus() {
        mbean = buildObjectName(mbeanName);
        if (mbean != null && mbeanExists(mbean)) {
            StatusCode status = calculateStatus();
            return new Status(status, statusMessage, providerName);
        } else {
            log.info("Cannot resolve mbean " + mbeanName);
            return new Status(StatusCode.UNKNOWN, "cannot find mbean", providerName);
        }
    }

    /**
     * @see de.joerghoh.cq5.healthcheck.StatusProvider#getCategory()
     */
    public String getCategory() {
        return category != null ? category : DEFAULT_CATEGORY;
    }

    @Activate
    protected void activate(ComponentContext ctx) throws RepositoryException {

        Dictionary<?, ?> props = ctx.getProperties();
        category = PropertiesUtil.toString(props.get(CATEGORY), null);
        mbeanName = PropertiesUtil.toString(props.get(MBEAN_NAME), null);
        properties = PropertiesUtil.toStringArray(props.get(MBEAN_PROPERTY));
        providerHint = PropertiesUtil.toString(props.get(MBEAN_PROVIDER_HINT), null);

        mbean = buildObjectName(mbeanName);
        if (mbean != null && mbeanExists(mbean)) {
            log.info("Instantiate healtcheck for MBean {}", mbeanName);
        } else {
            log.warn("Cannot instantiate healthcheck for MBean {}", mbeanName);
        }
        providerName = mbeanName;
        if (providerHint != null) {
            providerName += " (" + providerHint + ")";
        }
    }

    @Deactivate
    protected void deactivate() {

    }

    /**
     * calculate the overall status of the service
     * 
     * @return the overall status
     */

    private StatusCode calculateStatus() {
        StatusCode accumulatedStatus = StatusCode.OK;

        statusMessage = "";

        for (String property : properties) {

            /*
             * value might be: "my.JMX.propertyName.warn.>", and we need the
             * triple of "jmxAttributeName","level" and "type of comparison".
             * The jmxAttributeName might contain dots, so we need to reverse to
             * use String.split()
             */

            final String key = new StringBuffer(property).reverse().toString(); // revert
            // it
            final String[] split = key.split("\\.", 4);
            if (split.length != 4) {
                // key does not match the needed configuration triple
                continue;
            }
            final String comparisonAttributeName = new StringBuffer(split[3]).reverse().toString();

            // what would be the statusCode if we have a match?

            final String comparisonLevel = new StringBuffer(split[2]).reverse().toString();
            StatusCode statusCode = StatusCode.OK;

            if (comparisonLevel.equalsIgnoreCase("warn")) {
                statusCode = StatusCode.WARN;
            } else if (comparisonLevel.equalsIgnoreCase("critical")) {
                statusCode = StatusCode.CRITICAL;
            } else {
                log.warn("Ignoring property (invalid level): " + property);
                continue;
            }

            final String comparisonOperation = new StringBuffer(split[1]).reverse().toString();

            // retrieve the long value for comparison
            final String comparisonValue = new StringBuffer(split[0]).reverse().toString();
            log.debug("compare {} and {}", property, comparisonValue);

            // read the correct value via JMX
            Object jmxValueObj = getAttributeValue(comparisonAttributeName);
            if (jmxValueObj == null) {

                log.warn("Ignoring property " + property + " (no such JMX attribute " + comparisonAttributeName
                        + ")");
                statusMessage = "Cannot resolve property or MBean";
                return StatusCode.WARN;
            }
            log.debug("jmx value = {}", jmxValueObj);

            // do the comparison
            boolean match = false;
            try {
                match = compareAttributeValue(comparisonOperation, comparisonValue, jmxValueObj);
            } catch (RuntimeException e) {
                log.info("Ignoring property (invalid value type): " + property);
                continue;
            }

            // if we have a WARN or CRITICAL state for this value, report it
            if (match) {
                if (statusMessage.length() > 0) {
                    statusMessage += ", ";
                }
                statusMessage += comparisonAttributeName + " = "
                        + (jmxValueObj.getClass().isArray() ? ArrayUtils.toString(jmxValueObj)
                                : jmxValueObj.toString());
            }

            // if we have a match, update the overall status
            if (match && statusCode.compareTo(accumulatedStatus) > 0) {
                accumulatedStatus = statusCode;
            }
        }
        return accumulatedStatus;
    }

    /**
     * small wrapper method to read the value via JMX
     * 
     * @param attributeName
     * @return
     */
    private Object getAttributeValue(String attributeName) {
        try {
            return server.getAttribute(mbean, attributeName);
        } catch (Exception e) {
            log.warn("Cannot read attribute " + attributeName + " from MBean " + mbean);
        }
        return null;
    }

    /**
     * compare helper method to compare the different types of attribute values
     * supported types: ** long ** integer ** boolean ** string
     * 
     * @param comparisonOperation
     * @param comparisonValue
     * @param jmxValueObj
     * @return
     */
    private boolean compareAttributeValue(final String comparisonOperation, final String comparisonValue,
            Object jmxValueObj) {

        boolean match = false;

        if (jmxValueObj instanceof Long) { // first check for plain value
            final long jmxLongValue = Long.parseLong(jmxValueObj.toString());
            final long comparisonLongValue = NumberUtils.createLong(comparisonValue);
            if (comparisonOperation.equals(">")) {
                match = (jmxLongValue > comparisonLongValue);
            } else if (comparisonOperation.equals("==")) {
                match = (jmxLongValue == comparisonLongValue);
            } else if (comparisonOperation.equals("!=")) {
                match = (jmxLongValue != comparisonLongValue);
            } else if (comparisonOperation.equals("<")) {
                match = (jmxLongValue < comparisonLongValue);
            } else {
                log.warn("Can not compare long values {} and {}", jmxLongValue, comparisonLongValue);
                throw new RuntimeException();
            }

        } else if (jmxValueObj instanceof Integer) {
            final int jmxIntValue = Integer.parseInt(jmxValueObj.toString());
            final int comparisonIntValue = NumberUtils.createInteger(comparisonValue);
            if (comparisonOperation.equals(">")) {
                match = (jmxIntValue > comparisonIntValue);
            } else if (comparisonOperation.equals("==")) {
                match = (jmxIntValue == comparisonIntValue);
            } else if (comparisonOperation.equals("!=")) {
                match = (jmxIntValue != comparisonIntValue);
            } else if (comparisonOperation.equals("<")) {
                match = (jmxIntValue < comparisonIntValue);
            } else {
                log.warn("Can not compare int values {} and {}", jmxIntValue, comparisonIntValue);
                throw new RuntimeException();
            }

        } else if (jmxValueObj instanceof long[]) { // second check for array
            final long[] jmxLongArrayValue = (long[]) jmxValueObj;
            final long comparisonLongValue = NumberUtils.createLong(comparisonValue);
            if (comparisonOperation.equals(">")) {
                match = (NumberUtils.max(jmxLongArrayValue) > comparisonLongValue);
            } else if (comparisonOperation.equals("==")) {
                // TODO maybe it makes more sense for == to compare all array
                // values?
                match = ArrayUtils.contains(jmxLongArrayValue, comparisonLongValue);
            } else if (comparisonOperation.equals("<")) {
                match = (NumberUtils.min(jmxLongArrayValue) < comparisonLongValue);
            } else {
                log.warn("Can not compare long array values {} and {}", jmxLongArrayValue, comparisonLongValue);
                throw new RuntimeException();
            }
        } else if (jmxValueObj instanceof String) { // third check for String
            // values
            log.info("type comparison: string");
            final String jmxStringValue = jmxValueObj.toString();
            if (comparisonOperation.equals("equals")) {
                match = (comparisonValue.equals(jmxStringValue));
            } else if (comparisonOperation.equals("notequals")) {
                match = (!comparisonValue.equals(jmxStringValue));
            } else {
                log.warn("Can not compare String values {} and {}", jmxStringValue, comparisonValue);
                throw new RuntimeException();
            }
        } else if (jmxValueObj instanceof Boolean) {
            final boolean jmxBooleanValue = (Boolean) jmxValueObj;
            final boolean comparisonBooleanValue = Boolean.parseBoolean(comparisonValue);
            if (comparisonOperation.equals("equals")) {
                match = (comparisonBooleanValue == jmxBooleanValue);
            } else if (comparisonOperation.equals("==")) {
                match = (comparisonBooleanValue == jmxBooleanValue);
            } else if (comparisonOperation.equals("notequals")) {
                match = (comparisonBooleanValue != jmxBooleanValue);
            } else {
                log.warn("Can not compare boolean values {} and {}", jmxBooleanValue, comparisonBooleanValue);
                throw new RuntimeException();
            }
        } else {
            log.warn("Can not compare jmx attribute value {} with {}", jmxValueObj, comparisonValue);
            log.warn("jmxValueObj type = " + jmxValueObj.getClass().getName());
            throw new RuntimeException();
        }
        return match;
    }

    private ObjectName buildObjectName(String name) {
        ObjectName mbean = null;
        try {
            mbean = new ObjectName(name);
        } catch (MalformedObjectNameException e) {
            log.error("Cannot create ObjectName " + name, e);
        } catch (NullPointerException e) {
            log.error("Cannot create ObjectName " + name, e);
        }
        return mbean;
    }

    private boolean mbeanExists(ObjectName mbean) {
        Set<ObjectName> beans = server.queryNames(mbean, null);
        return (beans.size() == 1);
    }
}