d868f7c3321dd8ee9b0c28697d2c0c8b43c60fd5
[controller.git] / opendaylight / config / config-manager / src / main / java / org / opendaylight / controller / config / manager / impl / dynamicmbean / AbstractDynamicWrapper.java
1 /*
2  * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.controller.config.manager.impl.dynamicmbean;
9
10 import static java.lang.String.format;
11
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;
19 import java.util.Map;
20 import java.util.Set;
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;
51
52 import static java.lang.String.format;
53
54 /**
55  * Contains common code for readable/rw dynamic mbean wrappers. Routes all
56  * requests (getAttribute, setAttribute, invoke) into the actual instance, but
57  * provides additional functionality - namely it disallows setting attribute on
58  * a read only wrapper.
59  */
60 abstract class AbstractDynamicWrapper implements DynamicMBeanModuleWrapper {
61     private static final class ModuleNotificationListener implements NotificationListener {
62         private final ObjectName objectNameInternal;
63         private final MBeanServer internalServer;
64         private final ObjectName thisWrapperObjectName;
65         private final MBeanServer configMBeanServer;
66
67         private ModuleNotificationListener(final ObjectName objectNameInternal, final MBeanServer internalServer,
68                 final ObjectName thisWrapperObjectName, final MBeanServer configMBeanServer) {
69             this.objectNameInternal = objectNameInternal;
70             this.internalServer = internalServer;
71             this.thisWrapperObjectName = thisWrapperObjectName;
72             this.configMBeanServer = configMBeanServer;
73         }
74
75         @Override
76         public void handleNotification(final Notification n, final Object handback) {
77             if (n instanceof MBeanServerNotification
78                     && n.getType()
79                     .equals(MBeanServerNotification.UNREGISTRATION_NOTIFICATION)) {
80                 if (((MBeanServerNotification) n).getMBeanName().equals(
81                         thisWrapperObjectName)) {
82                     try {
83                         internalServer.unregisterMBean(objectNameInternal);
84                         configMBeanServer.removeNotificationListener(
85                                 MBeanServerDelegate.DELEGATE_NAME, this);
86                     } catch (MBeanRegistrationException
87                             | ListenerNotFoundException
88                             | InstanceNotFoundException e) {
89                         throw new IllegalStateException(e);
90                     }
91                 }
92             }
93         }
94     }
95
96     private static final Logger LOG = LoggerFactory.getLogger(AbstractDynamicWrapper.class);
97     protected final boolean writable;
98     protected final Module module;
99
100     private final MBeanInfo mbeanInfo;
101     protected final ObjectName objectNameInternal;
102     protected final Map<String, AttributeHolder> attributeHolderMap;
103     protected final ModuleIdentifier moduleIdentifier;
104     protected final MBeanServer internalServer;
105
106     public AbstractDynamicWrapper(final Module module, final boolean writable,
107                                   final ModuleIdentifier moduleIdentifier,
108                                   final ObjectName thisWrapperObjectName, final MBeanOperationInfo[] dOperations,
109                                   final MBeanServer internalServer, final MBeanServer configMBeanServer) {
110
111         this.writable = writable;
112         this.module = module;
113         this.moduleIdentifier = moduleIdentifier;
114         this.internalServer = internalServer;
115         this.objectNameInternal = thisWrapperObjectName;
116         // register the actual instance into an mbean server.
117         registerActualModule(module, thisWrapperObjectName, objectNameInternal,
118                 internalServer, configMBeanServer);
119         Set<Class<?>> jmxInterfaces = InterfacesHelper.getMXInterfaces(module
120                 .getClass());
121         this.attributeHolderMap = buildMBeanInfo(module, writable,
122                 moduleIdentifier, jmxInterfaces, internalServer,
123                 objectNameInternal);
124         this.mbeanInfo = generateMBeanInfo(module.getClass().getName(), module,
125                 attributeHolderMap, dOperations, jmxInterfaces);
126     }
127
128     /**
129      * Register module into an internal mbean server, attach listener to the
130      * platform mbean server. Wait until this wrapper gets unregistered, in that
131      * case unregister the module and remove listener.
132      */
133     private final NotificationListener registerActualModule(final Module module,
134                                                             final ObjectName thisWrapperObjectName,
135                                                             final ObjectName objectNameInternal,
136                                                             final MBeanServer internalServer,
137                                                             final MBeanServer configMBeanServer) {
138
139         try {
140             internalServer.registerMBean(module, objectNameInternal);
141         } catch (InstanceAlreadyExistsException | MBeanRegistrationException
142                 | NotCompliantMBeanException | IllegalStateException e) {
143             throw new IllegalStateException(
144                     "Error occured during mbean registration with name " + objectNameInternal, e);
145         }
146
147         NotificationListener listener = new ModuleNotificationListener(objectNameInternal, internalServer, thisWrapperObjectName, configMBeanServer);
148         try {
149             configMBeanServer.addNotificationListener(
150                     MBeanServerDelegate.DELEGATE_NAME, listener, null, null);
151         } catch (InstanceNotFoundException e) {
152             throw new RuntimeException("Could not add notification listener", e);
153         }
154         return listener;
155     }
156
157     private static MBeanInfo generateMBeanInfo(final String className, final Module module,
158                                                final Map<String, AttributeHolder> attributeHolderMap,
159                                                final MBeanOperationInfo[] dOperations, final Set<Class<?>> jmxInterfaces) {
160
161         String dDescription = findDescription(module.getClass(), jmxInterfaces);
162         MBeanConstructorInfo[] dConstructors = new MBeanConstructorInfo[0];
163         List<MBeanAttributeInfo> attributes = new ArrayList<>(
164                 attributeHolderMap.size());
165         for (AttributeHolder attributeHolder : attributeHolderMap.values()) {
166             attributes.add(attributeHolder.toMBeanAttributeInfo());
167         }
168         return new MBeanInfo(className, dDescription,
169                 attributes.toArray(new MBeanAttributeInfo[0]), dConstructors,
170                 dOperations, new MBeanNotificationInfo[0]);
171     }
172
173     static String findDescription(final Class<?> clazz, final Set<Class<?>> jmxInterfaces) {
174         List<Description> descriptions = AnnotationsHelper
175                 .findClassAnnotationInSuperClassesAndIfcs(clazz, Description.class, jmxInterfaces);
176         return AnnotationsHelper.aggregateDescriptions(descriptions);
177     }
178
179     protected static MBeanOperationInfo[] getEmptyOperations() {
180         return new MBeanOperationInfo[0];
181     }
182
183     // inspect all exported interfaces ending with MXBean, extract getters &
184     // setters into attribute holder
185     private static Map<String, AttributeHolder> buildMBeanInfo(final Module module,
186                                                                final boolean writable, final ModuleIdentifier moduleIdentifier,
187                                                                final Set<Class<?>> jmxInterfaces, final MBeanServer internalServer,
188                                                                final ObjectName internalObjectName) {
189
190         // internal variables for describing MBean elements
191         Set<Method> methods = new HashSet<>();
192
193         for (Class<?> exportedClass : jmxInterfaces) {
194             Method[] ifcMethods = exportedClass.getMethods();
195             methods.addAll(Arrays.asList(ifcMethods));
196         }
197         // TODO: fix reflection, not used
198         MBeanInfo internalInfo;
199         try {
200             internalInfo = internalServer.getMBeanInfo(internalObjectName);
201         } catch (InstanceNotFoundException | ReflectionException
202                 | IntrospectionException e) {
203             throw new RuntimeException("MBean info not found", e);
204         }
205
206         Map<String, MBeanAttributeInfo> attributeMap = new HashMap<>();
207         for (MBeanAttributeInfo a : internalInfo.getAttributes()) {
208             attributeMap.put(a.getName(), a);
209         }
210         Map<String, AttributeHolder> attributeHolderMap = new HashMap<>();
211         for (Method method : methods) {
212
213             if (method.getParameterTypes().length == 1
214                     && method.getName().startsWith("set")) {
215                 Method setter;
216                 String attribName = method.getName().substring(3);
217                 try {
218                     setter = module.getClass().getMethod(method.getName(),
219                             method.getParameterTypes());
220                 } catch (NoSuchMethodException e) {
221                     throw new RuntimeException("No such method on "
222                             + moduleIdentifier, e);
223                 }
224                 RequireInterface ifc = AttributeHolder
225                         .findRequireInterfaceAnnotation(setter, jmxInterfaces);
226                 String description = null;
227                 if (ifc != null) {
228                     description = AttributeHolder.findDescription(setter,
229                             jmxInterfaces);
230                 }
231                 AttributeHolder attributeHolder = new AttributeHolder(
232                         attribName, module, attributeMap.get(attribName)
233                             .getType(), writable, ifc, description);
234                 attributeHolderMap.put(attribName, attributeHolder);
235             }
236         }
237         return attributeHolderMap;
238     }
239
240     // DynamicMBean methods
241
242     @Override
243     public MBeanInfo getMBeanInfo() {
244         return mbeanInfo;
245     }
246
247     @Override
248     public Object getAttribute(final String attributeName)
249             throws AttributeNotFoundException, MBeanException,
250             ReflectionException {
251         if ("MBeanInfo".equals(attributeName)) {
252             return getMBeanInfo();
253         }
254
255         Object obj = null;
256         try {
257             obj = internalServer
258                     .getAttribute(objectNameInternal, attributeName);
259         } catch (InstanceNotFoundException e) {
260             new MBeanException(e);
261         }
262
263         if (obj instanceof ObjectName) {
264             AttributeHolder attributeHolder = attributeHolderMap
265                     .get(attributeName);
266             if (attributeHolder.getRequireInterfaceOrNull() != null) {
267                 obj = fixObjectName((ObjectName) obj);
268             }
269             return obj;
270         }
271
272
273         if (isDependencyListAttr(attributeName, obj)) {
274             obj = fixDependencyListAttribute(obj);
275         }
276
277         return obj;
278     }
279
280     private Object fixDependencyListAttribute(final Object attribute) {
281         if (attribute.getClass().isArray() == false) {
282             throw new IllegalArgumentException("Unexpected attribute type, should be an array, but was " + attribute.getClass());
283         }
284
285         for (int i = 0; i < Array.getLength(attribute); i++) {
286
287             Object on = Array.get(attribute, i);
288             if (on instanceof ObjectName == false) {
289                 throw new IllegalArgumentException("Unexpected attribute type, should be an ObjectName, but was " + on.getClass());
290             }
291             on = fixObjectName((ObjectName) on);
292
293             Array.set(attribute, i, on);
294         }
295
296         return attribute;
297     }
298
299     private boolean isDependencyListAttr(final String attributeName, final Object attribute) {
300         if (attributeHolderMap.containsKey(attributeName) == false) {
301             return false;
302         }
303
304         AttributeHolder attributeHolder = attributeHolderMap.get(attributeName);
305
306         boolean isDepList = true;
307         isDepList &= attributeHolder.getRequireInterfaceOrNull() != null;
308         isDepList &= attribute instanceof ObjectName[];
309         return isDepList;
310     }
311
312     protected ObjectName fixObjectName(final ObjectName on) {
313         if (!ObjectNameUtil.ON_DOMAIN.equals(on.getDomain())) {
314             throw new IllegalArgumentException("Wrong domain, expected "
315                     + ObjectNameUtil.ON_DOMAIN + " setter on " + on);
316         }
317         // if on contains transaction name, remove it
318         String transactionName = ObjectNameUtil.getTransactionName(on);
319         if (transactionName != null) {
320             return ObjectNameUtil.withoutTransactionName(on);
321         } else {
322             return on;
323         }
324     }
325
326     @Override
327     public AttributeList getAttributes(final String[] attributes) {
328         AttributeList result = new AttributeList();
329         for (String attributeName : attributes) {
330             try {
331                 Object value = getAttribute(attributeName);
332                 result.add(new Attribute(attributeName, value));
333
334             } catch (Exception e) {
335                 LOG.debug("Getting attribute {} failed", attributeName, e);
336             }
337         }
338         return result;
339     }
340
341     @Override
342     public Object invoke(final String actionName, final Object[] params, final String[] signature)
343             throws MBeanException, ReflectionException {
344         if ("getAttribute".equals(actionName) && params.length == 1
345                 && signature[0].equals(String.class.getName())) {
346             try {
347                 return getAttribute((String) params[0]);
348             } catch (AttributeNotFoundException e) {
349                 throw new MBeanException(e, "Attribute not found on "
350                         + moduleIdentifier);
351             }
352         } else if ("getAttributes".equals(actionName) && params.length == 1
353                 && signature[0].equals(String[].class.getName())) {
354             return getAttributes((String[]) params[0]);
355         } else if ("setAttributes".equals(actionName) && params.length == 1
356                 && signature[0].equals(AttributeList.class.getName())) {
357             return setAttributes((AttributeList) params[0]);
358         } else {
359             LOG.debug("Operation not found {} ", actionName);
360             throw new UnsupportedOperationException(
361                     format("Operation not found on %s. Method invoke is only supported for getInstance and getAttribute(s) "
362                             + "method, got actionName %s, params %s, signature %s ",
363                             moduleIdentifier, actionName, params, signature));
364         }
365     }
366
367     @Override
368     public final int hashCode() {
369         return module.hashCode();
370     }
371
372     @Override
373     public final boolean equals(final Object other) {
374         return module.equals(other);
375     }
376
377 }