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