| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.catalina.manager; |
| |
| import java.io.IOException; |
| import java.io.PrintWriter; |
| import java.io.Serial; |
| import java.util.Set; |
| |
| import javax.management.Attribute; |
| import javax.management.InstanceNotFoundException; |
| import javax.management.MBeanException; |
| import javax.management.MBeanInfo; |
| import javax.management.MBeanOperationInfo; |
| import javax.management.MBeanParameterInfo; |
| import javax.management.MBeanServer; |
| import javax.management.ObjectName; |
| import javax.management.OperationsException; |
| import javax.management.ReflectionException; |
| import javax.management.openmbean.CompositeData; |
| |
| import jakarta.servlet.ServletException; |
| import jakarta.servlet.http.HttpServlet; |
| import jakarta.servlet.http.HttpServletRequest; |
| import jakarta.servlet.http.HttpServletResponse; |
| |
| import org.apache.catalina.mbeans.MBeanDumper; |
| import org.apache.tomcat.util.modeler.Registry; |
| import org.apache.tomcat.util.res.StringManager; |
| |
| /** |
| * This servlet will dump JMX attributes in a simple format and implement proxy services for modeler. |
| * |
| * @author Costin Manolache |
| */ |
| public class JMXProxyServlet extends HttpServlet { |
| |
| @Serial |
| private static final long serialVersionUID = 1L; |
| |
| // Constant for "no parameters" when invoking a JMX operation |
| // without any parameters. |
| private static final String[] NO_PARAMETERS = new String[0]; |
| |
| private static final StringManager sm = StringManager.getManager(JMXProxyServlet.class); |
| |
| // ----------------------------------------------------- Instance Variables |
| /** |
| * MBean server. |
| */ |
| protected transient MBeanServer mBeanServer = null; |
| protected transient Registry registry; |
| |
| |
| // --------------------------------------------------------- Public Methods |
| |
| @Override |
| public void init() throws ServletException { |
| // Retrieve the MBean server |
| registry = Registry.getRegistry(null); |
| mBeanServer = Registry.getRegistry(null).getMBeanServer(); |
| } |
| |
| |
| @Override |
| public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { |
| response.setContentType("text/plain;charset=" + Constants.CHARSET); |
| // Stop older versions of IE thinking they know best. We set text/plain |
| // in the line above for a reason. IE's behaviour is unwanted at best |
| // and dangerous at worst. |
| response.setHeader("X-Content-Type-Options", "nosniff"); |
| PrintWriter writer = response.getWriter(); |
| |
| if (mBeanServer == null) { |
| writer.println("Error - No mbean server"); |
| return; |
| } |
| |
| String qry = request.getParameter("set"); |
| if (qry != null) { |
| String name = request.getParameter("att"); |
| String val = request.getParameter("val"); |
| |
| setAttribute(writer, qry, name, val); |
| return; |
| } |
| qry = request.getParameter("get"); |
| if (qry != null) { |
| String name = request.getParameter("att"); |
| getAttribute(writer, qry, name, request.getParameter("key")); |
| return; |
| } |
| qry = request.getParameter("invoke"); |
| if (qry != null) { |
| String opName = request.getParameter("op"); |
| String[] params = getInvokeParameters(request.getParameter("ps")); |
| invokeOperation(writer, qry, opName, params); |
| return; |
| } |
| qry = request.getParameter("qry"); |
| if (qry == null) { |
| qry = "*:*"; |
| } |
| |
| listBeans(writer, qry); |
| } |
| |
| |
| public void getAttribute(PrintWriter writer, String onameStr, String att, String key) { |
| try { |
| ObjectName oname = new ObjectName(onameStr); |
| Object value = mBeanServer.getAttribute(oname, att); |
| |
| if (null != key && value instanceof CompositeData) { |
| value = ((CompositeData) value).get(key); |
| } |
| |
| String valueStr; |
| if (value != null) { |
| valueStr = value.toString(); |
| } else { |
| valueStr = "<null>"; |
| } |
| |
| writer.print("OK - Attribute get '"); |
| writer.print(onameStr); |
| writer.print("' - "); |
| writer.print(att); |
| |
| if (null != key) { |
| writer.print(" - key '"); |
| writer.print(key); |
| writer.print("'"); |
| } |
| |
| writer.print(" = "); |
| |
| writer.println(MBeanDumper.escape(valueStr)); |
| } catch (Exception e) { |
| writer.println("Error - " + e.toString()); |
| e.printStackTrace(writer); |
| } |
| } |
| |
| |
| public void setAttribute(PrintWriter writer, String onameStr, String att, String val) { |
| try { |
| setAttributeInternal(onameStr, att, val); |
| writer.println("OK - Attribute set"); |
| } catch (Exception e) { |
| writer.println("Error - " + e.toString()); |
| e.printStackTrace(writer); |
| } |
| } |
| |
| |
| public void listBeans(PrintWriter writer, String qry) { |
| |
| Set<ObjectName> names; |
| try { |
| names = mBeanServer.queryNames(new ObjectName(qry), null); |
| writer.println("OK - Number of results: " + names.size()); |
| writer.println(); |
| } catch (Exception e) { |
| writer.println("Error - " + e.toString()); |
| e.printStackTrace(writer); |
| return; |
| } |
| |
| String dump = MBeanDumper.dumpBeans(mBeanServer, names); |
| writer.print(dump); |
| } |
| |
| |
| /** |
| * Determines if a type is supported by the {@link JMXProxyServlet}. |
| * |
| * @param type The type to check |
| * |
| * @return Always returns <code>true</code> |
| */ |
| public boolean isSupported(String type) { |
| return true; |
| } |
| |
| |
| private void invokeOperation(PrintWriter writer, String onameStr, String op, String[] valuesStr) { |
| try { |
| Object retVal = invokeOperationInternal(onameStr, op, valuesStr); |
| if (retVal != null) { |
| writer.println("OK - Operation " + op + " returned:"); |
| output("", writer, retVal); |
| } else { |
| writer.println("OK - Operation " + op + " without return value"); |
| } |
| } catch (Exception e) { |
| writer.println("Error - " + e.toString()); |
| e.printStackTrace(writer); |
| } |
| } |
| |
| |
| /** |
| * Parses parameter values from a parameter string. |
| * |
| * @param paramString The string containing comma-separated operation-invocation parameters, or <code>null</code> if |
| * there are no parameters. |
| * |
| * @return An array of String parameters (empty array if <code>paramString</code> was <code>null</code>). |
| */ |
| private String[] getInvokeParameters(String paramString) { |
| if (paramString == null) { |
| return NO_PARAMETERS; |
| } else { |
| return paramString.split(","); |
| } |
| } |
| |
| |
| /** |
| * Sets an MBean attribute's value. |
| */ |
| private void setAttributeInternal(String onameStr, String attributeName, String value) |
| throws OperationsException, MBeanException, ReflectionException { |
| ObjectName oname = new ObjectName(onameStr); |
| String type = registry.getType(oname, attributeName); |
| Object valueObj = registry.convertValue(type, value); |
| mBeanServer.setAttribute(oname, new Attribute(attributeName, valueObj)); |
| } |
| |
| |
| /** |
| * Invokes an operation on an MBean. |
| * |
| * @param onameStr The name of the MBean. |
| * @param operation The name of the operation to invoke. |
| * @param parameters An array of Strings containing the parameters to the operation. They will be converted to the |
| * appropriate types to call the requested operation. |
| * |
| * @return The value returned by the requested operation. |
| */ |
| @SuppressWarnings("null") // parameters can't be null if signature.length > 0 |
| private Object invokeOperationInternal(String onameStr, String operation, String[] parameters) |
| throws OperationsException, MBeanException, ReflectionException { |
| ObjectName oname = new ObjectName(onameStr); |
| int paramCount = null == parameters ? 0 : parameters.length; |
| MBeanOperationInfo methodInfo = registry.getMethodInfo(oname, operation, paramCount); |
| if (null == methodInfo) { |
| // getMethodInfo returns null for both "object not found" and "operation not found" |
| MBeanInfo info; |
| try { |
| info = registry.getMBeanServer().getMBeanInfo(oname); |
| } catch (InstanceNotFoundException infe) { |
| throw infe; |
| } catch (Exception e) { |
| throw new IllegalArgumentException(sm.getString("jmxProxyServlet.noBeanFound", onameStr), e); |
| } |
| throw new IllegalArgumentException(sm.getString("jmxProxyServlet.noOperationOnBean", operation, |
| Integer.valueOf(paramCount), onameStr, info.getClassName())); |
| } |
| |
| MBeanParameterInfo[] signature = methodInfo.getSignature(); |
| String[] signatureTypes = new String[signature.length]; |
| Object[] values = new Object[signature.length]; |
| for (int i = 0; i < signature.length; i++) { |
| MBeanParameterInfo pi = signature[i]; |
| signatureTypes[i] = pi.getType(); |
| values[i] = registry.convertValue(pi.getType(), parameters[i]); |
| } |
| |
| return mBeanServer.invoke(oname, operation, values, signatureTypes); |
| } |
| |
| |
| private void output(String indent, PrintWriter writer, Object result) { |
| if (result instanceof Object[]) { |
| for (Object obj : (Object[]) result) { |
| output(" " + indent, writer, obj); |
| } |
| } else { |
| String strValue; |
| if (result != null) { |
| strValue = result.toString(); |
| } else { |
| strValue = "<null>"; |
| } |
| writer.println(indent + strValue); |
| } |
| } |
| } |