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 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 abstract class AbstractDynamicWrapper implements DynamicMBeanModuleWrapper {
57 private static final class ModuleNotificationListener implements NotificationListener {
58 private final ObjectName objectNameInternal;
59 private final MBeanServer internalServer;
60 private final MBeanServer configMBeanServer;
62 private ModuleNotificationListener(final ObjectName objectNameInternal, final MBeanServer internalServer,
63 final MBeanServer configMBeanServer) {
64 this.objectNameInternal = objectNameInternal;
65 this.internalServer = internalServer;
66 this.configMBeanServer = configMBeanServer;
70 public void handleNotification(final Notification n, final Object handback) {
71 if (n instanceof MBeanServerNotification
73 .equals(MBeanServerNotification.UNREGISTRATION_NOTIFICATION)
74 && ((MBeanServerNotification) n).getMBeanName().equals(
75 objectNameInternal)) {
77 internalServer.unregisterMBean(objectNameInternal);
78 configMBeanServer.removeNotificationListener(
79 MBeanServerDelegate.DELEGATE_NAME, this);
80 } catch (MBeanRegistrationException
81 | ListenerNotFoundException
82 | InstanceNotFoundException e) {
83 throw new IllegalStateException(e);
89 private static final Logger LOG = LoggerFactory.getLogger(AbstractDynamicWrapper.class);
90 protected final Module module;
92 private final MBeanInfo mbeanInfo;
93 protected final ObjectName objectNameInternal;
94 protected final Map<String, AttributeHolder> attributeHolderMap;
95 protected final ModuleIdentifier moduleIdentifier;
96 protected final MBeanServer internalServer;
98 public AbstractDynamicWrapper(final Module module, final boolean writable,
99 final ModuleIdentifier moduleIdentifier,
100 final ObjectName thisWrapperObjectName, final MBeanOperationInfo[] dOperations,
101 final MBeanServer internalServer, final MBeanServer configMBeanServer) {
103 this.module = module;
104 this.moduleIdentifier = moduleIdentifier;
105 this.internalServer = internalServer;
106 this.objectNameInternal = thisWrapperObjectName;
107 // register the actual instance into an mbean server.
108 registerActualModule(objectNameInternal, configMBeanServer);
109 Set<Class<?>> jmxInterfaces = InterfacesHelper.getMXInterfaces(module
111 this.attributeHolderMap = buildMBeanInfo(writable,
112 moduleIdentifier, jmxInterfaces, objectNameInternal);
113 this.mbeanInfo = generateMBeanInfo(module,
114 attributeHolderMap, dOperations, jmxInterfaces);
118 * Register module into an internal mbean server, attach listener to the
119 * platform mbean server. Wait until this wrapper gets unregistered, in that
120 * case unregister the module and remove listener.
122 private final NotificationListener registerActualModule(final ObjectName objectNameInternal,
123 final MBeanServer configMBeanServer) {
125 internalServer.registerMBean(module, objectNameInternal);
126 } catch (InstanceAlreadyExistsException | MBeanRegistrationException
127 | NotCompliantMBeanException | IllegalStateException e) {
128 throw new IllegalStateException(
129 "Error occured during mbean registration with name " + objectNameInternal, e);
132 NotificationListener listener = new ModuleNotificationListener(objectNameInternal, internalServer, configMBeanServer);
134 configMBeanServer.addNotificationListener(
135 MBeanServerDelegate.DELEGATE_NAME, listener, null, null);
136 } catch (final InstanceNotFoundException e) {
137 throw new RuntimeException("Could not add notification listener", e);
142 private static MBeanInfo generateMBeanInfo(final Module module,
143 final Map<String, AttributeHolder> attributeHolderMap,
144 final MBeanOperationInfo[] dOperations, final Set<Class<?>> jmxInterfaces) {
146 String dDescription = findDescription(module.getClass(), jmxInterfaces);
147 MBeanConstructorInfo[] dConstructors = new MBeanConstructorInfo[0];
148 List<MBeanAttributeInfo> attributes = new ArrayList<>(
149 attributeHolderMap.size());
150 for (AttributeHolder attributeHolder : attributeHolderMap.values()) {
151 attributes.add(attributeHolder.toMBeanAttributeInfo());
153 return new MBeanInfo(module.getClass().getName(), dDescription,
154 attributes.toArray(new MBeanAttributeInfo[0]), dConstructors,
155 dOperations, new MBeanNotificationInfo[0]);
158 static String findDescription(final Class<?> clazz, final Set<Class<?>> jmxInterfaces) {
159 List<Description> descriptions = AnnotationsHelper
160 .findClassAnnotationInSuperClassesAndIfcs(clazz, Description.class, jmxInterfaces);
161 return AnnotationsHelper.aggregateDescriptions(descriptions);
164 protected static MBeanOperationInfo[] getEmptyOperations() {
165 return new MBeanOperationInfo[0];
168 // inspect all exported interfaces ending with MXBean, extract getters &
169 // setters into attribute holder
170 private Map<String, AttributeHolder> buildMBeanInfo(final boolean writable, final ModuleIdentifier moduleIdentifier,
171 final Set<Class<?>> jmxInterfaces,
172 final ObjectName internalObjectName) {
174 // internal variables for describing MBean elements
175 Set<Method> methods = new HashSet<>();
177 for (Class<?> exportedClass : jmxInterfaces) {
178 Method[] ifcMethods = exportedClass.getMethods();
179 methods.addAll(Arrays.asList(ifcMethods));
181 // TODO: fix reflection, not used
182 MBeanInfo internalInfo;
184 internalInfo = internalServer.getMBeanInfo(internalObjectName);
185 } catch (InstanceNotFoundException | ReflectionException
186 | IntrospectionException e) {
187 throw new RuntimeException("MBean info not found", e);
190 Map<String, MBeanAttributeInfo> attributeMap = new HashMap<>();
191 for (MBeanAttributeInfo a : internalInfo.getAttributes()) {
192 attributeMap.put(a.getName(), a);
194 Map<String, AttributeHolder> attributeHolderMapLocal = new HashMap<>();
195 for (Method method : methods) {
197 if (method.getParameterTypes().length == 1
198 && method.getName().startsWith("set")) {
200 String attribName = method.getName().substring(3);
202 setter = module.getClass().getMethod(method.getName(),
203 method.getParameterTypes());
204 } catch (final NoSuchMethodException e) {
205 throw new RuntimeException("No such method on "
206 + moduleIdentifier, e);
208 RequireInterface ifc = AttributeHolder
209 .findRequireInterfaceAnnotation(setter, jmxInterfaces);
210 String description = null;
212 description = AttributeHolder.findDescription(setter,
215 AttributeHolder attributeHolder = new AttributeHolder(
216 attribName, module, attributeMap.get(attribName)
217 .getType(), writable, ifc, description);
218 attributeHolderMapLocal.put(attribName, attributeHolder);
221 return attributeHolderMapLocal;
224 // DynamicMBean methods
227 public MBeanInfo getMBeanInfo() {
232 public Object getAttribute(final String attributeName)
233 throws AttributeNotFoundException, MBeanException,
234 ReflectionException {
235 if ("MBeanInfo".equals(attributeName)) {
236 return getMBeanInfo();
242 .getAttribute(objectNameInternal, attributeName);
243 } catch (final InstanceNotFoundException e) {
244 new MBeanException(e);
247 if (obj instanceof ObjectName) {
248 AttributeHolder attributeHolder = attributeHolderMap
250 if (attributeHolder.getRequireInterfaceOrNull() != null) {
251 obj = fixObjectName((ObjectName) obj);
257 if (isDependencyListAttr(attributeName, obj)) {
258 obj = fixDependencyListAttribute(obj);
264 private Object fixDependencyListAttribute(final Object attribute) {
265 if (!attribute.getClass().isArray()) {
266 throw new IllegalArgumentException("Unexpected attribute type, should be an array, but was " + attribute.getClass());
269 for (int i = 0; i < Array.getLength(attribute); i++) {
271 Object on = Array.get(attribute, i);
272 if (!(on instanceof ObjectName)) {
273 throw new IllegalArgumentException("Unexpected attribute type, should be an ObjectName, but was " + on.getClass());
275 on = fixObjectName((ObjectName) on);
277 Array.set(attribute, i, on);
283 private boolean isDependencyListAttr(final String attributeName, final Object attribute) {
284 if (!attributeHolderMap.containsKey(attributeName)) {
288 AttributeHolder attributeHolder = attributeHolderMap.get(attributeName);
290 boolean isDepList = true;
291 isDepList &= attributeHolder.getRequireInterfaceOrNull() != null;
292 isDepList &= attribute instanceof ObjectName[];
296 protected ObjectName fixObjectName(final ObjectName on) {
297 if (!ObjectNameUtil.ON_DOMAIN.equals(on.getDomain())) {
298 throw new IllegalArgumentException("Wrong domain, expected "
299 + ObjectNameUtil.ON_DOMAIN + " setter on " + on);
301 // if on contains transaction name, remove it
302 String transactionName = ObjectNameUtil.getTransactionName(on);
303 if (transactionName != null) {
304 return ObjectNameUtil.withoutTransactionName(on);
311 public AttributeList getAttributes(final String[] attributes) {
312 AttributeList result = new AttributeList();
313 for (String attributeName : attributes) {
315 Object value = getAttribute(attributeName);
316 result.add(new Attribute(attributeName, value));
318 } catch (final Exception e) {
319 LOG.debug("Getting attribute {} failed", attributeName, e);
326 public Object invoke(final String actionName, final Object[] params, final String[] signature)
327 throws MBeanException, ReflectionException {
328 if ("getAttribute".equals(actionName) && params.length == 1
329 && signature[0].equals(String.class.getName())) {
331 return getAttribute((String) params[0]);
332 } catch (final AttributeNotFoundException e) {
333 throw new MBeanException(e, "Attribute not found on "
336 } else if ("getAttributes".equals(actionName) && params.length == 1
337 && signature[0].equals(String[].class.getName())) {
338 return getAttributes((String[]) params[0]);
339 } else if ("setAttributes".equals(actionName) && params.length == 1
340 && signature[0].equals(AttributeList.class.getName())) {
341 return setAttributes((AttributeList) params[0]);
343 LOG.debug("Operation not found {} ", actionName);
344 throw new UnsupportedOperationException(
345 String.format("Operation not found on %s. Method invoke is only supported for getInstance and getAttribute(s) "
346 + "method, got actionName %s, params %s, signature %s ",
347 moduleIdentifier, actionName, params, signature));
352 public final int hashCode() {
353 return module.hashCode();
357 public final boolean equals(final Object other) {
358 return module.equals(other);