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