2 * Copyright (c) 2013, 2017 Cisco Systems, Inc. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.controller.config.manager.impl.dynamicmbean;
10 import java.lang.reflect.Array;
11 import java.lang.reflect.Method;
12 import java.util.ArrayList;
13 import java.util.Arrays;
14 import java.util.HashMap;
15 import java.util.HashSet;
16 import java.util.List;
19 import javax.management.Attribute;
20 import javax.management.AttributeList;
21 import javax.management.AttributeNotFoundException;
22 import javax.management.InstanceAlreadyExistsException;
23 import javax.management.InstanceNotFoundException;
24 import javax.management.IntrospectionException;
25 import javax.management.ListenerNotFoundException;
26 import javax.management.MBeanAttributeInfo;
27 import javax.management.MBeanConstructorInfo;
28 import javax.management.MBeanException;
29 import javax.management.MBeanInfo;
30 import javax.management.MBeanNotificationInfo;
31 import javax.management.MBeanOperationInfo;
32 import javax.management.MBeanRegistrationException;
33 import javax.management.MBeanServer;
34 import javax.management.MBeanServerDelegate;
35 import javax.management.MBeanServerNotification;
36 import javax.management.NotCompliantMBeanException;
37 import javax.management.Notification;
38 import javax.management.NotificationListener;
39 import javax.management.ObjectName;
40 import javax.management.ReflectionException;
41 import org.opendaylight.controller.config.api.ModuleIdentifier;
42 import org.opendaylight.controller.config.api.annotations.Description;
43 import org.opendaylight.controller.config.api.annotations.RequireInterface;
44 import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
45 import org.opendaylight.controller.config.manager.impl.util.InterfacesHelper;
46 import org.opendaylight.controller.config.spi.Module;
47 import org.slf4j.Logger;
48 import org.slf4j.LoggerFactory;
51 * Contains common code for readable/rw dynamic mbean wrappers. Routes all
52 * requests (getAttribute, setAttribute, invoke) into the actual instance, but
53 * provides additional functionality - namely it disallows setting attribute on
54 * a read only wrapper.
56 public abstract class AbstractDynamicWrapper implements DynamicMBeanModuleWrapper {
58 protected final Module module;
59 private final MBeanInfo mbeanInfo;
60 protected final ObjectName objectNameInternal;
61 protected final Map<String, AttributeHolder> attributeHolderMap;
62 protected final ModuleIdentifier moduleIdentifier;
63 protected final MBeanServer internalServer;
65 private static final Logger LOG = LoggerFactory.getLogger(AbstractDynamicWrapper.class);
67 public AbstractDynamicWrapper(final Module module, final boolean writable, final ModuleIdentifier moduleIdentifier,
68 final ObjectName thisWrapperObjectName, final MBeanOperationInfo[] operations,
69 final MBeanServer internalServer, final MBeanServer configMBeanServer) {
71 this.moduleIdentifier = moduleIdentifier;
72 this.internalServer = internalServer;
73 this.objectNameInternal = thisWrapperObjectName;
74 // register the actual instance into an mbean server.
75 registerActualModule(objectNameInternal, configMBeanServer);
76 Set<Class<?>> jmxInterfaces = InterfacesHelper.getMXInterfaces(module.getClass());
77 this.attributeHolderMap = buildMBeanInfo(writable, moduleIdentifier, jmxInterfaces, objectNameInternal);
78 this.mbeanInfo = generateMBeanInfo(module, attributeHolderMap, operations, jmxInterfaces);
81 private static final class ModuleNotificationListener implements NotificationListener {
82 private final ObjectName objectNameInternal;
83 private final MBeanServer internalServer;
84 private final MBeanServer configMBeanServer;
86 private ModuleNotificationListener(final ObjectName objectNameInternal, final MBeanServer internalServer,
87 final MBeanServer configMBeanServer) {
88 this.objectNameInternal = objectNameInternal;
89 this.internalServer = internalServer;
90 this.configMBeanServer = configMBeanServer;
94 public void handleNotification(final Notification notification, final Object handback) {
95 if (notification instanceof MBeanServerNotification
96 && notification.getType().equals(MBeanServerNotification.UNREGISTRATION_NOTIFICATION)
97 && ((MBeanServerNotification) notification).getMBeanName().equals(objectNameInternal)) {
99 internalServer.unregisterMBean(objectNameInternal);
100 configMBeanServer.removeNotificationListener(MBeanServerDelegate.DELEGATE_NAME, this);
101 } catch (MBeanRegistrationException | ListenerNotFoundException | InstanceNotFoundException e) {
102 throw new IllegalStateException(e);
109 * Register module into an internal mbean server, attach listener to the
110 * platform mbean server. Wait until this wrapper gets unregistered, in that
111 * case unregister the module and remove listener.
113 private NotificationListener registerActualModule(final ObjectName internalObjectName,
114 final MBeanServer configMBeanServer) {
116 internalServer.registerMBean(module, internalObjectName);
117 } catch (InstanceAlreadyExistsException | MBeanRegistrationException | NotCompliantMBeanException
118 | IllegalStateException e) {
119 throw new IllegalStateException("Error occured during mbean registration with name " + internalObjectName,
123 NotificationListener listener = new ModuleNotificationListener(internalObjectName, internalServer,
126 configMBeanServer.addNotificationListener(MBeanServerDelegate.DELEGATE_NAME, listener, null, null);
127 } catch (final InstanceNotFoundException e) {
128 throw new RuntimeException("Could not add notification listener", e);
133 private static MBeanInfo generateMBeanInfo(final Module module,
134 final Map<String, AttributeHolder> attributeHolderMap, final MBeanOperationInfo[] operations,
135 final Set<Class<?>> jmxInterfaces) {
137 String description = findDescription(module.getClass(), jmxInterfaces);
138 MBeanConstructorInfo[] constructors = new MBeanConstructorInfo[0];
139 List<MBeanAttributeInfo> attributes = new ArrayList<>(attributeHolderMap.size());
140 for (AttributeHolder attributeHolder : attributeHolderMap.values()) {
141 attributes.add(attributeHolder.toMBeanAttributeInfo());
143 return new MBeanInfo(module.getClass().getName(), description, attributes.toArray(new MBeanAttributeInfo[0]),
144 constructors, operations, new MBeanNotificationInfo[0]);
147 static String findDescription(final Class<?> clazz, final Set<Class<?>> jmxInterfaces) {
148 List<Description> descriptions = AnnotationsHelper.findClassAnnotationInSuperClassesAndIfcs(clazz,
149 Description.class, jmxInterfaces);
150 return AnnotationsHelper.aggregateDescriptions(descriptions);
153 protected static MBeanOperationInfo[] getEmptyOperations() {
154 return new MBeanOperationInfo[0];
157 // inspect all exported interfaces ending with MXBean, extract getters &
158 // setters into attribute holder
159 private Map<String, AttributeHolder> buildMBeanInfo(final boolean writable, final ModuleIdentifier modId,
160 final Set<Class<?>> jmxInterfaces, final ObjectName internalObjectName) {
162 // internal variables for describing MBean elements
163 Set<Method> methods = new HashSet<>();
165 for (Class<?> exportedClass : jmxInterfaces) {
166 Method[] ifcMethods = exportedClass.getMethods();
167 methods.addAll(Arrays.asList(ifcMethods));
169 // TODO: fix reflection, not used
170 MBeanInfo internalInfo;
172 internalInfo = internalServer.getMBeanInfo(internalObjectName);
173 } catch (InstanceNotFoundException | ReflectionException | IntrospectionException e) {
174 throw new RuntimeException("MBean info not found", e);
177 Map<String, MBeanAttributeInfo> attributeMap = new HashMap<>();
178 for (MBeanAttributeInfo a : internalInfo.getAttributes()) {
179 attributeMap.put(a.getName(), a);
181 Map<String, AttributeHolder> attributeHolderMapLocal = new HashMap<>();
182 for (Method method : methods) {
184 if (method.getParameterTypes().length == 1 && method.getName().startsWith("set")) {
186 String attribName = method.getName().substring(3);
188 setter = module.getClass().getMethod(method.getName(), method.getParameterTypes());
189 } catch (final NoSuchMethodException e) {
190 throw new RuntimeException("No such method on " + modId, e);
192 RequireInterface ifc = AttributeHolder.findRequireInterfaceAnnotation(setter, jmxInterfaces);
193 String description = null;
195 description = AttributeHolder.findDescription(setter, jmxInterfaces);
197 AttributeHolder attributeHolder = new AttributeHolder(attribName, module,
198 attributeMap.get(attribName).getType(), writable, ifc, description);
199 attributeHolderMapLocal.put(attribName, attributeHolder);
202 return attributeHolderMapLocal;
205 // DynamicMBean methods
208 public MBeanInfo getMBeanInfo() {
213 public Object getAttribute(final String attributeName)
214 throws AttributeNotFoundException, MBeanException, ReflectionException {
215 if ("MBeanInfo".equals(attributeName)) {
216 return getMBeanInfo();
221 obj = internalServer.getAttribute(objectNameInternal, attributeName);
222 } catch (final InstanceNotFoundException e) {
223 throw new MBeanException(e);
226 if (obj instanceof ObjectName) {
227 AttributeHolder attributeHolder = attributeHolderMap.get(attributeName);
228 if (attributeHolder.getRequireInterfaceOrNull() != null) {
229 obj = fixObjectName((ObjectName) obj);
234 if (isDependencyListAttr(attributeName, obj)) {
235 obj = fixDependencyListAttribute(obj);
241 private Object fixDependencyListAttribute(final Object attribute) {
242 if (!attribute.getClass().isArray()) {
243 throw new IllegalArgumentException(
244 "Unexpected attribute type, should be an array, but was " + attribute.getClass());
247 for (int i = 0; i < Array.getLength(attribute); i++) {
249 Object on = Array.get(attribute, i);
250 if (!(on instanceof ObjectName)) {
251 throw new IllegalArgumentException(
252 "Unexpected attribute type, should be an ObjectName, but was " + on.getClass());
254 on = fixObjectName((ObjectName) on);
256 Array.set(attribute, i, on);
262 private boolean isDependencyListAttr(final String attributeName, final Object attribute) {
263 if (!attributeHolderMap.containsKey(attributeName)) {
267 AttributeHolder attributeHolder = attributeHolderMap.get(attributeName);
269 boolean isDepList = true;
270 isDepList &= attributeHolder.getRequireInterfaceOrNull() != null;
271 isDepList &= attribute instanceof ObjectName[];
275 protected ObjectName fixObjectName(final ObjectName on) {
276 if (!ObjectNameUtil.ON_DOMAIN.equals(on.getDomain())) {
277 throw new IllegalArgumentException(
278 "Wrong domain, expected " + ObjectNameUtil.ON_DOMAIN + " setter on " + on);
280 // if on contains transaction name, remove it
281 String transactionName = ObjectNameUtil.getTransactionName(on);
282 if (transactionName != null) {
283 return ObjectNameUtil.withoutTransactionName(on);
290 public AttributeList getAttributes(final String[] attributes) {
291 AttributeList result = new AttributeList();
292 for (String attributeName : attributes) {
295 value = getAttribute(attributeName);
296 result.add(new Attribute(attributeName, value));
297 } catch (AttributeNotFoundException | MBeanException | ReflectionException e) {
298 LOG.debug("Getting attribute {} failed", attributeName, e);
305 public Object invoke(final String actionName, final Object[] params, final String[] signature)
306 throws MBeanException, ReflectionException {
307 if ("getAttribute".equals(actionName) && params.length == 1 && signature[0].equals(String.class.getName())) {
309 return getAttribute((String) params[0]);
310 } catch (final AttributeNotFoundException e) {
311 throw new MBeanException(e, "Attribute not found on " + moduleIdentifier);
313 } else if ("getAttributes".equals(actionName) && params.length == 1
314 && signature[0].equals(String[].class.getName())) {
315 return getAttributes((String[]) params[0]);
316 } else if ("setAttributes".equals(actionName) && params.length == 1
317 && signature[0].equals(AttributeList.class.getName())) {
318 return setAttributes((AttributeList) params[0]);
320 LOG.debug("Operation not found {} ", actionName);
321 throw new UnsupportedOperationException(String.format(
322 "Operation not found on %s. Method invoke is only supported for getInstance and getAttribute(s) "
323 + "method, got actionName %s, params %s, signature %s ",
324 moduleIdentifier, actionName, params, signature));
329 public final int hashCode() {
330 return module.hashCode();
334 public final boolean equals(final Object other) {
335 return module.equals(other);