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