2 * Copyright (c) 2013 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 static java.lang.String.format;
11 import java.lang.reflect.Array;
12 import java.lang.reflect.Method;
13 import java.util.ArrayList;
14 import java.util.Arrays;
15 import java.util.HashMap;
16 import java.util.HashSet;
17 import java.util.List;
20 import javax.management.Attribute;
21 import javax.management.AttributeList;
22 import javax.management.AttributeNotFoundException;
23 import javax.management.InstanceAlreadyExistsException;
24 import javax.management.InstanceNotFoundException;
25 import javax.management.IntrospectionException;
26 import javax.management.ListenerNotFoundException;
27 import javax.management.MBeanAttributeInfo;
28 import javax.management.MBeanConstructorInfo;
29 import javax.management.MBeanException;
30 import javax.management.MBeanInfo;
31 import javax.management.MBeanNotificationInfo;
32 import javax.management.MBeanOperationInfo;
33 import javax.management.MBeanRegistrationException;
34 import javax.management.MBeanServer;
35 import javax.management.MBeanServerDelegate;
36 import javax.management.MBeanServerNotification;
37 import javax.management.NotCompliantMBeanException;
38 import javax.management.Notification;
39 import javax.management.NotificationListener;
40 import javax.management.ObjectName;
41 import javax.management.ReflectionException;
42 import org.opendaylight.controller.config.api.ModuleIdentifier;
43 import org.opendaylight.controller.config.api.annotations.Description;
44 import org.opendaylight.controller.config.api.annotations.RequireInterface;
45 import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
46 import org.opendaylight.controller.config.manager.impl.util.InterfacesHelper;
47 import org.opendaylight.controller.config.spi.Module;
48 import org.slf4j.Logger;
49 import org.slf4j.LoggerFactory;
52 * Contains common code for readable/rw dynamic mbean wrappers. Routes all
53 * requests (getAttribute, setAttribute, invoke) into the actual instance, but
54 * provides additional functionality - namely it disallows setting attribute on
55 * a read only wrapper.
57 abstract class AbstractDynamicWrapper implements DynamicMBeanModuleWrapper {
58 private static final class ModuleNotificationListener implements NotificationListener {
59 private final ObjectName objectNameInternal;
60 private final MBeanServer internalServer;
61 private final ObjectName thisWrapperObjectName;
62 private final MBeanServer configMBeanServer;
64 private ModuleNotificationListener(final ObjectName objectNameInternal, final MBeanServer internalServer,
65 final ObjectName thisWrapperObjectName, final MBeanServer configMBeanServer) {
66 this.objectNameInternal = objectNameInternal;
67 this.internalServer = internalServer;
68 this.thisWrapperObjectName = thisWrapperObjectName;
69 this.configMBeanServer = configMBeanServer;
73 public void handleNotification(final Notification n, final Object handback) {
74 if (n instanceof MBeanServerNotification
76 .equals(MBeanServerNotification.UNREGISTRATION_NOTIFICATION)) {
77 if (((MBeanServerNotification) n).getMBeanName().equals(
78 thisWrapperObjectName)) {
80 internalServer.unregisterMBean(objectNameInternal);
81 configMBeanServer.removeNotificationListener(
82 MBeanServerDelegate.DELEGATE_NAME, this);
83 } catch (MBeanRegistrationException
84 | ListenerNotFoundException
85 | InstanceNotFoundException e) {
86 throw new IllegalStateException(e);
93 private static final Logger LOG = LoggerFactory.getLogger(AbstractDynamicWrapper.class);
94 protected final boolean writable;
95 protected final Module module;
97 private final MBeanInfo mbeanInfo;
98 protected final ObjectName objectNameInternal;
99 protected final Map<String, AttributeHolder> attributeHolderMap;
100 protected final ModuleIdentifier moduleIdentifier;
101 protected final MBeanServer internalServer;
103 public AbstractDynamicWrapper(final Module module, final boolean writable,
104 final ModuleIdentifier moduleIdentifier,
105 final ObjectName thisWrapperObjectName, final MBeanOperationInfo[] dOperations,
106 final MBeanServer internalServer, final MBeanServer configMBeanServer) {
108 this.writable = writable;
109 this.module = module;
110 this.moduleIdentifier = moduleIdentifier;
111 this.internalServer = internalServer;
112 this.objectNameInternal = thisWrapperObjectName;
113 // register the actual instance into an mbean server.
114 registerActualModule(module, thisWrapperObjectName, objectNameInternal,
115 internalServer, configMBeanServer);
116 Set<Class<?>> jmxInterfaces = InterfacesHelper.getMXInterfaces(module
118 this.attributeHolderMap = buildMBeanInfo(module, writable,
119 moduleIdentifier, jmxInterfaces, internalServer,
121 this.mbeanInfo = generateMBeanInfo(module.getClass().getName(), module,
122 attributeHolderMap, dOperations, jmxInterfaces);
126 * Register module into an internal mbean server, attach listener to the
127 * platform mbean server. Wait until this wrapper gets unregistered, in that
128 * case unregister the module and remove listener.
130 private final NotificationListener registerActualModule(final Module module,
131 final ObjectName thisWrapperObjectName,
132 final ObjectName objectNameInternal,
133 final MBeanServer internalServer,
134 final MBeanServer configMBeanServer) {
137 internalServer.registerMBean(module, objectNameInternal);
138 } catch (InstanceAlreadyExistsException | MBeanRegistrationException
139 | NotCompliantMBeanException | IllegalStateException e) {
140 throw new IllegalStateException(
141 "Error occured during mbean registration with name " + objectNameInternal, e);
144 NotificationListener listener = new ModuleNotificationListener(objectNameInternal, internalServer, thisWrapperObjectName, configMBeanServer);
146 configMBeanServer.addNotificationListener(
147 MBeanServerDelegate.DELEGATE_NAME, listener, null, null);
148 } catch (InstanceNotFoundException e) {
149 throw new RuntimeException("Could not add notification listener", e);
154 private static MBeanInfo generateMBeanInfo(final String className, final Module module,
155 final Map<String, AttributeHolder> attributeHolderMap,
156 final MBeanOperationInfo[] dOperations, final Set<Class<?>> jmxInterfaces) {
158 String dDescription = findDescription(module.getClass(), jmxInterfaces);
159 MBeanConstructorInfo[] dConstructors = new MBeanConstructorInfo[0];
160 List<MBeanAttributeInfo> attributes = new ArrayList<>(
161 attributeHolderMap.size());
162 for (AttributeHolder attributeHolder : attributeHolderMap.values()) {
163 attributes.add(attributeHolder.toMBeanAttributeInfo());
165 return new MBeanInfo(className, dDescription,
166 attributes.toArray(new MBeanAttributeInfo[0]), dConstructors,
167 dOperations, new MBeanNotificationInfo[0]);
170 static String findDescription(final Class<?> clazz, final Set<Class<?>> jmxInterfaces) {
171 List<Description> descriptions = AnnotationsHelper
172 .findClassAnnotationInSuperClassesAndIfcs(clazz, Description.class, jmxInterfaces);
173 return AnnotationsHelper.aggregateDescriptions(descriptions);
176 protected static MBeanOperationInfo[] getEmptyOperations() {
177 return new MBeanOperationInfo[0];
180 // inspect all exported interfaces ending with MXBean, extract getters &
181 // setters into attribute holder
182 private static Map<String, AttributeHolder> buildMBeanInfo(final Module module,
183 final boolean writable, final ModuleIdentifier moduleIdentifier,
184 final Set<Class<?>> jmxInterfaces, final MBeanServer internalServer,
185 final ObjectName internalObjectName) {
187 // internal variables for describing MBean elements
188 Set<Method> methods = new HashSet<>();
190 for (Class<?> exportedClass : jmxInterfaces) {
191 Method[] ifcMethods = exportedClass.getMethods();
192 methods.addAll(Arrays.asList(ifcMethods));
194 // TODO: fix reflection, not used
195 MBeanInfo internalInfo;
197 internalInfo = internalServer.getMBeanInfo(internalObjectName);
198 } catch (InstanceNotFoundException | ReflectionException
199 | IntrospectionException e) {
200 throw new RuntimeException("MBean info not found", e);
203 Map<String, MBeanAttributeInfo> attributeMap = new HashMap<>();
204 for (MBeanAttributeInfo a : internalInfo.getAttributes()) {
205 attributeMap.put(a.getName(), a);
207 Map<String, AttributeHolder> attributeHolderMap = new HashMap<>();
208 for (Method method : methods) {
210 if (method.getParameterTypes().length == 1
211 && method.getName().startsWith("set")) {
213 String attribName = method.getName().substring(3);
215 setter = module.getClass().getMethod(method.getName(),
216 method.getParameterTypes());
217 } catch (NoSuchMethodException e) {
218 throw new RuntimeException("No such method on "
219 + moduleIdentifier, e);
221 RequireInterface ifc = AttributeHolder
222 .findRequireInterfaceAnnotation(setter, jmxInterfaces);
223 String description = null;
225 description = AttributeHolder.findDescription(setter,
228 AttributeHolder attributeHolder = new AttributeHolder(
229 attribName, module, attributeMap.get(attribName)
230 .getType(), writable, ifc, description);
231 attributeHolderMap.put(attribName, attributeHolder);
234 return attributeHolderMap;
237 // DynamicMBean methods
240 public MBeanInfo getMBeanInfo() {
245 public Object getAttribute(final String attributeName)
246 throws AttributeNotFoundException, MBeanException,
247 ReflectionException {
248 if ("MBeanInfo".equals(attributeName)) {
249 return getMBeanInfo();
255 .getAttribute(objectNameInternal, attributeName);
256 } catch (InstanceNotFoundException e) {
257 new MBeanException(e);
260 if (obj instanceof ObjectName) {
261 AttributeHolder attributeHolder = attributeHolderMap
263 if (attributeHolder.getRequireInterfaceOrNull() != null) {
264 obj = fixObjectName((ObjectName) obj);
270 if (isDependencyListAttr(attributeName, obj)) {
271 obj = fixDependencyListAttribute(obj);
277 private Object fixDependencyListAttribute(final Object attribute) {
278 if (attribute.getClass().isArray() == false) {
279 throw new IllegalArgumentException("Unexpected attribute type, should be an array, but was " + attribute.getClass());
282 for (int i = 0; i < Array.getLength(attribute); i++) {
284 Object on = Array.get(attribute, i);
285 if (on instanceof ObjectName == false) {
286 throw new IllegalArgumentException("Unexpected attribute type, should be an ObjectName, but was " + on.getClass());
288 on = fixObjectName((ObjectName) on);
290 Array.set(attribute, i, on);
296 private boolean isDependencyListAttr(final String attributeName, final Object attribute) {
297 if (attributeHolderMap.containsKey(attributeName) == false) {
301 AttributeHolder attributeHolder = attributeHolderMap.get(attributeName);
303 boolean isDepList = true;
304 isDepList &= attributeHolder.getRequireInterfaceOrNull() != null;
305 isDepList &= attribute instanceof ObjectName[];
309 protected ObjectName fixObjectName(final ObjectName on) {
310 if (!ObjectNameUtil.ON_DOMAIN.equals(on.getDomain())) {
311 throw new IllegalArgumentException("Wrong domain, expected "
312 + ObjectNameUtil.ON_DOMAIN + " setter on " + on);
314 // if on contains transaction name, remove it
315 String transactionName = ObjectNameUtil.getTransactionName(on);
316 if (transactionName != null) {
317 return ObjectNameUtil.withoutTransactionName(on);
324 public AttributeList getAttributes(final String[] attributes) {
325 AttributeList result = new AttributeList();
326 for (String attributeName : attributes) {
328 Object value = getAttribute(attributeName);
329 result.add(new Attribute(attributeName, value));
331 } catch (Exception e) {
332 LOG.debug("Getting attribute {} failed", attributeName, e);
339 public Object invoke(final String actionName, final Object[] params, final String[] signature)
340 throws MBeanException, ReflectionException {
341 if ("getAttribute".equals(actionName) && params.length == 1
342 && signature[0].equals(String.class.getName())) {
344 return getAttribute((String) params[0]);
345 } catch (AttributeNotFoundException e) {
346 throw new MBeanException(e, "Attribute not found on "
349 } else if ("getAttributes".equals(actionName) && params.length == 1
350 && signature[0].equals(String[].class.getName())) {
351 return getAttributes((String[]) params[0]);
352 } else if ("setAttributes".equals(actionName) && params.length == 1
353 && signature[0].equals(AttributeList.class.getName())) {
354 return setAttributes((AttributeList) params[0]);
356 LOG.debug("Operation not found {} ", actionName);
357 throw new UnsupportedOperationException(
358 format("Operation not found on %s. Method invoke is only supported for getInstance and getAttribute(s) "
359 + "method, got actionName %s, params %s, signature %s ",
360 moduleIdentifier, actionName, params, signature));
365 public final int hashCode() {
366 return module.hashCode();
370 public final boolean equals(final Object other) {
371 return module.equals(other);