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.
60 abstract class AbstractDynamicWrapper implements DynamicMBeanModuleWrapper {
61 private static final Logger logger = LoggerFactory
62 .getLogger(AbstractDynamicWrapper.class);
64 protected final boolean writable;
65 protected final Module module;
67 private final MBeanInfo mbeanInfo;
68 protected final ObjectName objectNameInternal;
69 protected final Map<String, AttributeHolder> attributeHolderMap;
70 protected final ModuleIdentifier moduleIdentifier;
71 protected final MBeanServer internalServer;
73 public AbstractDynamicWrapper(Module module, boolean writable,
74 ModuleIdentifier moduleIdentifier,
75 ObjectName thisWrapperObjectName, MBeanOperationInfo[] dOperations,
76 MBeanServer internalServer, MBeanServer configMBeanServer) {
78 this.writable = writable;
80 this.moduleIdentifier = moduleIdentifier;
81 this.internalServer = internalServer;
82 this.objectNameInternal = thisWrapperObjectName;
83 // register the actual instance into an mbean server.
84 registerActualModule(module, thisWrapperObjectName, objectNameInternal,
85 internalServer, configMBeanServer);
86 Set<Class<?>> jmxInterfaces = InterfacesHelper.getMXInterfaces(module
88 this.attributeHolderMap = buildMBeanInfo(module, writable,
89 moduleIdentifier, jmxInterfaces, internalServer,
91 this.mbeanInfo = generateMBeanInfo(module.getClass().getName(), module,
92 attributeHolderMap, dOperations, jmxInterfaces);
96 * Register module into an internal mbean server, attach listener to the
97 * platform mbean server. Wait until this wrapper gets unregistered, in that
98 * case unregister the module and remove listener.
100 private final NotificationListener registerActualModule(Module module,
101 final ObjectName thisWrapperObjectName,
102 final ObjectName objectNameInternal,
103 final MBeanServer internalServer,
104 final MBeanServer configMBeanServer) {
107 internalServer.registerMBean(module, objectNameInternal);
108 } catch (InstanceAlreadyExistsException | MBeanRegistrationException
109 | NotCompliantMBeanException | IllegalStateException e) {
110 throw new IllegalStateException(
111 "Error occured during mbean registration with name " + objectNameInternal, e);
114 NotificationListener listener = new NotificationListener() {
116 public void handleNotification(Notification n, Object handback) {
117 if (n instanceof MBeanServerNotification
119 .equals(MBeanServerNotification.UNREGISTRATION_NOTIFICATION)) {
120 if (((MBeanServerNotification) n).getMBeanName().equals(
121 thisWrapperObjectName)) {
123 internalServer.unregisterMBean(objectNameInternal);
124 configMBeanServer.removeNotificationListener(
125 MBeanServerDelegate.DELEGATE_NAME, this);
126 } catch (MBeanRegistrationException
127 | ListenerNotFoundException
128 | InstanceNotFoundException e) {
129 throw new IllegalStateException(e);
136 configMBeanServer.addNotificationListener(
137 MBeanServerDelegate.DELEGATE_NAME, listener, null, null);
138 } catch (InstanceNotFoundException e) {
139 throw new RuntimeException("Could not add notification listener", e);
144 private static MBeanInfo generateMBeanInfo(String className, Module module,
145 Map<String, AttributeHolder> attributeHolderMap,
146 MBeanOperationInfo[] dOperations, Set<Class<?>> jmxInterfaces) {
148 String dDescription = findDescription(module.getClass(), jmxInterfaces);
149 MBeanConstructorInfo[] dConstructors = new MBeanConstructorInfo[0];
150 List<MBeanAttributeInfo> attributes = new ArrayList<>(
151 attributeHolderMap.size());
152 for (AttributeHolder attributeHolder : attributeHolderMap.values()) {
153 attributes.add(attributeHolder.toMBeanAttributeInfo());
155 return new MBeanInfo(className, dDescription,
156 attributes.toArray(new MBeanAttributeInfo[0]), dConstructors,
157 dOperations, new MBeanNotificationInfo[0]);
160 static String findDescription(Class<?> clazz, Set<Class<?>> jmxInterfaces) {
161 List<Description> descriptions = AnnotationsHelper
162 .findClassAnnotationInSuperClassesAndIfcs(clazz, Description.class, jmxInterfaces);
163 return AnnotationsHelper.aggregateDescriptions(descriptions);
166 protected static MBeanOperationInfo[] getEmptyOperations() {
167 return new MBeanOperationInfo[0];
170 // inspect all exported interfaces ending with MXBean, extract getters &
171 // setters into attribute holder
172 private static Map<String, AttributeHolder> buildMBeanInfo(Module module,
173 boolean writable, ModuleIdentifier moduleIdentifier,
174 Set<Class<?>> jmxInterfaces, MBeanServer internalServer,
175 ObjectName internalObjectName) {
177 // internal variables for describing MBean elements
178 Set<Method> methods = new HashSet<>();
180 for (Class<?> exportedClass : jmxInterfaces) {
181 Method[] ifcMethods = exportedClass.getMethods();
182 methods.addAll(Arrays.asList(ifcMethods));
184 // TODO: fix reflection, not used
185 MBeanInfo internalInfo;
187 internalInfo = internalServer.getMBeanInfo(internalObjectName);
188 } catch (InstanceNotFoundException | ReflectionException
189 | IntrospectionException e) {
190 throw new RuntimeException("MBean info not found", e);
193 Map<String, MBeanAttributeInfo> attributeMap = new HashMap<>();
194 for (MBeanAttributeInfo a : internalInfo.getAttributes()) {
195 attributeMap.put(a.getName(), a);
197 Map<String, AttributeHolder> attributeHolderMap = new HashMap<>();
198 for (Method method : methods) {
200 if (method.getParameterTypes().length == 1
201 && method.getName().startsWith("set")) {
203 String attribName = method.getName().substring(3);
205 setter = module.getClass().getMethod(method.getName(),
206 method.getParameterTypes());
207 } catch (NoSuchMethodException e) {
208 throw new RuntimeException("No such method on "
209 + moduleIdentifier, e);
211 RequireInterface ifc = AttributeHolder
212 .findRequireInterfaceAnnotation(setter, jmxInterfaces);
213 String description = null;
215 description = AttributeHolder.findDescription(setter,
218 AttributeHolder attributeHolder = new AttributeHolder(
219 attribName, module, attributeMap.get(attribName)
220 .getType(), writable, ifc, description);
221 attributeHolderMap.put(attribName, attributeHolder);
224 return attributeHolderMap;
227 // DynamicMBean methods
230 public MBeanInfo getMBeanInfo() {
235 public Object getAttribute(String attributeName)
236 throws AttributeNotFoundException, MBeanException,
237 ReflectionException {
238 if (attributeName.equals("MBeanInfo")) {
239 return getMBeanInfo();
245 .getAttribute(objectNameInternal, attributeName);
246 } catch (InstanceNotFoundException e) {
247 new MBeanException(e);
250 if (obj instanceof ObjectName) {
251 AttributeHolder attributeHolder = attributeHolderMap
253 if (attributeHolder.getRequireInterfaceOrNull() != null) {
254 obj = fixObjectName((ObjectName) obj);
260 if(isDependencyListAttr(attributeName, obj)) {
261 obj = fixDependencyListAttribute(obj);
267 private Object fixDependencyListAttribute(Object attribute) {
268 if(attribute.getClass().isArray() == false)
269 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());
276 on = fixObjectName((ObjectName) on);
278 Array.set(attribute, i, on);
284 private boolean isDependencyListAttr(String attributeName, Object attribute) {
285 if (attributeHolderMap.containsKey(attributeName) == false)
288 AttributeHolder attributeHolder = attributeHolderMap.get(attributeName);
290 boolean isDepList = true;
291 isDepList &= attributeHolder.getRequireInterfaceOrNull() != null;
292 isDepList &= attribute instanceof ObjectName[];
296 protected ObjectName fixObjectName(ObjectName on) {
297 if (!ObjectNameUtil.ON_DOMAIN.equals(on.getDomain()))
298 throw new IllegalArgumentException("Wrong domain, expected "
299 + ObjectNameUtil.ON_DOMAIN + " setter on " + on);
300 // if on contains transaction name, remove it
301 String transactionName = ObjectNameUtil.getTransactionName(on);
302 if (transactionName != null)
303 return ObjectNameUtil.withoutTransactionName(on);
309 public AttributeList getAttributes(String[] attributes) {
310 AttributeList result = new AttributeList();
311 for (String attributeName : attributes) {
313 Object value = getAttribute(attributeName);
314 result.add(new Attribute(attributeName, value));
316 } catch (Exception e) {
317 logger.debug("Getting attribute {} failed", attributeName, e);
324 public Object invoke(String actionName, Object[] params, String[] signature)
325 throws MBeanException, ReflectionException {
326 if ("getAttribute".equals(actionName) && params.length == 1
327 && signature[0].equals(String.class.getName())) {
329 return getAttribute((String) params[0]);
330 } catch (AttributeNotFoundException e) {
331 throw new MBeanException(e, "Attribute not found on "
334 } else if ("getAttributes".equals(actionName) && params.length == 1
335 && signature[0].equals(String[].class.getName())) {
336 return getAttributes((String[]) params[0]);
337 } else if ("setAttributes".equals(actionName) && params.length == 1
338 && signature[0].equals(AttributeList.class.getName())) {
339 return setAttributes((AttributeList) params[0]);
341 logger.debug("Operation not found {} ", actionName);
342 throw new UnsupportedOperationException(
343 format("Operation not found on %s. Method invoke is only supported for getInstance and getAttribute(s) "
344 + "method, got actionName %s, params %s, signature %s ",
345 moduleIdentifier, actionName, params, signature));
350 public final int hashCode() {
351 return module.hashCode();
355 public final boolean equals(Object other) {
356 return module.equals(other);