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;
12 import java.lang.reflect.Array;
13 import java.lang.reflect.Method;
14 import java.util.ArrayList;
15 import java.util.Arrays;
16 import java.util.HashMap;
17 import java.util.HashSet;
18 import java.util.List;
21 import javax.management.Attribute;
22 import javax.management.AttributeList;
23 import javax.management.AttributeNotFoundException;
24 import javax.management.InstanceAlreadyExistsException;
25 import javax.management.InstanceNotFoundException;
26 import javax.management.IntrospectionException;
27 import javax.management.ListenerNotFoundException;
28 import javax.management.MBeanAttributeInfo;
29 import javax.management.MBeanConstructorInfo;
30 import javax.management.MBeanException;
31 import javax.management.MBeanInfo;
32 import javax.management.MBeanNotificationInfo;
33 import javax.management.MBeanOperationInfo;
34 import javax.management.MBeanRegistrationException;
35 import javax.management.MBeanServer;
36 import javax.management.MBeanServerDelegate;
37 import javax.management.MBeanServerNotification;
38 import javax.management.NotCompliantMBeanException;
39 import javax.management.Notification;
40 import javax.management.NotificationListener;
41 import javax.management.ObjectName;
42 import javax.management.ReflectionException;
43 import org.opendaylight.controller.config.api.ModuleIdentifier;
44 import org.opendaylight.controller.config.api.annotations.Description;
45 import org.opendaylight.controller.config.api.annotations.RequireInterface;
46 import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
47 import org.opendaylight.controller.config.manager.impl.util.InterfacesHelper;
48 import org.opendaylight.controller.config.spi.Module;
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
53 * Contains common code for readable/rw dynamic mbean wrappers. Routes all
54 * requests (getAttribute, setAttribute, invoke) into the actual instance, but
55 * provides additional functionality - namely it disallows setting attribute on
56 * a read only wrapper.
58 abstract class AbstractDynamicWrapper implements DynamicMBeanModuleWrapper {
59 private static final class ModuleNotificationListener implements NotificationListener {
60 private final ObjectName objectNameInternal;
61 private final MBeanServer internalServer;
62 private final ObjectName thisWrapperObjectName;
63 private final MBeanServer configMBeanServer;
65 private ModuleNotificationListener(final ObjectName objectNameInternal, final MBeanServer internalServer,
66 final ObjectName thisWrapperObjectName, final MBeanServer configMBeanServer) {
67 this.objectNameInternal = objectNameInternal;
68 this.internalServer = internalServer;
69 this.thisWrapperObjectName = thisWrapperObjectName;
70 this.configMBeanServer = configMBeanServer;
74 public void handleNotification(final Notification n, final Object handback) {
75 if (n instanceof MBeanServerNotification
77 .equals(MBeanServerNotification.UNREGISTRATION_NOTIFICATION)) {
78 if (((MBeanServerNotification) n).getMBeanName().equals(
79 thisWrapperObjectName)) {
81 internalServer.unregisterMBean(objectNameInternal);
82 configMBeanServer.removeNotificationListener(
83 MBeanServerDelegate.DELEGATE_NAME, this);
84 } catch (MBeanRegistrationException
85 | ListenerNotFoundException
86 | InstanceNotFoundException e) {
87 throw new IllegalStateException(e);
94 private static final Logger LOG = LoggerFactory.getLogger(AbstractDynamicWrapper.class);
95 protected final boolean writable;
96 protected final Module module;
98 private final MBeanInfo mbeanInfo;
99 protected final ObjectName objectNameInternal;
100 protected final Map<String, AttributeHolder> attributeHolderMap;
101 protected final ModuleIdentifier moduleIdentifier;
102 protected final MBeanServer internalServer;
104 public AbstractDynamicWrapper(final Module module, final boolean writable,
105 final ModuleIdentifier moduleIdentifier,
106 final ObjectName thisWrapperObjectName, final MBeanOperationInfo[] dOperations,
107 final MBeanServer internalServer, final MBeanServer configMBeanServer) {
109 this.writable = writable;
110 this.module = module;
111 this.moduleIdentifier = moduleIdentifier;
112 this.internalServer = internalServer;
113 this.objectNameInternal = thisWrapperObjectName;
114 // register the actual instance into an mbean server.
115 registerActualModule(module, thisWrapperObjectName, objectNameInternal,
116 internalServer, configMBeanServer);
117 Set<Class<?>> jmxInterfaces = InterfacesHelper.getMXInterfaces(module
119 this.attributeHolderMap = buildMBeanInfo(module, writable,
120 moduleIdentifier, jmxInterfaces, internalServer,
122 this.mbeanInfo = generateMBeanInfo(module.getClass().getName(), module,
123 attributeHolderMap, dOperations, jmxInterfaces);
127 * Register module into an internal mbean server, attach listener to the
128 * platform mbean server. Wait until this wrapper gets unregistered, in that
129 * case unregister the module and remove listener.
131 private final NotificationListener registerActualModule(final Module module,
132 final ObjectName thisWrapperObjectName,
133 final ObjectName objectNameInternal,
134 final MBeanServer internalServer,
135 final MBeanServer configMBeanServer) {
138 internalServer.registerMBean(module, objectNameInternal);
139 } catch (InstanceAlreadyExistsException | MBeanRegistrationException
140 | NotCompliantMBeanException | IllegalStateException e) {
141 throw new IllegalStateException(
142 "Error occured during mbean registration with name " + objectNameInternal, e);
145 NotificationListener listener = new ModuleNotificationListener(objectNameInternal, internalServer, thisWrapperObjectName, configMBeanServer);
147 configMBeanServer.addNotificationListener(
148 MBeanServerDelegate.DELEGATE_NAME, listener, null, null);
149 } catch (InstanceNotFoundException e) {
150 throw new RuntimeException("Could not add notification listener", e);
155 private static MBeanInfo generateMBeanInfo(final String className, final Module module,
156 final Map<String, AttributeHolder> attributeHolderMap,
157 final MBeanOperationInfo[] dOperations, final Set<Class<?>> jmxInterfaces) {
159 String dDescription = findDescription(module.getClass(), jmxInterfaces);
160 MBeanConstructorInfo[] dConstructors = new MBeanConstructorInfo[0];
161 List<MBeanAttributeInfo> attributes = new ArrayList<>(
162 attributeHolderMap.size());
163 for (AttributeHolder attributeHolder : attributeHolderMap.values()) {
164 attributes.add(attributeHolder.toMBeanAttributeInfo());
166 return new MBeanInfo(className, dDescription,
167 attributes.toArray(new MBeanAttributeInfo[0]), dConstructors,
168 dOperations, new MBeanNotificationInfo[0]);
171 static String findDescription(final Class<?> clazz, final Set<Class<?>> jmxInterfaces) {
172 List<Description> descriptions = AnnotationsHelper
173 .findClassAnnotationInSuperClassesAndIfcs(clazz, Description.class, jmxInterfaces);
174 return AnnotationsHelper.aggregateDescriptions(descriptions);
177 protected static MBeanOperationInfo[] getEmptyOperations() {
178 return new MBeanOperationInfo[0];
181 // inspect all exported interfaces ending with MXBean, extract getters &
182 // setters into attribute holder
183 private static Map<String, AttributeHolder> buildMBeanInfo(final Module module,
184 final boolean writable, final ModuleIdentifier moduleIdentifier,
185 final Set<Class<?>> jmxInterfaces, final MBeanServer internalServer,
186 final ObjectName internalObjectName) {
188 // internal variables for describing MBean elements
189 Set<Method> methods = new HashSet<>();
191 for (Class<?> exportedClass : jmxInterfaces) {
192 Method[] ifcMethods = exportedClass.getMethods();
193 methods.addAll(Arrays.asList(ifcMethods));
195 // TODO: fix reflection, not used
196 MBeanInfo internalInfo;
198 internalInfo = internalServer.getMBeanInfo(internalObjectName);
199 } catch (InstanceNotFoundException | ReflectionException
200 | IntrospectionException e) {
201 throw new RuntimeException("MBean info not found", e);
204 Map<String, MBeanAttributeInfo> attributeMap = new HashMap<>();
205 for (MBeanAttributeInfo a : internalInfo.getAttributes()) {
206 attributeMap.put(a.getName(), a);
208 Map<String, AttributeHolder> attributeHolderMap = new HashMap<>();
209 for (Method method : methods) {
211 if (method.getParameterTypes().length == 1
212 && method.getName().startsWith("set")) {
214 String attribName = method.getName().substring(3);
216 setter = module.getClass().getMethod(method.getName(),
217 method.getParameterTypes());
218 } catch (NoSuchMethodException e) {
219 throw new RuntimeException("No such method on "
220 + moduleIdentifier, e);
222 RequireInterface ifc = AttributeHolder
223 .findRequireInterfaceAnnotation(setter, jmxInterfaces);
224 String description = null;
226 description = AttributeHolder.findDescription(setter,
229 AttributeHolder attributeHolder = new AttributeHolder(
230 attribName, module, attributeMap.get(attribName)
231 .getType(), writable, ifc, description);
232 attributeHolderMap.put(attribName, attributeHolder);
235 return attributeHolderMap;
238 // DynamicMBean methods
241 public MBeanInfo getMBeanInfo() {
246 public Object getAttribute(final String attributeName)
247 throws AttributeNotFoundException, MBeanException,
248 ReflectionException {
249 if ("MBeanInfo".equals(attributeName)) {
250 return getMBeanInfo();
256 .getAttribute(objectNameInternal, attributeName);
257 } catch (InstanceNotFoundException e) {
258 new MBeanException(e);
261 if (obj instanceof ObjectName) {
262 AttributeHolder attributeHolder = attributeHolderMap
264 if (attributeHolder.getRequireInterfaceOrNull() != null) {
265 obj = fixObjectName((ObjectName) obj);
271 if (isDependencyListAttr(attributeName, obj)) {
272 obj = fixDependencyListAttribute(obj);
278 private Object fixDependencyListAttribute(final Object attribute) {
279 if (attribute.getClass().isArray() == false) {
280 throw new IllegalArgumentException("Unexpected attribute type, should be an array, but was " + attribute.getClass());
283 for (int i = 0; i < Array.getLength(attribute); i++) {
285 Object on = Array.get(attribute, i);
286 if (on instanceof ObjectName == false) {
287 throw new IllegalArgumentException("Unexpected attribute type, should be an ObjectName, but was " + on.getClass());
289 on = fixObjectName((ObjectName) on);
291 Array.set(attribute, i, on);
297 private boolean isDependencyListAttr(final String attributeName, final Object attribute) {
298 if (attributeHolderMap.containsKey(attributeName) == false) {
302 AttributeHolder attributeHolder = attributeHolderMap.get(attributeName);
304 boolean isDepList = true;
305 isDepList &= attributeHolder.getRequireInterfaceOrNull() != null;
306 isDepList &= attribute instanceof ObjectName[];
310 protected ObjectName fixObjectName(final ObjectName on) {
311 if (!ObjectNameUtil.ON_DOMAIN.equals(on.getDomain())) {
312 throw new IllegalArgumentException("Wrong domain, expected "
313 + ObjectNameUtil.ON_DOMAIN + " setter on " + on);
315 // if on contains transaction name, remove it
316 String transactionName = ObjectNameUtil.getTransactionName(on);
317 if (transactionName != null) {
318 return ObjectNameUtil.withoutTransactionName(on);
325 public AttributeList getAttributes(final String[] attributes) {
326 AttributeList result = new AttributeList();
327 for (String attributeName : attributes) {
329 Object value = getAttribute(attributeName);
330 result.add(new Attribute(attributeName, value));
332 } catch (Exception e) {
333 LOG.debug("Getting attribute {} failed", attributeName, e);
340 public Object invoke(final String actionName, final Object[] params, final String[] signature)
341 throws MBeanException, ReflectionException {
342 if ("getAttribute".equals(actionName) && params.length == 1
343 && signature[0].equals(String.class.getName())) {
345 return getAttribute((String) params[0]);
346 } catch (AttributeNotFoundException e) {
347 throw new MBeanException(e, "Attribute not found on "
350 } else if ("getAttributes".equals(actionName) && params.length == 1
351 && signature[0].equals(String[].class.getName())) {
352 return getAttributes((String[]) params[0]);
353 } else if ("setAttributes".equals(actionName) && params.length == 1
354 && signature[0].equals(AttributeList.class.getName())) {
355 return setAttributes((AttributeList) params[0]);
357 LOG.debug("Operation not found {} ", actionName);
358 throw new UnsupportedOperationException(
359 format("Operation not found on %s. Method invoke is only supported for getInstance and getAttribute(s) "
360 + "method, got actionName %s, params %s, signature %s ",
361 moduleIdentifier, actionName, params, signature));
366 public final int hashCode() {
367 return module.hashCode();
371 public final boolean equals(final Object other) {
372 return module.equals(other);