blob: 7b26cde8f2eac730f4cc75575b79de22ebec522f [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The SF licenses this file
* to you 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 org.apache.felix.hc.jmx.impl;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.AttributeNotFoundException;
import javax.management.DynamicMBean;
import javax.management.InvalidAttributeValueException;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanException;
import javax.management.MBeanInfo;
import javax.management.ReflectionException;
import javax.management.openmbean.CompositeDataSupport;
import javax.management.openmbean.CompositeType;
import javax.management.openmbean.OpenDataException;
import javax.management.openmbean.OpenMBeanAttributeInfoSupport;
import javax.management.openmbean.OpenType;
import javax.management.openmbean.SimpleType;
import javax.management.openmbean.TabularData;
import javax.management.openmbean.TabularDataSupport;
import javax.management.openmbean.TabularType;
import org.apache.felix.hc.api.HealthCheck;
import org.apache.felix.hc.api.Result;
import org.apache.felix.hc.api.ResultLog;
import org.apache.felix.hc.api.execution.HealthCheckExecutionResult;
import org.apache.felix.hc.core.impl.executor.ExtendedHealthCheckExecutor;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceReference;
/** A {@link DynamicMBean} used to execute a {@link HealthCheck} service */
public class HealthCheckMBean implements DynamicMBean {
private static final String HC_OK_ATTRIBUTE_NAME = "ok";
private static final String HC_STATUS_ATTRIBUTE_NAME = "status";
private static final String HC_LOG_ATTRIBUTE_NAME = "log";
private static final String HC_TIMED_OUT_ATTRIBUTE_NAME = "timedOut";
private static final String HC_ELAPSED_TIMED_ATTRIBUTE_NAME = "elapsedTime";
private static final String HC_FINISHED_AT_ATTRIBUTE_NAME = "finishedAt";
private static CompositeType LOG_ROW_TYPE;
private static TabularType LOG_TABLE_TYPE;
private static final String INDEX_COLUMN = "index";
private static final String LEVEL_COLUMN = "level";
private static final String MESSAGE_COLUMN = "message";
/** The health check service to call. */
private final ServiceReference<HealthCheck> healthCheckRef;
/** The executor service. */
private final ExtendedHealthCheckExecutor executor;
/** The mbean info. */
private final MBeanInfo mbeanInfo;
/** The default attributes. */
private final Map<String, Object> defaultAttributes;
static {
try {
// Define the log row and table types
LOG_ROW_TYPE = new CompositeType(
"LogLine",
"A line in the result log",
new String[] { INDEX_COLUMN, LEVEL_COLUMN, MESSAGE_COLUMN },
new String[] { "log line index", "log level", "log message" },
new OpenType[] { SimpleType.INTEGER, SimpleType.STRING, SimpleType.STRING });
final String[] indexes = { INDEX_COLUMN };
LOG_TABLE_TYPE = new TabularType("LogTable", "Result log messages", LOG_ROW_TYPE, indexes);
} catch (Exception ignore) {
// row or table type will be null if this happens
}
}
public HealthCheckMBean(final ServiceReference<HealthCheck> ref, final ExtendedHealthCheckExecutor executor) {
this.healthCheckRef = ref;
this.executor = executor;
this.mbeanInfo = this.createMBeanInfo(ref);
this.defaultAttributes = this.createDefaultAttributes(ref);
}
@Override
public Object getAttribute(final String attribute)
throws AttributeNotFoundException, MBeanException, ReflectionException {
// we should call getAttributes - and not vice versa to have the result
// of a single check call - and not do a check call for each attribute
final AttributeList result = this.getAttributes(new String[] { attribute });
if (result.size() == 0) {
throw new AttributeNotFoundException(attribute);
}
final Attribute attr = (Attribute) result.get(0);
return attr.getValue();
}
private TabularData logData(final Result er) throws OpenDataException {
final TabularDataSupport result = new TabularDataSupport(LOG_TABLE_TYPE);
int i = 1;
for (final ResultLog.Entry e : er) {
final Map<String, Object> data = new HashMap<String, Object>();
data.put(INDEX_COLUMN, i++);
data.put(LEVEL_COLUMN, e.getLogLevel());
data.put(MESSAGE_COLUMN, e.getMessage());
result.put(new CompositeDataSupport(LOG_ROW_TYPE, data));
}
return result;
}
@Override
public AttributeList getAttributes(final String[] attributes) {
final AttributeList result = new AttributeList();
if (attributes != null) {
HealthCheckExecutionResult hcResult = null;
for (final String key : attributes) {
final Object defaultValue = this.defaultAttributes.get(key);
if (defaultValue != null) {
result.add(new Attribute(key, defaultValue));
} else {
// we assume that a valid attribute name is used
// which is requesting a hc result
if (hcResult == null) {
hcResult = this.getHealthCheckResult();
}
if (HC_OK_ATTRIBUTE_NAME.equals(key)) {
result.add(new Attribute(key, hcResult.getHealthCheckResult().isOk()));
} else if (HC_LOG_ATTRIBUTE_NAME.equals(key)) {
try {
result.add(new Attribute(key, logData(hcResult.getHealthCheckResult())));
} catch (final OpenDataException ignore) {
// we ignore this and simply don't add the attribute
}
} else if (HC_STATUS_ATTRIBUTE_NAME.equals(key)) {
result.add(new Attribute(key, hcResult.getHealthCheckResult().getStatus().toString()));
} else if (HC_ELAPSED_TIMED_ATTRIBUTE_NAME.equals(key)) {
result.add(new Attribute(key, hcResult.getElapsedTimeInMs()));
} else if (HC_FINISHED_AT_ATTRIBUTE_NAME.equals(key)) {
result.add(new Attribute(key, hcResult.getFinishedAt()));
} else if (HC_TIMED_OUT_ATTRIBUTE_NAME.equals(key)) {
result.add(new Attribute(key, hcResult.hasTimedOut()));
}
}
}
}
return result;
}
/** Create the mbean info */
private MBeanInfo createMBeanInfo(final ServiceReference<HealthCheck> serviceReference) {
final List<MBeanAttributeInfo> attrs = new ArrayList<MBeanAttributeInfo>();
// add relevant service properties
if (serviceReference.getProperty(HealthCheck.NAME) != null) {
attrs.add(new MBeanAttributeInfo(HealthCheck.NAME, String.class.getName(), "The name of the health check service.", true, false,
false));
}
if (serviceReference.getProperty(HealthCheck.TAGS) != null) {
attrs.add(new MBeanAttributeInfo(HealthCheck.TAGS, String.class.getName(), "The tags of the health check service.", true, false,
false));
}
// add standard attributes
attrs.add(new MBeanAttributeInfo(HC_OK_ATTRIBUTE_NAME, Boolean.class.getName(), "The health check result", true, false, false));
attrs.add(new MBeanAttributeInfo(HC_STATUS_ATTRIBUTE_NAME, String.class.getName(), "The health check status", true, false, false));
attrs.add(new MBeanAttributeInfo(HC_ELAPSED_TIMED_ATTRIBUTE_NAME, Long.class.getName(), "The elapsed time in miliseconds", true,
false, false));
attrs.add(new MBeanAttributeInfo(HC_FINISHED_AT_ATTRIBUTE_NAME, Date.class.getName(), "The date when the execution finished", true,
false, false));
attrs.add(new MBeanAttributeInfo(HC_TIMED_OUT_ATTRIBUTE_NAME, Boolean.class.getName(), "Indicates of the execution timed out", true,
false, false));
attrs.add(new OpenMBeanAttributeInfoSupport(HC_LOG_ATTRIBUTE_NAME, "The health check result log", LOG_TABLE_TYPE, true, false,
false));
final String description;
if (serviceReference.getProperty(Constants.SERVICE_DESCRIPTION) != null) {
description = serviceReference.getProperty(Constants.SERVICE_DESCRIPTION).toString();
} else {
description = "Health check";
}
return new MBeanInfo(this.getClass().getName(),
description,
attrs.toArray(new MBeanAttributeInfo[attrs.size()]), null, null, null);
}
/** Create the default attributes. */
private Map<String, Object> createDefaultAttributes(final ServiceReference<HealthCheck> serviceReference) {
final Map<String, Object> list = new HashMap<String, Object>();
if (serviceReference.getProperty(HealthCheck.NAME) != null) {
list.put(HealthCheck.NAME, serviceReference.getProperty(HealthCheck.NAME).toString());
}
if (serviceReference.getProperty(HealthCheck.TAGS) != null) {
final Object value = serviceReference.getProperty(HealthCheck.TAGS);
if (value instanceof String[]) {
list.put(HealthCheck.TAGS, Arrays.toString((String[]) value));
} else {
list.put(HealthCheck.TAGS, value.toString());
}
}
return list;
}
@Override
public MBeanInfo getMBeanInfo() {
return this.mbeanInfo;
}
@Override
public Object invoke(final String actionName, final Object[] params, final String[] signature)
throws MBeanException, ReflectionException {
throw new MBeanException(new UnsupportedOperationException(getClass().getSimpleName() + " does not support operations."));
}
@Override
public void setAttribute(final Attribute attribute)
throws AttributeNotFoundException, InvalidAttributeValueException,
MBeanException, ReflectionException {
throw new MBeanException(new UnsupportedOperationException(getClass().getSimpleName() + " does not support setting attributes."));
}
@Override
public AttributeList setAttributes(final AttributeList attributes) {
return new AttributeList();
}
@Override
public String toString() {
return "HealthCheckMBean [healthCheck=" + this.healthCheckRef + "]";
}
private HealthCheckExecutionResult getHealthCheckResult() {
return this.executor.execute(this.healthCheckRef);
}
}