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