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