blob: b0db8a95396259878017d6d8691af663d0452f61 [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 ASF 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.ignite.internal.mxbean;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.HashMap;
import java.util.Map;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanInfo;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanParameterInfo;
import javax.management.NotCompliantMBeanException;
import javax.management.StandardMBean;
import org.apache.ignite.internal.util.typedef.internal.U;
import org.apache.ignite.mxbean.MXBeanDescription;
import org.apache.ignite.mxbean.MXBeanParameter;
import org.apache.ignite.mxbean.MXBeanParametersDescriptions;
import org.apache.ignite.mxbean.MXBeanParametersNames;
/**
* Extension of standard Java MBean. Overrides some hooks to return
* annotation based descriptions.
*/
public class IgniteStandardMXBean extends StandardMBean {
/** */
private static final String DESC_MUST_START_WITH_UPP_CASE = "Description must start with upper case: ";
/** */
private static final String DESC_MUST_END_WITH_PERIOD = "Description must end with period: ";
/** */
private static final String DESC_MUST_BE_NOT_NULL = "Description must be not null: ";
/** */
private static final String DESC_MUST_BE_NOT_EMPTY = "Description must be not empty: ";
/** */
private static final String NAME_MUST_BE_NOT_NULL = "Parameter name must be not null: ";
/** */
private static final String NAME_MUST_BE_NOT_EMPTY = "Parameter name must be not empty: ";
/**
* Objects maps from primitive classes to primitive object classes.
*/
private static final Map<String, Class<?>> primCls = new HashMap<>();
/**
* Static constructor.
*/
static{
primCls.put(Boolean.TYPE.toString().toLowerCase(), Boolean.TYPE);
primCls.put(Character.TYPE.toString().toLowerCase(), Character.TYPE);
primCls.put(Byte.TYPE.toString().toLowerCase(), Byte.TYPE);
primCls.put(Short.TYPE.toString().toLowerCase(), Short.TYPE);
primCls.put(Integer.TYPE.toString().toLowerCase(), Integer.TYPE);
primCls.put(Long.TYPE.toString().toLowerCase(), Long.TYPE);
primCls.put(Float.TYPE.toString().toLowerCase(), Float.TYPE);
primCls.put(Double.TYPE.toString().toLowerCase(), Double.TYPE);
}
/**
* Make a DynamicMBean out of the object implementation, using the specified
* mbeanInterface class.
*
* @param implementation The implementation of this MBean.
* @param mbeanInterface The Management Interface exported by this
* MBean's implementation. If {@code null}, then this
* object will use standard JMX design pattern to determine
* the management interface associated with the given
* implementation.
* If {@code null} value passed then information will be built by
* {@link StandardMBean}
*
* @exception NotCompliantMBeanException if the {@code mbeanInterface}
* does not follow JMX design patterns for Management Interfaces, or
* if the given {@code implementation} does not implement the
* specified interface.
*/
public <T> IgniteStandardMXBean(T implementation, Class<T> mbeanInterface)
throws NotCompliantMBeanException {
super(implementation, mbeanInterface);
}
/** {@inheritDoc} */
@Override protected String getDescription(MBeanAttributeInfo info) {
String str = super.getDescription(info);
String methodName = (info.isIs() ? "is" : "get") + info.getName();
try {
// Recursively get method.
Method mtd = findMethod(getMBeanInterface(), methodName, new Class[]{});
if (mtd != null) {
MXBeanDescription desc = mtd.getAnnotation(MXBeanDescription.class);
if (desc != null) {
str = desc.value();
assert str != null : "Failed to find method: " + mtd;
assert !str.trim().isEmpty() : "Method description cannot be empty: " + mtd;
// Enforce proper English.
assert Character.isUpperCase(str.charAt(0)) : DESC_MUST_START_WITH_UPP_CASE + str;
assert str.charAt(str.length() - 1) == '.' : DESC_MUST_END_WITH_PERIOD + str;
}
}
}
catch (SecurityException ignored) {
// No-op. Default value will be returned.
}
return str;
}
/** {@inheritDoc} */
@Override protected String getDescription(MBeanInfo info) {
String str = super.getDescription(info);
// Return either default one or given by annotation.
MXBeanDescription desc = U.getAnnotation(getMBeanInterface(), MXBeanDescription.class);
if (desc != null) {
str = desc.value();
assert str != null;
assert !str.trim().isEmpty();
// Enforce proper English.
assert Character.isUpperCase(str.charAt(0)) : DESC_MUST_START_WITH_UPP_CASE + str;
assert str.charAt(str.length() - 1) == '.' : DESC_MUST_END_WITH_PERIOD + str;
}
return str;
}
/** {@inheritDoc} */
@Override protected String getDescription(MBeanOperationInfo info) {
String str = super.getDescription(info);
try {
Method m = getMethod(info);
MXBeanDescription desc = m.getAnnotation(MXBeanDescription.class);
if (desc != null) {
str = desc.value();
assert str != null;
assert !str.trim().isEmpty();
// Enforce proper English.
assert Character.isUpperCase(str.charAt(0)) : DESC_MUST_START_WITH_UPP_CASE + str;
assert str.charAt(str.length() - 1) == '.' : DESC_MUST_END_WITH_PERIOD + str;
}
}
catch (SecurityException | ClassNotFoundException ignored) {
// No-op. Default value will be returned.
}
return str;
}
/** {@inheritDoc} */
@Override protected String getDescription(MBeanOperationInfo op, MBeanParameterInfo param, int seq) {
String str = super.getDescription(op, param, seq);
try {
Method m = getMethod(op);
MXBeanParametersDescriptions decsAnn = m.getAnnotation(MXBeanParametersDescriptions.class);
if (decsAnn != null) {
assert decsAnn.value() != null;
assert seq < decsAnn.value().length;
str = decsAnn.value()[seq];
assert str != null : DESC_MUST_BE_NOT_NULL + str;
assert !str.trim().isEmpty() : DESC_MUST_BE_NOT_EMPTY + str;
// Enforce proper English.
assert Character.isUpperCase(str.charAt(0)) : DESC_MUST_START_WITH_UPP_CASE + str;
assert str.charAt(str.length() - 1) == '.' : DESC_MUST_END_WITH_PERIOD + str;
}
else {
MXBeanParameter argInfoAnnotation = getMXBeanParameterAnnotation(m, seq);
if (argInfoAnnotation != null) {
str = argInfoAnnotation.description();
assert str != null : DESC_MUST_BE_NOT_NULL + str;
assert !str.trim().isEmpty() : DESC_MUST_BE_NOT_EMPTY + str;
// Enforce proper English.
assert Character.isUpperCase(str.charAt(0)) : DESC_MUST_START_WITH_UPP_CASE + str;
assert str.charAt(str.length() - 1) == '.' : DESC_MUST_END_WITH_PERIOD + str;
}
}
}
catch (SecurityException | ClassNotFoundException ignored) {
// No-op. Default value will be returned.
}
return str;
}
/** {@inheritDoc} */
@Override protected String getParameterName(MBeanOperationInfo op, MBeanParameterInfo param, int seq) {
String str = super.getParameterName(op, param, seq);
try {
Method m = getMethod(op);
MXBeanParametersNames namesAnn = m.getAnnotation(MXBeanParametersNames.class);
if (namesAnn != null) {
assert namesAnn.value() != null;
assert seq < namesAnn.value().length;
str = namesAnn.value()[seq];
assert str != null : NAME_MUST_BE_NOT_NULL + str;
assert !str.trim().isEmpty() : NAME_MUST_BE_NOT_EMPTY + str;
}
else {
MXBeanParameter argInfoAnnotation = getMXBeanParameterAnnotation(m, seq);
if (argInfoAnnotation != null) {
str = argInfoAnnotation.name();
assert str != null : NAME_MUST_BE_NOT_NULL + str;
assert !str.trim().isEmpty() : NAME_MUST_BE_NOT_EMPTY + str;
}
}
}
catch (SecurityException | ClassNotFoundException ignored) {
// No-op. Default value will be returned.
}
return str;
}
/**
* Gets {@link MXBeanParameter} annotation instance from method if possible, otherwise returns {@code null}.
*
* @param m Method instance.
* @param seq The sequence number of the argument considered ("0" for the first parameter,
* "1" for the second parameter, etc...)
* @return {@link MXBeanParameter} annotation instance.
*/
private MXBeanParameter getMXBeanParameterAnnotation(Method m, int seq) {
Parameter[] params = m.getParameters();
if (seq < params.length)
return params[seq].getAnnotation(MXBeanParameter.class);
return null;
}
/**
* Gets method by operation info.
*
* @param op MBean operation info.
* @return Method.
* @throws ClassNotFoundException Thrown if parameter type is unknown.
* @throws SecurityException Thrown if method access is not allowed.
*/
private Method getMethod(MBeanOperationInfo op) throws ClassNotFoundException, SecurityException {
String mtdName = op.getName();
MBeanParameterInfo[] signature = op.getSignature();
Class<?>[] params = new Class<?>[signature.length];
for (int i = 0; i < signature.length; i++) {
// Parameter type is either a primitive type or class. Try both.
Class<?> type = primCls.get(signature[i].getType().toLowerCase());
if (type == null)
type = Class.forName(signature[i].getType());
params[i] = type;
}
return findMethod(getMBeanInterface(), mtdName, params);
}
/**
* Finds method for the given interface.
*
* @param itf MBean interface.
* @param methodName Method name.
* @param params Method parameters.
* @return Method.
*/
@SuppressWarnings("unchecked")
private Method findMethod(Class itf, String methodName, Class[] params) {
assert itf.isInterface() : itf + " must represent the interface";
Method res = null;
// Try to get method from given interface.
try {
res = itf.getDeclaredMethod(methodName, params);
if (res != null)
return res;
}
catch (NoSuchMethodException ignored) {
// No-op. Default value will be returned.
}
// Process recursively super interfaces.
Class[] superItfs = itf.getInterfaces();
for (Class superItf: superItfs) {
res = findMethod(superItf, methodName, params);
if (res != null)
return res;
}
return res;
}
}