Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright (C) 2020 ActiveJ LLC.
*
* 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 io.activej.jmx;
import io.activej.common.ref.Ref;
import io.activej.common.reflection.ReflectionUtils;
import io.activej.jmx.api.ConcurrentJmxBeanAdapter;
import io.activej.jmx.api.JmxBeanAdapter;
import io.activej.jmx.api.JmxBeanAdapterWithRefresh;
import io.activej.jmx.api.JmxRefreshable;
import io.activej.jmx.api.attribute.JmxAttribute;
import io.activej.jmx.api.attribute.JmxOperation;
import io.activej.jmx.api.attribute.JmxParameter;
import io.activej.jmx.api.attribute.JmxReducer;
import io.activej.jmx.api.attribute.JmxReducers.JmxReducerDistinct;
import io.activej.jmx.stats.JmxRefreshableStats;
import io.activej.jmx.stats.JmxStats;
import io.activej.types.Types;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.management.*;
import javax.management.openmbean.OpenType;
import javax.management.openmbean.SimpleType;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.function.Function;
import java.util.function.UnaryOperator;
import static io.activej.common.Checks.*;
import static io.activej.common.Utils.first;
import static io.activej.common.Utils.nonNullElse;
import static io.activej.common.reflection.ReflectionUtils.*;
import static io.activej.jmx.Utils.findAdapterClass;
import static io.activej.jmx.stats.StatsUtils.isJmxStats;
import static java.lang.String.format;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toSet;
@SuppressWarnings("rawtypes")
public final class DynamicMBeanFactory {
private static final Logger logger = LoggerFactory.getLogger(DynamicMBeanFactory.class);
// refreshing jmx
public static final Duration DEFAULT_REFRESH_PERIOD_IN_SECONDS = Duration.ofSeconds(1);
public static final int MAX_JMX_REFRESHES_PER_ONE_CYCLE_DEFAULT = 500;
private int maxJmxRefreshesPerOneCycle;
private Duration specifiedRefreshPeriod;
private final Map, JmxBeanAdapter> adapters = new HashMap<>();
private static final JmxReducer> DEFAULT_REDUCER = new JmxReducerDistinct();
// JmxStats creator methods
private static final String CREATE = "create";
private static final String CREATE_ACCUMULATOR = "createAccumulator";
private static final DynamicMBeanFactory INSTANCE_WITH_DEFAULT_REFRESH_PERIOD = new DynamicMBeanFactory(DEFAULT_REFRESH_PERIOD_IN_SECONDS, MAX_JMX_REFRESHES_PER_ONE_CYCLE_DEFAULT);
private DynamicMBeanFactory(Duration refreshPeriod, int maxJmxRefreshesPerOneCycle) {
this.specifiedRefreshPeriod = refreshPeriod;
this.maxJmxRefreshesPerOneCycle = maxJmxRefreshesPerOneCycle;
}
public static DynamicMBeanFactory create() {
return INSTANCE_WITH_DEFAULT_REFRESH_PERIOD;
}
public static DynamicMBeanFactory create(Duration refreshPeriod, int maxJmxRefreshesPerOneCycle) {
return new DynamicMBeanFactory(refreshPeriod, maxJmxRefreshesPerOneCycle);
}
// region exportable stats for JmxRegistry
public Duration getSpecifiedRefreshPeriod() {
return specifiedRefreshPeriod;
}
public void setRefreshPeriod(Duration refreshPeriod) {
this.specifiedRefreshPeriod = refreshPeriod;
updateAdaptersRefreshParameters();
}
public int getMaxJmxRefreshesPerOneCycle() {
return maxJmxRefreshesPerOneCycle;
}
public void setMaxJmxRefreshesPerOneCycle(int maxJmxRefreshesPerOneCycle) {
this.maxJmxRefreshesPerOneCycle = maxJmxRefreshesPerOneCycle;
updateAdaptersRefreshParameters();
}
public String[] getRefreshStats() {
return adapters.values().stream()
.filter(adapter -> adapter instanceof JmxBeanAdapterWithRefresh)
.map(JmxBeanAdapterWithRefresh.class::cast)
.map(JmxBeanAdapterWithRefresh::getRefreshStats)
.flatMap(Collection::stream)
.toArray(String[]::new);
}
// endregion
/**
* Creates Jmx MBean for beans with operations and attributes.
*/
public DynamicMBean createDynamicMBean(List> beans, JmxBeanSettings setting, boolean enableRefresh) {
checkArgument(!beans.isEmpty(), "List of beans should not be empty");
checkArgument(beans.stream().noneMatch(Objects::isNull), "Bean can not be null");
checkArgument(beans.stream().map(Object::getClass).collect(toSet()).size() == 1, "Beans should be of the same type");
Class> beanClass = beans.get(0).getClass();
JmxBeanAdapter adapter = ensureAdapter(beanClass);
if (adapter.getClass().equals(ConcurrentJmxBeanAdapter.class)) {
checkArgument(beans.size() == 1, "ConcurrentJmxBeans cannot be used in pool");
}
AttributeNodeForPojo rootNode = createAttributesTree(beanClass, setting.getCustomTypes());
rootNode.hideNullPojos(beans);
for (String included : setting.getIncludedOptionals()) {
rootNode.setVisible(included);
}
// TODO(vmykhalko): check in JmxRegistry that modifiers are applied only once in case of workers and pool registration
for (String attrName : setting.getModifiers().keySet()) {
AttributeModifier> modifier = setting.getModifiers().get(attrName);
try {
rootNode.applyModifier(attrName, modifier, beans);
} catch (ClassCastException e) {
throw new IllegalArgumentException(format("Cannot apply modifier \"%s\" for attribute \"%s\": %s",
modifier.getClass().getName(), attrName, e));
}
}
MBeanInfo mBeanInfo = createMBeanInfo(rootNode, beanClass, setting.getCustomTypes());
Map opkeyToInvokable = fetchOpkeyToInvokable(beanClass, setting.getCustomTypes());
DynamicMBeanAggregator mbean = new DynamicMBeanAggregator(mBeanInfo, adapter, beans, rootNode, opkeyToInvokable);
// TODO(vmykhalko): maybe try to get all attributes and log warn message in case of exception? (to prevent potential errors during viewing jmx stats using jconsole)
// tryGetAllAttributes(mbean);
if (enableRefresh && adapter instanceof JmxBeanAdapterWithRefresh) {
for (Object bean : beans) {
((JmxBeanAdapterWithRefresh) adapter).registerRefreshableBean(bean, rootNode.getAllRefreshables(bean));
}
}
return mbean;
}
JmxBeanAdapter ensureAdapter(Class> beanClass) {
Class extends JmxBeanAdapter> adapterClass = findAdapterClass(beanClass)
.orElseThrow(() -> new NoSuchElementException("Class or its superclass or any of implemented interfaces should be annotated with @JmxBean annotation"));
return adapters.computeIfAbsent(adapterClass, $ -> {
try {
JmxBeanAdapter jmxBeanAdapter = adapterClass.getDeclaredConstructor().newInstance();
if (jmxBeanAdapter instanceof JmxBeanAdapterWithRefresh) {
((JmxBeanAdapterWithRefresh) jmxBeanAdapter).setRefreshParameters(specifiedRefreshPeriod, maxJmxRefreshesPerOneCycle);
}
return jmxBeanAdapter;
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
});
}
// region building tree of AttributeNodes
private List createNodesFor(
Class> clazz, Class> beanClass, String[] includedOptionalAttrs, @Nullable Method getter,
Map> customTypes
) {
Set includedOptionals = new HashSet<>(List.of(includedOptionalAttrs));
List attrDescriptors = fetchAttributeDescriptors(clazz, customTypes);
List attrNodes = new ArrayList<>();
for (AttributeDescriptor descriptor : attrDescriptors) {
checkNotNull(descriptor.getter(), "@JmxAttribute \"%s\" does not have getter", descriptor.name());
String attrName;
Method attrGetter = descriptor.getter();
JmxAttribute attrAnnotation = attrGetter.getAnnotation(JmxAttribute.class);
String attrAnnotationName = attrAnnotation.name();
if (attrAnnotationName.equals(JmxAttribute.USE_GETTER_NAME)) {
attrName = extractFieldNameFromGetter(attrGetter);
} else {
attrName = attrAnnotationName;
}
checkArgument(!attrName.contains("_"), "@JmxAttribute with name \"%s\" contains underscores", attrName);
Type type = attrGetter.getGenericReturnType();
Class> rawType = Types.getRawType(type);
String attrDescription = null;
if (!attrAnnotation.description().equals(JmxAttribute.NO_DESCRIPTION)) {
attrDescription = attrAnnotation.description();
} else if (isEnumType(rawType)) {
attrDescription = "Possible enum values: " + Arrays.toString(rawType.getEnumConstants());
}
boolean included = !attrAnnotation.optional() || includedOptionals.contains(attrName);
includedOptionals.remove(attrName);
Method attrSetter = descriptor.setter();
AttributeNode attrNode = createAttributeNodeFor(attrName, attrDescription, type, included,
attrAnnotation, null, attrGetter, attrSetter, beanClass,
customTypes);
attrNodes.add(attrNode);
}
if (!includedOptionals.isEmpty()) {
assert getter != null; // in this case getter cannot be null
throw new RuntimeException(format(
"Error in \"extraSubAttributes\" parameter in @JmxAnnotation on %s.%s(). There is no field \"%s\" in %s.",
getter.getDeclaringClass().getName(), getter.getName(),
first(includedOptionals), getter.getReturnType().getName()));
}
return attrNodes;
}
private List fetchAttributeDescriptors(Class> clazz, Map> customTypes) {
Map nameToAttr = new HashMap<>();
for (Method method : getAllMethods(clazz)) {
if (method.isAnnotationPresent(JmxAttribute.class)) {
validateJmxMethod(method, JmxAttribute.class);
if (isGetter(method)) {
processGetter(nameToAttr, method);
} else if (isSetter(method)) {
processSetter(nameToAttr, method, customTypes);
} else {
throw new RuntimeException(format(
"Method \"%s\" of class \"%s\" is annotated with @JmxAnnotation but is neither getter nor setter",
method.getName(), method.getClass().getName())
);
}
}
}
return new ArrayList<>(nameToAttr.values());
}
private static void validateJmxMethod(Method method, Class extends Annotation> annotationClass) {
if (!isPublic(method)) {
throw new IllegalStateException(format("A method \"%s\" in class '%s' annotated with @%s should be declared public",
method.getName(), method.getDeclaringClass().getName(), annotationClass.getSimpleName()));
}
if (!isPublic(method.getDeclaringClass())) {
throw new IllegalStateException(format("A class '%s' containing methods annotated with @%s should be declared public",
method.getDeclaringClass().getName(), annotationClass.getSimpleName()));
}
}
private static void processGetter(Map nameToAttr, Method getter) {
String name = extractFieldNameFromGetter(getter);
Type attrType = getter.getReturnType();
if (nameToAttr.containsKey(name)) {
AttributeDescriptor previousDescriptor = nameToAttr.get(name);
checkArgument(previousDescriptor.getter() == null,
"More than one getter with name \"%s\"", getter.getName());
checkArgument(previousDescriptor.type().equals(attrType),
"Getter with name \"%s\" has different type than appropriate setter", getter.getName());
nameToAttr.put(name, new AttributeDescriptor(name, attrType, getter, previousDescriptor.setter()));
} else {
nameToAttr.put(name, new AttributeDescriptor(name, attrType, getter, null));
}
}
private void processSetter(Map nameToAttr, Method setter, Map> customTypes) {
Class> attrType = setter.getParameterTypes()[0];
checkArgument(ReflectionUtils.isSimpleType(attrType) || isStringWrappedType(customTypes, attrType),
"Setters are allowed only on attributes of simple, custom or Enum types. But setter \"%s\" is for neither of the above types",
setter.getName());
String name = extractFieldNameFromSetter(setter);
if (nameToAttr.containsKey(name)) {
AttributeDescriptor previousDescriptor = nameToAttr.get(name);
checkArgument(previousDescriptor.setter() == null,
"More than one setter with name \"%s\"", setter.getName());
checkArgument(previousDescriptor.type().equals(attrType),
"Setter with name \"%s\" has different type than appropriate getter", setter.getName());
nameToAttr.put(name, new AttributeDescriptor(
name, attrType, previousDescriptor.getter(), setter));
} else {
nameToAttr.put(name, new AttributeDescriptor(name, attrType, null, setter));
}
}
private void updateAdaptersRefreshParameters() {
for (JmxBeanAdapter adapter : adapters.values()) {
if (adapter instanceof JmxBeanAdapterWithRefresh) {
((JmxBeanAdapterWithRefresh) adapter).setRefreshParameters(specifiedRefreshPeriod, maxJmxRefreshesPerOneCycle);
}
}
}
@SuppressWarnings("unchecked")
private AttributeNode createAttributeNodeFor(
String attrName, @Nullable String attrDescription, Type attrType, boolean included,
@Nullable JmxAttribute attrAnnotation, @Nullable JmxReducer> reducer, @Nullable Method getter,
@Nullable Method setter, Class> beanClass, Map> customTypes
) {
if (attrType instanceof Class) {
ValueFetcher defaultFetcher = createAppropriateFetcher(getter);
// 5 cases: custom-type, simple-type, JmxRefreshableStats, POJO, enum
Class extends JmxStats> returnClass = (Class extends JmxStats>) attrType;
if (customTypes.containsKey(attrType)) {
reducer = reducer == null ? fetchReducerFrom(getter) : reducer;
JmxCustomTypeAdapter> customTypeAdapter = customTypes.get(attrType);
return AttributeNodeForType.createCustom(attrName, attrDescription, defaultFetcher,
included, setter,
(Function