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