1/*
2 * Copyright (c) 2005, 2006, 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 java.security.AccessController;
29import java.util.Arrays;
30import java.util.Collections;
31import java.util.List;
32import java.util.Map;
33import javax.management.AttributeNotFoundException;
34import javax.management.InvalidAttributeValueException;
35import javax.management.MBeanException;
36import javax.management.MBeanInfo;
37import javax.management.ReflectionException;
38
39import static com.sun.jmx.mbeanserver.Util.*;
40
41/**
42 * Per-MBean-interface behavior.  A single instance of this class can be shared
43 * by all MBeans of the same kind (Standard MBean or MXBean) that have the same
44 * MBean interface.
45 *
46 * @since 1.6
47 */
48final class PerInterface<M> {
49    PerInterface(Class<?> mbeanInterface, MBeanIntrospector<M> introspector,
50                 MBeanAnalyzer<M> analyzer, MBeanInfo mbeanInfo) {
51        this.mbeanInterface = mbeanInterface;
52        this.introspector = introspector;
53        this.mbeanInfo = mbeanInfo;
54        analyzer.visit(new InitMaps());
55    }
56
57    Class<?> getMBeanInterface() {
58        return mbeanInterface;
59    }
60
61    MBeanInfo getMBeanInfo() {
62        return mbeanInfo;
63    }
64
65    boolean isMXBean() {
66        return introspector.isMXBean();
67    }
68
69    Object getAttribute(Object resource, String attribute, Object cookie)
70            throws AttributeNotFoundException,
71                   MBeanException,
72                   ReflectionException {
73
74        final M cm = getters.get(attribute);
75        if (cm == null) {
76            final String msg;
77            if (setters.containsKey(attribute))
78                msg = "Write-only attribute: " + attribute;
79            else
80                msg = "No such attribute: " + attribute;
81            throw new AttributeNotFoundException(msg);
82        }
83        return introspector.invokeM(cm, resource, (Object[]) null, cookie);
84    }
85
86    void setAttribute(Object resource, String attribute, Object value,
87                      Object cookie)
88            throws AttributeNotFoundException,
89                   InvalidAttributeValueException,
90                   MBeanException,
91                   ReflectionException {
92
93        final M cm = setters.get(attribute);
94        if (cm == null) {
95            final String msg;
96            if (getters.containsKey(attribute))
97                msg = "Read-only attribute: " + attribute;
98            else
99                msg = "No such attribute: " + attribute;
100            throw new AttributeNotFoundException(msg);
101        }
102        introspector.invokeSetter(attribute, cm, resource, value, cookie);
103    }
104
105    Object invoke(Object resource, String operation, Object[] params,
106                  String[] signature, Object cookie)
107            throws MBeanException, ReflectionException {
108
109        final List<MethodAndSig> list = ops.get(operation);
110        if (list == null) {
111            final String msg = "No such operation: " + operation;
112            return noSuchMethod(msg, resource, operation, params, signature,
113                                cookie);
114        }
115        if (signature == null)
116            signature = new String[0];
117        MethodAndSig found = null;
118        for (MethodAndSig mas : list) {
119            if (Arrays.equals(mas.signature, signature)) {
120                found = mas;
121                break;
122            }
123        }
124        if (found == null) {
125            final String badSig = sigString(signature);
126            final String msg;
127            if (list.size() == 1) {  // helpful exception message
128                msg = "Signature mismatch for operation " + operation +
129                        ": " + badSig + " should be " +
130                        sigString(list.get(0).signature);
131            } else {
132                msg = "Operation " + operation + " exists but not with " +
133                        "this signature: " + badSig;
134            }
135            return noSuchMethod(msg, resource, operation, params, signature,
136                                cookie);
137        }
138        return introspector.invokeM(found.method, resource, params, cookie);
139    }
140
141    /*
142     * This method is called when invoke doesn't find the named method.
143     * Before throwing an exception, we check to see whether the
144     * jmx.invoke.getters property is set, and if so whether the method
145     * being invoked might be a getter or a setter.  If so we invoke it
146     * and return the result.  This is for compatibility
147     * with code based on JMX RI 1.0 or 1.1 which allowed invoking getters
148     * and setters.  It is *not* recommended that new code use this feature.
149     *
150     * Since this method is either going to throw an exception or use
151     * functionality that is strongly discouraged, we consider that its
152     * performance is not very important.
153     *
154     * A simpler way to implement the functionality would be to add the getters
155     * and setters to the operations map when jmx.invoke.getters is set.
156     * However, that means that the property is consulted when an MBean
157     * interface is being introspected and not thereafter.  Previously,
158     * the property was consulted on every invocation.  So this simpler
159     * implementation could potentially break code that sets and unsets
160     * the property at different times.
161     */
162    private Object noSuchMethod(String msg, Object resource, String operation,
163                                Object[] params, String[] signature,
164                                Object cookie)
165            throws MBeanException, ReflectionException {
166
167        // Construct the exception that we will probably throw
168        final NoSuchMethodException nsme =
169            new NoSuchMethodException(operation + sigString(signature));
170        final ReflectionException exception =
171            new ReflectionException(nsme, msg);
172
173        if (introspector.isMXBean())
174            throw exception; // No compatibility requirement here
175
176        // Is the compatibility property set?
177        GetPropertyAction act = new GetPropertyAction("jmx.invoke.getters");
178        String invokeGettersS;
179        try {
180            invokeGettersS = AccessController.doPrivileged(act);
181        } catch (Exception e) {
182            // We don't expect an exception here but if we get one then
183            // we'll simply assume that the property is not set.
184            invokeGettersS = null;
185        }
186        if (invokeGettersS == null)
187            throw exception;
188
189        int rest = 0;
190        Map<String, M> methods = null;
191        if (signature == null || signature.length == 0) {
192            if (operation.startsWith("get"))
193                rest = 3;
194            else if (operation.startsWith("is"))
195                rest = 2;
196            if (rest != 0)
197                methods = getters;
198        } else if (signature.length == 1 &&
199                   operation.startsWith("set")) {
200            rest = 3;
201            methods = setters;
202        }
203
204        if (rest != 0) {
205            String attrName = operation.substring(rest);
206            M method = methods.get(attrName);
207            if (method != null && introspector.getName(method).equals(operation)) {
208                String[] msig = introspector.getSignature(method);
209                if ((signature == null && msig.length == 0) ||
210                        Arrays.equals(signature, msig)) {
211                    return introspector.invokeM(method, resource, params, cookie);
212                }
213            }
214        }
215
216        throw exception;
217    }
218
219    private String sigString(String[] signature) {
220        StringBuilder b = new StringBuilder("(");
221        if (signature != null) {
222            for (String s : signature) {
223                if (b.length() > 1)
224                    b.append(", ");
225                b.append(s);
226            }
227        }
228        return b.append(")").toString();
229    }
230
231    /**
232     * Visitor that sets up the method maps (operations, getters, setters).
233     */
234    private class InitMaps implements MBeanAnalyzer.MBeanVisitor<M> {
235        public void visitAttribute(String attributeName,
236                                   M getter,
237                                   M setter) {
238            if (getter != null) {
239                introspector.checkMethod(getter);
240                final Object old = getters.put(attributeName, getter);
241                assert(old == null);
242            }
243            if (setter != null) {
244                introspector.checkMethod(setter);
245                final Object old = setters.put(attributeName, setter);
246                assert(old == null);
247            }
248        }
249
250        public void visitOperation(String operationName,
251                                   M operation) {
252            introspector.checkMethod(operation);
253            final String[] sig = introspector.getSignature(operation);
254            final MethodAndSig mas = new MethodAndSig();
255            mas.method = operation;
256            mas.signature = sig;
257            List<MethodAndSig> list = ops.get(operationName);
258            if (list == null)
259                list = Collections.singletonList(mas);
260            else {
261                if (list.size() == 1)
262                    list = newList(list);
263                list.add(mas);
264            }
265            ops.put(operationName, list);
266        }
267    }
268
269    private class MethodAndSig {
270        M method;
271        String[] signature;
272    }
273
274    private final Class<?> mbeanInterface;
275    private final MBeanIntrospector<M> introspector;
276    private final MBeanInfo mbeanInfo;
277    private final Map<String, M> getters = newMap();
278    private final Map<String, M> setters = newMap();
279    private final Map<String, List<MethodAndSig>> ops = newMap();
280}
281