001/*
002 * (C) Copyright 2006-2008 Nuxeo SAS (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     matic
016 */
017package org.nuxeo.runtime.management.inspector;
018
019import java.beans.BeanInfo;
020import java.beans.FeatureDescriptor;
021import java.beans.Introspector;
022import java.beans.MethodDescriptor;
023import java.beans.ParameterDescriptor;
024import java.beans.PropertyDescriptor;
025import java.lang.reflect.Method;
026import java.util.HashMap;
027import java.util.HashSet;
028import java.util.Map;
029import java.util.Set;
030import java.util.regex.Matcher;
031import java.util.regex.Pattern;
032
033import javax.management.Descriptor;
034import javax.management.IntrospectionException;
035import javax.management.modelmbean.DescriptorSupport;
036import javax.management.modelmbean.ModelMBeanAttributeInfo;
037import javax.management.modelmbean.ModelMBeanConstructorInfo;
038import javax.management.modelmbean.ModelMBeanInfo;
039import javax.management.modelmbean.ModelMBeanInfoSupport;
040import javax.management.modelmbean.ModelMBeanNotificationInfo;
041import javax.management.modelmbean.ModelMBeanOperationInfo;
042
043import org.nuxeo.runtime.management.ManagementRuntimeException;
044
045/**
046 * @author matic
047 */
048public class ModelMBeanIntrospector {
049
050    protected final Class<?> clazz;
051
052    protected ModelMBeanInfo managementInfo;
053
054    protected final Map<String, ModelMBeanAttributeInfo> attributesInfo = new HashMap<String, ModelMBeanAttributeInfo>();
055
056    protected final Map<String, ModelMBeanConstructorInfo> constructorsInfo = new HashMap<String, ModelMBeanConstructorInfo>();
057
058    protected final Map<String, ModelMBeanOperationInfo> operationsInfo = new HashMap<String, ModelMBeanOperationInfo>();
059
060    protected final Map<String, ModelMBeanNotificationInfo> notificationsInfo = new HashMap<String, ModelMBeanNotificationInfo>();
061
062    public ModelMBeanIntrospector(Class<?> clazz) {
063        this.clazz = clazz;
064    }
065
066    ModelMBeanInfo introspect() {
067        if (managementInfo != null) {
068            return managementInfo;
069        }
070
071        // Collect ifaces
072        Set<Class<?>> ifaces = new HashSet<Class<?>>(1);
073        if (clazz.isInterface()) {
074            ifaces.add(clazz);
075        } else {
076            doCollectMgmtIfaces(ifaces, clazz);
077            if (ifaces.isEmpty()) {
078                doCollectIfaces(ifaces, clazz);
079            }
080        }
081
082        // Introspect
083        for (Class<?> iface : ifaces) {
084            BeanInfo beanInfo;
085            try {
086                beanInfo = Introspector.getBeanInfo(iface);
087            } catch (java.beans.IntrospectionException e) {
088                throw ManagementRuntimeException.wrap("Cannot introspect " + iface, e);
089            }
090            doCollectAttributes(iface, beanInfo);
091            doCollectConstructors(iface, beanInfo);
092            doCollectOperations(iface, beanInfo);
093            doCollectNotifications(iface, beanInfo);
094        }
095
096        // Assemble model mbean infos
097        managementInfo = new ModelMBeanInfoSupport(clazz.getCanonicalName(), "", attributesInfo.values().toArray(
098                new ModelMBeanAttributeInfo[attributesInfo.size()]), constructorsInfo.values().toArray(
099                new ModelMBeanConstructorInfo[constructorsInfo.size()]), operationsInfo.values().toArray(
100                new ModelMBeanOperationInfo[operationsInfo.size()]), notificationsInfo.values().toArray(
101                new ModelMBeanNotificationInfo[notificationsInfo.size()]));
102
103        return managementInfo;
104    }
105
106    protected void doCollectMgmtIfaces(Set<Class<?>> ifaces, Class<?> clazz) {
107        if (clazz == null) {
108            return;
109        }
110        if (Object.class.equals(clazz)) {
111            return;
112        }
113        for (Class<?> iface : clazz.getInterfaces()) {
114            if (iface.getName().endsWith("MBean") || iface.getName().endsWith("MXBean")) {
115                ifaces.add(iface);
116                doCollectMgmtIfaces(ifaces, iface);
117            }
118        }
119        doCollectMgmtIfaces(ifaces, clazz.getSuperclass());
120    }
121
122    protected void doCollectIfaces(Set<Class<?>> ifaces, Class<?> clazz) {
123        if (clazz == null) {
124            return;
125        }
126        if (Object.class.equals(clazz)) {
127            return;
128        }
129        for (Class<?> iface : clazz.getInterfaces()) {
130            if (iface.getName().endsWith("MBean") || iface.getName().endsWith("MXBean")) {
131                ifaces.clear();
132                ifaces.add(iface);
133                return;
134            }
135            ifaces.add(iface);
136        }
137        doCollectIfaces(ifaces, clazz.getSuperclass());
138    }
139
140    protected void doCollectNotifications(Class<?> clazz, BeanInfo info) {
141    }
142
143    protected void doCollectAttributes(Class<?> clazz, BeanInfo beanInfo) {
144        for (PropertyDescriptor propertyInfo : beanInfo.getPropertyDescriptors()) {
145            if (propertyInfo.isHidden()) {
146                continue;
147            }
148            ModelMBeanAttributeInfo attributeInfo = null;
149            try {
150                Descriptor descriptor = doGetDescriptor(propertyInfo, "attribute");
151                Method readMethod = propertyInfo.getReadMethod();
152                Method writeMethod = propertyInfo.getWriteMethod();
153                if (readMethod != null) {
154                    descriptor.setField("getMethod", readMethod.getName());
155                }
156                if (writeMethod != null) {
157                    descriptor.setField("setMethod", writeMethod.getName());
158                }
159                attributeInfo = new ModelMBeanAttributeInfo(propertyInfo.getName(), propertyInfo.getShortDescription(),
160                        propertyInfo.getReadMethod(), propertyInfo.getWriteMethod(), descriptor);
161
162            } catch (IntrospectionException e) {
163                continue;
164            }
165            attributesInfo.put(attributeInfo.getName(), attributeInfo);
166        }
167    }
168
169    protected void doCollectConstructors(Class<?> clazz, BeanInfo info) {
170    }
171
172    protected void doCollectOperations(Class<?> clazz, BeanInfo beanInfo) {
173        for (MethodDescriptor methodInfo : beanInfo.getMethodDescriptors()) {
174            if (methodInfo.isHidden()) {
175                continue;
176            }
177            Descriptor descriptor = doGetDescriptor(methodInfo, "operation");
178            String name = methodInfo.getName();
179            Method method = methodInfo.getMethod();
180            ParameterDescriptor[] parameters = methodInfo.getParameterDescriptors();
181            boolean hasParameters = parameters != null && parameters.length > 0;
182            Class<?> returnType = method.getReturnType();
183            boolean returnValue = returnType != null && !void.class.equals(returnType);
184            if ((name.startsWith("get") && hasParameters && returnValue)
185                    || (name.startsWith("is") && !hasParameters && boolean.class.equals(returnType))) {
186                descriptor.setField("role", "getter");
187            } else if (methodInfo.getName().startsWith("set") && void.class.equals(returnType) && hasParameters
188                    && parameters.length == 1) {
189                // doFixAttribute(clazz, methodInfo.getName());
190                descriptor.setField("role", "setter");
191            } else {
192                descriptor.setField("role", "operation");
193            }
194            ModelMBeanOperationInfo operationInfo = new ModelMBeanOperationInfo(methodInfo.getShortDescription(),
195                    methodInfo.getMethod(), descriptor);
196            operationsInfo.put(operationInfo.getName(), operationInfo);
197        }
198    }
199
200    protected Descriptor doGetDescriptor(FeatureDescriptor info, String descriptorType) {
201        Descriptor descriptor = new DescriptorSupport();
202        descriptor.setField("name", info.getName());
203        descriptor.setField("displayName", info.getDisplayName());
204        descriptor.setField("description", info.getShortDescription());
205        descriptor.setField("descriptorType", descriptorType);
206        return descriptor;
207    }
208
209    private final Pattern attributePattern = Pattern.compile("(get|set|is)(.*)");
210
211    protected String doExtractMethodSuffix(String operationName) {
212        Matcher matcher = attributePattern.matcher(operationName);
213        if (!matcher.matches()) {
214            throw new IllegalArgumentException(operationName + " does not match");
215        }
216        return matcher.group(2);
217    }
218
219}