Initial code drop of yang model driven configuration system
[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.Method;
13 import java.util.ArrayList;
14 import java.util.Arrays;
15 import java.util.HashMap;
16 import java.util.HashSet;
17 import java.util.List;
18 import java.util.Map;
19 import java.util.Set;
20
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
44 import org.opendaylight.controller.config.api.ModuleIdentifier;
45 import org.opendaylight.controller.config.api.annotations.Description;
46 import org.opendaylight.controller.config.api.annotations.RequireInterface;
47 import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
48 import org.opendaylight.controller.config.manager.impl.util.InterfacesHelper;
49 import org.opendaylight.controller.config.spi.Module;
50 import org.slf4j.Logger;
51 import org.slf4j.LoggerFactory;
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 ", 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         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         return obj;
258
259     }
260
261     protected ObjectName fixObjectName(ObjectName on) {
262         if (!ObjectNameUtil.ON_DOMAIN.equals(on.getDomain()))
263             throw new IllegalArgumentException("Wrong domain, expected "
264                     + ObjectNameUtil.ON_DOMAIN + " setter on " + on);
265         // if on contains transaction name, remove it
266         String transactionName = ObjectNameUtil.getTransactionName(on);
267         if (transactionName != null)
268             return ObjectNameUtil.withoutTransactionName(on);
269         else
270             return on;
271     }
272
273     @Override
274     public AttributeList getAttributes(String[] attributes) {
275         AttributeList result = new AttributeList();
276         for (String attributeName : attributes) {
277             try {
278                 Object value = getAttribute(attributeName);
279                 result.add(new Attribute(attributeName, value));
280
281             } catch (Exception e) {
282                 logger.debug("Getting attribute {} failed", attributeName, e);
283             }
284         }
285         return result;
286     }
287
288     @Override
289     public Object invoke(String actionName, Object[] params, String[] signature)
290             throws MBeanException, ReflectionException {
291         if ("getAttribute".equals(actionName) && params.length == 1
292                 && signature[0].equals(String.class.getName())) {
293             try {
294                 return getAttribute((String) params[0]);
295             } catch (AttributeNotFoundException e) {
296                 throw new MBeanException(e, "Attribute not found on "
297                         + moduleIdentifier);
298             }
299         } else if ("getAttributes".equals(actionName) && params.length == 1
300                 && signature[0].equals(String[].class.getName())) {
301             return getAttributes((String[]) params[0]);
302         } else if ("setAttributes".equals(actionName) && params.length == 1
303                 && signature[0].equals(AttributeList.class.getName())) {
304             return setAttributes((AttributeList) params[0]);
305         } else {
306             logger.debug("Operation not found {} ", actionName);
307             throw new UnsupportedOperationException(
308                     format("Operation not found on %s. Method invoke is only supported for getInstance and getAttribute(s) "
309                             + "method, got actionName %s, params %s, signature %s ",
310                             moduleIdentifier, actionName, params, signature));
311         }
312     }
313
314     @Override
315     public final int hashCode() {
316         return module.hashCode();
317     }
318
319     @Override
320     public final boolean equals(Object other) {
321         return module.equals(other);
322     }
323
324 }

©2013 OpenDaylight, A Linux Foundation Collaborative Project. All Rights Reserved.
OpenDaylight is a registered trademark of The OpenDaylight Project, Inc.
Linux Foundation and OpenDaylight are registered trademarks of the Linux Foundation.
Linux is a registered trademark of Linus Torvalds.