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.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;
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;
44 import org.opendaylight.controller.config.api.ModuleIdentifier;
45 import org.opendaylight.controller.config.api.annotations.Description;
46 import org.opendaylight.controller.config.api.annotations.RequireInterface;
47 import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
48 import org.opendaylight.controller.config.manager.impl.util.InterfacesHelper;
49 import org.opendaylight.controller.config.spi.Module;
50 import org.slf4j.Logger;
51 import org.slf4j.LoggerFactory;
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 ", 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);
249 if (obj instanceof ObjectName) {
250 AttributeHolder attributeHolder = attributeHolderMap
252 if (attributeHolder.getRequireInterfaceOrNull() != null) {
253 obj = fixObjectName((ObjectName) obj);
261 protected ObjectName fixObjectName(ObjectName on) {
262 if (!ObjectNameUtil.ON_DOMAIN.equals(on.getDomain()))
263 throw new IllegalArgumentException("Wrong domain, expected "
264 + ObjectNameUtil.ON_DOMAIN + " setter on " + on);
265 // if on contains transaction name, remove it
266 String transactionName = ObjectNameUtil.getTransactionName(on);
267 if (transactionName != null)
268 return ObjectNameUtil.withoutTransactionName(on);
274 public AttributeList getAttributes(String[] attributes) {
275 AttributeList result = new AttributeList();
276 for (String attributeName : attributes) {
278 Object value = getAttribute(attributeName);
279 result.add(new Attribute(attributeName, value));
281 } catch (Exception e) {
282 logger.debug("Getting attribute {} failed", attributeName, e);
289 public Object invoke(String actionName, Object[] params, String[] signature)
290 throws MBeanException, ReflectionException {
291 if ("getAttribute".equals(actionName) && params.length == 1
292 && signature[0].equals(String.class.getName())) {
294 return getAttribute((String) params[0]);
295 } catch (AttributeNotFoundException e) {
296 throw new MBeanException(e, "Attribute not found on "
299 } else if ("getAttributes".equals(actionName) && params.length == 1
300 && signature[0].equals(String[].class.getName())) {
301 return getAttributes((String[]) params[0]);
302 } else if ("setAttributes".equals(actionName) && params.length == 1
303 && signature[0].equals(AttributeList.class.getName())) {
304 return setAttributes((AttributeList) params[0]);
306 logger.debug("Operation not found {} ", actionName);
307 throw new UnsupportedOperationException(
308 format("Operation not found on %s. Method invoke is only supported for getInstance and getAttribute(s) "
309 + "method, got actionName %s, params %s, signature %s ",
310 moduleIdentifier, actionName, params, signature));
315 public final int hashCode() {
316 return module.hashCode();
320 public final boolean equals(Object other) {
321 return module.equals(other);