1/*
2 * Copyright (c) 2005, 2008, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package com.sun.jmx.mbeanserver;
27
28import com.sun.jmx.mbeanserver.MBeanIntrospector.MBeanInfoMap;
29import com.sun.jmx.mbeanserver.MBeanIntrospector.PerInterfaceMap;
30import java.lang.annotation.Annotation;
31import java.lang.reflect.GenericArrayType;
32import java.lang.reflect.InvocationTargetException;
33import java.lang.reflect.Method;
34import java.lang.reflect.ParameterizedType;
35import java.lang.reflect.Type;
36import javax.management.Descriptor;
37import javax.management.ImmutableDescriptor;
38import javax.management.MBeanAttributeInfo;
39import javax.management.MBeanException;
40import javax.management.MBeanOperationInfo;
41import javax.management.MBeanParameterInfo;
42import javax.management.NotCompliantMBeanException;
43import javax.management.openmbean.OpenMBeanAttributeInfoSupport;
44import javax.management.openmbean.OpenMBeanOperationInfoSupport;
45import javax.management.openmbean.OpenMBeanParameterInfo;
46import javax.management.openmbean.OpenMBeanParameterInfoSupport;
47import javax.management.openmbean.OpenType;
48
49/**
50 * Introspector for MXBeans.  There is exactly one instance of this class.
51 *
52 * @since 1.6
53 */
54class MXBeanIntrospector extends MBeanIntrospector<ConvertingMethod> {
55    private static final MXBeanIntrospector instance = new MXBeanIntrospector();
56
57    static MXBeanIntrospector getInstance() {
58        return instance;
59    }
60
61    @Override
62    PerInterfaceMap<ConvertingMethod> getPerInterfaceMap() {
63        return perInterfaceMap;
64    }
65
66    @Override
67    MBeanInfoMap getMBeanInfoMap() {
68        return mbeanInfoMap;
69    }
70
71    @Override
72    MBeanAnalyzer<ConvertingMethod> getAnalyzer(Class<?> mbeanInterface)
73            throws NotCompliantMBeanException {
74        return MBeanAnalyzer.analyzer(mbeanInterface, this);
75    }
76
77    @Override
78    boolean isMXBean() {
79        return true;
80    }
81
82    @Override
83    ConvertingMethod mFrom(Method m) {
84        return ConvertingMethod.from(m);
85    }
86
87    @Override
88    String getName(ConvertingMethod m) {
89        return m.getName();
90    }
91
92    @Override
93    Type getGenericReturnType(ConvertingMethod m) {
94        return m.getGenericReturnType();
95    }
96
97    @Override
98    Type[] getGenericParameterTypes(ConvertingMethod m) {
99        return m.getGenericParameterTypes();
100    }
101
102    @Override
103    String[] getSignature(ConvertingMethod m) {
104        return m.getOpenSignature();
105    }
106
107    @Override
108    void checkMethod(ConvertingMethod m) {
109        m.checkCallFromOpen();
110    }
111
112    @Override
113    Object invokeM2(ConvertingMethod m, Object target, Object[] args,
114                    Object cookie)
115            throws InvocationTargetException, IllegalAccessException,
116                   MBeanException {
117        return m.invokeWithOpenReturn((MXBeanLookup) cookie, target, args);
118    }
119
120    @Override
121    boolean validParameter(ConvertingMethod m, Object value, int paramNo,
122                           Object cookie) {
123        if (value == null) {
124            // Null is a valid value for all OpenTypes, even though
125            // OpenType.isValue(null) will return false.  It can always be
126            // matched to the corresponding Java type, except when that
127            // type is primitive.
128            Type t = m.getGenericParameterTypes()[paramNo];
129            return (!(t instanceof Class<?>) || !((Class<?>) t).isPrimitive());
130        } else {
131            Object v;
132            try {
133                v = m.fromOpenParameter((MXBeanLookup) cookie, value, paramNo);
134            } catch (Exception e) {
135                // Ignore the exception and let MBeanIntrospector.invokeSetter()
136                // throw the initial exception.
137                return true;
138            }
139            return isValidParameter(m.getMethod(), v, paramNo);
140        }
141    }
142
143    @Override
144    MBeanAttributeInfo getMBeanAttributeInfo(String attributeName,
145            ConvertingMethod getter, ConvertingMethod setter) {
146
147        final boolean isReadable = (getter != null);
148        final boolean isWritable = (setter != null);
149        final boolean isIs = isReadable && getName(getter).startsWith("is");
150
151        final String description = attributeName;
152
153        final OpenType<?> openType;
154        final Type originalType;
155        if (isReadable) {
156            openType = getter.getOpenReturnType();
157            originalType = getter.getGenericReturnType();
158        } else {
159            openType = setter.getOpenParameterTypes()[0];
160            originalType = setter.getGenericParameterTypes()[0];
161        }
162        Descriptor descriptor = typeDescriptor(openType, originalType);
163        if (isReadable) {
164            descriptor = ImmutableDescriptor.union(descriptor,
165                    getter.getDescriptor());
166        }
167        if (isWritable) {
168            descriptor = ImmutableDescriptor.union(descriptor,
169                    setter.getDescriptor());
170        }
171
172        final MBeanAttributeInfo ai;
173        if (canUseOpenInfo(originalType)) {
174            ai = new OpenMBeanAttributeInfoSupport(attributeName,
175                                                   description,
176                                                   openType,
177                                                   isReadable,
178                                                   isWritable,
179                                                   isIs,
180                                                   descriptor);
181        } else {
182            ai = new MBeanAttributeInfo(attributeName,
183                                        originalTypeString(originalType),
184                                        description,
185                                        isReadable,
186                                        isWritable,
187                                        isIs,
188                                        descriptor);
189        }
190        // could also consult annotations for defaultValue,
191        // minValue, maxValue, legalValues
192
193        return ai;
194    }
195
196    @Override
197    MBeanOperationInfo getMBeanOperationInfo(String operationName,
198            ConvertingMethod operation) {
199        final Method method = operation.getMethod();
200        final String description = operationName;
201        /* Ideally this would be an empty string, but
202           OMBOperationInfo constructor forbids that.  Also, we
203           could consult an annotation to get a useful
204           description.  */
205
206        final int impact = MBeanOperationInfo.UNKNOWN;
207
208        final OpenType<?> returnType = operation.getOpenReturnType();
209        final Type originalReturnType = operation.getGenericReturnType();
210        final OpenType<?>[] paramTypes = operation.getOpenParameterTypes();
211        final Type[] originalParamTypes = operation.getGenericParameterTypes();
212        final MBeanParameterInfo[] params =
213            new MBeanParameterInfo[paramTypes.length];
214        boolean openReturnType = canUseOpenInfo(originalReturnType);
215        boolean openParameterTypes = true;
216        Annotation[][] annots = method.getParameterAnnotations();
217        for (int i = 0; i < paramTypes.length; i++) {
218            final String paramName = "p" + i;
219            final String paramDescription = paramName;
220            final OpenType<?> openType = paramTypes[i];
221            final Type originalType = originalParamTypes[i];
222            Descriptor descriptor =
223                typeDescriptor(openType, originalType);
224            descriptor = ImmutableDescriptor.union(descriptor,
225                    Introspector.descriptorForAnnotations(annots[i]));
226            final MBeanParameterInfo pi;
227            if (canUseOpenInfo(originalType)) {
228                pi = new OpenMBeanParameterInfoSupport(paramName,
229                                                       paramDescription,
230                                                       openType,
231                                                       descriptor);
232            } else {
233                openParameterTypes = false;
234                pi = new MBeanParameterInfo(
235                    paramName,
236                    originalTypeString(originalType),
237                    paramDescription,
238                    descriptor);
239            }
240            params[i] = pi;
241        }
242
243        Descriptor descriptor =
244            typeDescriptor(returnType, originalReturnType);
245        descriptor = ImmutableDescriptor.union(descriptor,
246                Introspector.descriptorForElement(method));
247        final MBeanOperationInfo oi;
248        if (openReturnType && openParameterTypes) {
249            /* If the return value and all the parameters can be faithfully
250             * represented as OpenType then we return an OpenMBeanOperationInfo.
251             * If any of them is a primitive type, we can't.  Compatibility
252             * with JSR 174 means that we must return an MBean*Info where
253             * the getType() is the primitive type, not its wrapped type as
254             * we would get with an OpenMBean*Info.  The OpenType is available
255             * in the Descriptor in either case.
256             */
257            final OpenMBeanParameterInfo[] oparams =
258                new OpenMBeanParameterInfo[params.length];
259            System.arraycopy(params, 0, oparams, 0, params.length);
260            oi = new OpenMBeanOperationInfoSupport(operationName,
261                                                   description,
262                                                   oparams,
263                                                   returnType,
264                                                   impact,
265                                                   descriptor);
266        } else {
267            oi = new MBeanOperationInfo(operationName,
268                                        description,
269                                        params,
270                                        openReturnType ?
271                                        returnType.getClassName() :
272                                        originalTypeString(originalReturnType),
273                                        impact,
274                                        descriptor);
275        }
276
277        return oi;
278    }
279
280    @Override
281    Descriptor getBasicMBeanDescriptor() {
282        return new ImmutableDescriptor("mxbean=true",
283                                       "immutableInfo=true");
284    }
285
286    @Override
287    Descriptor getMBeanDescriptor(Class<?> resourceClass) {
288        /* We already have immutableInfo=true in the Descriptor
289         * included in the MBeanInfo for the MXBean interface.  This
290         * method is being called for the MXBean *class* to add any
291         * new items beyond those in the interface Descriptor, which
292         * currently it does not.
293         */
294        return ImmutableDescriptor.EMPTY_DESCRIPTOR;
295    }
296
297    private static Descriptor typeDescriptor(OpenType<?> openType,
298                                             Type originalType) {
299        return new ImmutableDescriptor(
300            new String[] {"openType",
301                          "originalType"},
302            new Object[] {openType,
303                          originalTypeString(originalType)});
304    }
305
306    /**
307     * <p>True if this type can be faithfully represented in an
308     * OpenMBean*Info.</p>
309     *
310     * <p>Compatibility with JSR 174 means that primitive types must be
311     * represented by an MBean*Info whose getType() is the primitive type
312     * string, e.g. "int".  If we used an OpenMBean*Info then this string
313     * would be the wrapped type, e.g. "java.lang.Integer".</p>
314     *
315     * <p>Compatibility with JMX 1.2 (including J2SE 5.0) means that arrays
316     * of primitive types cannot use an ArrayType representing an array of
317     * primitives, because that didn't exist in JMX 1.2.</p>
318     */
319    private static boolean canUseOpenInfo(Type type) {
320        if (type instanceof GenericArrayType) {
321            return canUseOpenInfo(
322                ((GenericArrayType) type).getGenericComponentType());
323        } else if (type instanceof Class<?> && ((Class<?>) type).isArray()) {
324            return canUseOpenInfo(
325                ((Class<?>) type).getComponentType());
326        }
327        return (!(type instanceof Class<?> && ((Class<?>) type).isPrimitive()));
328    }
329
330    private static String originalTypeString(Type type) {
331        if (type instanceof Class<?>)
332            return ((Class<?>) type).getName();
333        else
334            return typeName(type);
335    }
336
337    static String typeName(Type type) {
338        if (type instanceof Class<?>) {
339            Class<?> c = (Class<?>) type;
340            if (c.isArray())
341                return typeName(c.getComponentType()) + "[]";
342            else
343                return c.getName();
344        } else if (type instanceof GenericArrayType) {
345            GenericArrayType gat = (GenericArrayType) type;
346            return typeName(gat.getGenericComponentType()) + "[]";
347        } else if (type instanceof ParameterizedType) {
348            ParameterizedType pt = (ParameterizedType) type;
349            StringBuilder sb = new StringBuilder();
350            sb.append(typeName(pt.getRawType())).append("<");
351            String sep = "";
352            for (Type t : pt.getActualTypeArguments()) {
353                sb.append(sep).append(typeName(t));
354                sep = ", ";
355            }
356            return sb.append(">").toString();
357        } else
358            return "???";
359    }
360
361    private final PerInterfaceMap<ConvertingMethod>
362        perInterfaceMap = new PerInterfaceMap<ConvertingMethod>();
363
364    private static final MBeanInfoMap mbeanInfoMap = new MBeanInfoMap();
365}
366