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 org.opendaylight.controller.config.api.ModuleIdentifier;
11 import org.opendaylight.controller.config.api.annotations.Description;
12 import org.opendaylight.controller.config.api.annotations.RequireInterface;
13 import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
14 import org.opendaylight.controller.config.manager.impl.util.InterfacesHelper;
15 import org.opendaylight.controller.config.spi.Module;
16 import org.slf4j.Logger;
17 import org.slf4j.LoggerFactory;
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 java.lang.reflect.Array;
42 import java.lang.reflect.Method;
43 import java.util.ArrayList;
44 import java.util.Arrays;
45 import java.util.HashMap;
46 import java.util.HashSet;
47 import java.util.List;
51 import static java.lang.String.format;
54 * Contains common code for readable/rw dynamic mbean wrappers. Routes all
55 * requests (getAttribute, setAttribute, invoke) into the actual instance, but
56 * provides additional functionality - namely it disallows setting attribute on
57 * a read only wrapper.
59 abstract class AbstractDynamicWrapper implements DynamicMBeanModuleWrapper {
60 private static final Logger LOGGER = LoggerFactory
61 .getLogger(AbstractDynamicWrapper.class);
63 protected final boolean writable;
64 protected final Module module;
66 private final MBeanInfo mbeanInfo;
67 protected final ObjectName objectNameInternal;
68 protected final Map<String, AttributeHolder> attributeHolderMap;
69 protected final ModuleIdentifier moduleIdentifier;
70 protected final MBeanServer internalServer;
72 public AbstractDynamicWrapper(Module module, boolean writable,
73 ModuleIdentifier moduleIdentifier,
74 ObjectName thisWrapperObjectName, MBeanOperationInfo[] dOperations,
75 MBeanServer internalServer, MBeanServer configMBeanServer) {
77 this.writable = writable;
79 this.moduleIdentifier = moduleIdentifier;
80 this.internalServer = internalServer;
81 this.objectNameInternal = thisWrapperObjectName;
82 // register the actual instance into an mbean server.
83 registerActualModule(module, thisWrapperObjectName, objectNameInternal,
84 internalServer, configMBeanServer);
85 Set<Class<?>> jmxInterfaces = InterfacesHelper.getMXInterfaces(module
87 this.attributeHolderMap = buildMBeanInfo(module, writable,
88 moduleIdentifier, jmxInterfaces, internalServer,
90 this.mbeanInfo = generateMBeanInfo(module.getClass().getName(), module,
91 attributeHolderMap, dOperations, jmxInterfaces);
95 * Register module into an internal mbean server, attach listener to the
96 * platform mbean server. Wait until this wrapper gets unregistered, in that
97 * case unregister the module and remove listener.
99 private final NotificationListener registerActualModule(Module module,
100 final ObjectName thisWrapperObjectName,
101 final ObjectName objectNameInternal,
102 final MBeanServer internalServer,
103 final MBeanServer configMBeanServer) {
106 internalServer.registerMBean(module, objectNameInternal);
107 } catch (InstanceAlreadyExistsException | MBeanRegistrationException
108 | NotCompliantMBeanException | IllegalStateException e) {
109 throw new IllegalStateException(
110 "Error occured during mbean registration with name " + objectNameInternal, e);
113 NotificationListener listener = new NotificationListener() {
115 public void handleNotification(Notification n, Object handback) {
116 if (n instanceof MBeanServerNotification
118 .equals(MBeanServerNotification.UNREGISTRATION_NOTIFICATION)) {
119 if (((MBeanServerNotification) n).getMBeanName().equals(
120 thisWrapperObjectName)) {
122 internalServer.unregisterMBean(objectNameInternal);
123 configMBeanServer.removeNotificationListener(
124 MBeanServerDelegate.DELEGATE_NAME, this);
125 } catch (MBeanRegistrationException
126 | ListenerNotFoundException
127 | InstanceNotFoundException e) {
128 throw new IllegalStateException(e);
135 configMBeanServer.addNotificationListener(
136 MBeanServerDelegate.DELEGATE_NAME, listener, null, null);
137 } catch (InstanceNotFoundException e) {
138 throw new RuntimeException("Could not add notification listener", e);
143 private static MBeanInfo generateMBeanInfo(String className, Module module,
144 Map<String, AttributeHolder> attributeHolderMap,
145 MBeanOperationInfo[] dOperations, Set<Class<?>> jmxInterfaces) {
147 String dDescription = findDescription(module.getClass(), jmxInterfaces);
148 MBeanConstructorInfo[] dConstructors = new MBeanConstructorInfo[0];
149 List<MBeanAttributeInfo> attributes = new ArrayList<>(
150 attributeHolderMap.size());
151 for (AttributeHolder attributeHolder : attributeHolderMap.values()) {
152 attributes.add(attributeHolder.toMBeanAttributeInfo());
154 return new MBeanInfo(className, dDescription,
155 attributes.toArray(new MBeanAttributeInfo[0]), dConstructors,
156 dOperations, new MBeanNotificationInfo[0]);
159 static String findDescription(Class<?> clazz, Set<Class<?>> jmxInterfaces) {
160 List<Description> descriptions = AnnotationsHelper
161 .findClassAnnotationInSuperClassesAndIfcs(clazz, Description.class, jmxInterfaces);
162 return AnnotationsHelper.aggregateDescriptions(descriptions);
165 protected static MBeanOperationInfo[] getEmptyOperations() {
166 return new MBeanOperationInfo[0];
169 // inspect all exported interfaces ending with MXBean, extract getters &
170 // setters into attribute holder
171 private static Map<String, AttributeHolder> buildMBeanInfo(Module module,
172 boolean writable, ModuleIdentifier moduleIdentifier,
173 Set<Class<?>> jmxInterfaces, MBeanServer internalServer,
174 ObjectName internalObjectName) {
176 // internal variables for describing MBean elements
177 Set<Method> methods = new HashSet<>();
179 for (Class<?> exportedClass : jmxInterfaces) {
180 Method[] ifcMethods = exportedClass.getMethods();
181 methods.addAll(Arrays.asList(ifcMethods));
183 // TODO: fix reflection, not used
184 MBeanInfo internalInfo;
186 internalInfo = internalServer.getMBeanInfo(internalObjectName);
187 } catch (InstanceNotFoundException | ReflectionException
188 | IntrospectionException e) {
189 throw new RuntimeException("MBean info not found", e);
192 Map<String, MBeanAttributeInfo> attributeMap = new HashMap<>();
193 for (MBeanAttributeInfo a : internalInfo.getAttributes()) {
194 attributeMap.put(a.getName(), a);
196 Map<String, AttributeHolder> attributeHolderMap = new HashMap<>();
197 for (Method method : methods) {
199 if (method.getParameterTypes().length == 1
200 && method.getName().startsWith("set")) {
202 String attribName = method.getName().substring(3);
204 setter = module.getClass().getMethod(method.getName(),
205 method.getParameterTypes());
206 } catch (NoSuchMethodException e) {
207 throw new RuntimeException("No such method on "
208 + moduleIdentifier, e);
210 RequireInterface ifc = AttributeHolder
211 .findRequireInterfaceAnnotation(setter, jmxInterfaces);
212 String description = null;
214 description = AttributeHolder.findDescription(setter,
217 AttributeHolder attributeHolder = new AttributeHolder(
218 attribName, module, attributeMap.get(attribName)
219 .getType(), writable, ifc, description);
220 attributeHolderMap.put(attribName, attributeHolder);
223 return attributeHolderMap;
226 // DynamicMBean methods
229 public MBeanInfo getMBeanInfo() {
234 public Object getAttribute(String attributeName)
235 throws AttributeNotFoundException, MBeanException,
236 ReflectionException {
237 if ("MBeanInfo".equals(attributeName)) {
238 return getMBeanInfo();
244 .getAttribute(objectNameInternal, attributeName);
245 } catch (InstanceNotFoundException e) {
246 new MBeanException(e);
249 if (obj instanceof ObjectName) {
250 AttributeHolder attributeHolder = attributeHolderMap
252 if (attributeHolder.getRequireInterfaceOrNull() != null) {
253 obj = fixObjectName((ObjectName) obj);
259 if (isDependencyListAttr(attributeName, obj)) {
260 obj = fixDependencyListAttribute(obj);
266 private Object fixDependencyListAttribute(Object attribute) {
267 if (attribute.getClass().isArray() == false) {
268 throw new IllegalArgumentException("Unexpected attribute type, should be an array, but was " + attribute.getClass());
271 for (int i = 0; i < Array.getLength(attribute); i++) {
273 Object on = Array.get(attribute, i);
274 if (on instanceof ObjectName == false) {
275 throw new IllegalArgumentException("Unexpected attribute type, should be an ObjectName, but was " + on.getClass());
277 on = fixObjectName((ObjectName) on);
279 Array.set(attribute, i, on);
285 private boolean isDependencyListAttr(String attributeName, Object attribute) {
286 if (attributeHolderMap.containsKey(attributeName) == false) {
290 AttributeHolder attributeHolder = attributeHolderMap.get(attributeName);
292 boolean isDepList = true;
293 isDepList &= attributeHolder.getRequireInterfaceOrNull() != null;
294 isDepList &= attribute instanceof ObjectName[];
298 protected ObjectName fixObjectName(ObjectName on) {
299 if (!ObjectNameUtil.ON_DOMAIN.equals(on.getDomain())) {
300 throw new IllegalArgumentException("Wrong domain, expected "
301 + ObjectNameUtil.ON_DOMAIN + " setter on " + on);
303 // if on contains transaction name, remove it
304 String transactionName = ObjectNameUtil.getTransactionName(on);
305 if (transactionName != null) {
306 return ObjectNameUtil.withoutTransactionName(on);
313 public AttributeList getAttributes(String[] attributes) {
314 AttributeList result = new AttributeList();
315 for (String attributeName : attributes) {
317 Object value = getAttribute(attributeName);
318 result.add(new Attribute(attributeName, value));
320 } catch (Exception e) {
321 LOGGER.debug("Getting attribute {} failed", attributeName, e);
328 public Object invoke(String actionName, Object[] params, String[] signature)
329 throws MBeanException, ReflectionException {
330 if ("getAttribute".equals(actionName) && params.length == 1
331 && signature[0].equals(String.class.getName())) {
333 return getAttribute((String) params[0]);
334 } catch (AttributeNotFoundException e) {
335 throw new MBeanException(e, "Attribute not found on "
338 } else if ("getAttributes".equals(actionName) && params.length == 1
339 && signature[0].equals(String[].class.getName())) {
340 return getAttributes((String[]) params[0]);
341 } else if ("setAttributes".equals(actionName) && params.length == 1
342 && signature[0].equals(AttributeList.class.getName())) {
343 return setAttributes((AttributeList) params[0]);
345 LOGGER.debug("Operation not found {} ", actionName);
346 throw new UnsupportedOperationException(
347 format("Operation not found on %s. Method invoke is only supported for getInstance and getAttribute(s) "
348 + "method, got actionName %s, params %s, signature %s ",
349 moduleIdentifier, actionName, params, signature));
354 public final int hashCode() {
355 return module.hashCode();
359 public final boolean equals(Object other) {
360 return module.equals(other);