1 /*
2  * Copyright (c) 2005, 2021, 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 
26 package com.sun.jmx.mbeanserver;
27 
28 import java.security.AccessController;
29 import java.util.Arrays;
30 import java.util.Collections;
31 import java.util.List;
32 import java.util.Map;
33 import javax.management.AttributeNotFoundException;
34 import javax.management.InvalidAttributeValueException;
35 import javax.management.MBeanException;
36 import javax.management.MBeanInfo;
37 import javax.management.ReflectionException;
38 
39 import 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  */
48 final class PerInterface<M> {
PerInterface(Class<?> mbeanInterface, MBeanIntrospector<M> introspector, MBeanAnalyzer<M> analyzer, MBeanInfo mbeanInfo)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 
getMBeanInterface()57     Class<?> getMBeanInterface() {
58         return mbeanInterface;
59     }
60 
getMBeanInfo()61     MBeanInfo getMBeanInfo() {
62         return mbeanInfo;
63     }
64 
isMXBean()65     boolean isMXBean() {
66         return introspector.isMXBean();
67     }
68 
getAttribute(Object resource, String attribute, Object cookie)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 
setAttribute(Object resource, String attribute, Object value, Object cookie)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 
invoke(Object resource, String operation, Object[] params, String[] signature, Object cookie)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     @SuppressWarnings("removal")
noSuchMethod(String msg, Object resource, String operation, Object[] params, String[] signature, Object cookie)163     private Object noSuchMethod(String msg, Object resource, String operation,
164                                 Object[] params, String[] signature,
165                                 Object cookie)
166             throws MBeanException, ReflectionException {
167 
168         // Construct the exception that we will probably throw
169         final NoSuchMethodException nsme =
170             new NoSuchMethodException(operation + sigString(signature));
171         final ReflectionException exception =
172             new ReflectionException(nsme, msg);
173 
174         if (introspector.isMXBean())
175             throw exception; // No compatibility requirement here
176 
177         // Is the compatibility property set?
178         GetPropertyAction act = new GetPropertyAction("jmx.invoke.getters");
179         String invokeGettersS;
180         try {
181             invokeGettersS = AccessController.doPrivileged(act);
182         } catch (Exception e) {
183             // We don't expect an exception here but if we get one then
184             // we'll simply assume that the property is not set.
185             invokeGettersS = null;
186         }
187         if (invokeGettersS == null)
188             throw exception;
189 
190         int rest = 0;
191         Map<String, M> methods = null;
192         if (signature == null || signature.length == 0) {
193             if (operation.startsWith("get"))
194                 rest = 3;
195             else if (operation.startsWith("is"))
196                 rest = 2;
197             if (rest != 0)
198                 methods = getters;
199         } else if (signature.length == 1 &&
200                    operation.startsWith("set")) {
201             rest = 3;
202             methods = setters;
203         }
204 
205         if (rest != 0) {
206             String attrName = operation.substring(rest);
207             M method = methods.get(attrName);
208             if (method != null && introspector.getName(method).equals(operation)) {
209                 String[] msig = introspector.getSignature(method);
210                 if ((signature == null && msig.length == 0) ||
211                         Arrays.equals(signature, msig)) {
212                     return introspector.invokeM(method, resource, params, cookie);
213                 }
214             }
215         }
216 
217         throw exception;
218     }
219 
sigString(String[] signature)220     private String sigString(String[] signature) {
221         StringBuilder b = new StringBuilder("(");
222         if (signature != null) {
223             for (String s : signature) {
224                 if (b.length() > 1)
225                     b.append(", ");
226                 b.append(s);
227             }
228         }
229         return b.append(")").toString();
230     }
231 
232     /**
233      * Visitor that sets up the method maps (operations, getters, setters).
234      */
235     private class InitMaps implements MBeanAnalyzer.MBeanVisitor<M> {
visitAttribute(String attributeName, M getter, M setter)236         public void visitAttribute(String attributeName,
237                                    M getter,
238                                    M setter) {
239             if (getter != null) {
240                 introspector.checkMethod(getter);
241                 final Object old = getters.put(attributeName, getter);
242                 assert(old == null);
243             }
244             if (setter != null) {
245                 introspector.checkMethod(setter);
246                 final Object old = setters.put(attributeName, setter);
247                 assert(old == null);
248             }
249         }
250 
visitOperation(String operationName, M operation)251         public void visitOperation(String operationName,
252                                    M operation) {
253             introspector.checkMethod(operation);
254             final String[] sig = introspector.getSignature(operation);
255             final MethodAndSig mas = new MethodAndSig();
256             mas.method = operation;
257             mas.signature = sig;
258             List<MethodAndSig> list = ops.get(operationName);
259             if (list == null)
260                 list = Collections.singletonList(mas);
261             else {
262                 if (list.size() == 1)
263                     list = newList(list);
264                 list.add(mas);
265             }
266             ops.put(operationName, list);
267         }
268     }
269 
270     private class MethodAndSig {
271         M method;
272         String[] signature;
273     }
274 
275     private final Class<?> mbeanInterface;
276     private final MBeanIntrospector<M> introspector;
277     private final MBeanInfo mbeanInfo;
278     private final Map<String, M> getters = newMap();
279     private final Map<String, M> setters = newMap();
280     private final Map<String, List<MethodAndSig>> ops = newMap();
281 }
282