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