| /* |
| * 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.geode.internal.process; |
| |
| import static org.apache.commons.lang3.Validate.isTrue; |
| import static org.apache.commons.lang3.Validate.notNull; |
| |
| import java.io.IOException; |
| import java.util.Properties; |
| import java.util.Set; |
| |
| import javax.management.InstanceNotFoundException; |
| import javax.management.MBeanException; |
| import javax.management.MBeanServerConnection; |
| import javax.management.ObjectName; |
| import javax.management.Query; |
| import javax.management.QueryExp; |
| import javax.management.ReflectionException; |
| import javax.management.remote.JMXConnector; |
| import javax.management.remote.JMXConnectorFactory; |
| import javax.management.remote.JMXServiceURL; |
| |
| import com.sun.tools.attach.AttachNotSupportedException; |
| import com.sun.tools.attach.VirtualMachine; |
| |
| /** |
| * Controls a process using the Attach API to manipulate MBeans. |
| * |
| * @since GemFire 8.0 |
| */ |
| class MBeanProcessController implements ProcessController { |
| |
| /** Property name for the JMX local connector address (from sun.management.Agent) */ |
| private static final String PROPERTY_LOCAL_CONNECTOR_ADDRESS = |
| "com.sun.management.jmxremote.localConnectorAddress"; |
| |
| private static final Object[] PARAMS = {}; |
| private static final String[] SIGNATURE = {}; |
| |
| private final MBeanControllerParameters arguments; |
| private final int pid; |
| |
| private JMXConnector jmxc; |
| private MBeanServerConnection server; |
| |
| /** |
| * Constructs an instance for controlling a local process. |
| * |
| * @param pid process id identifying the process to attach to |
| * |
| * @throws IllegalArgumentException if pid is not a positive integer |
| */ |
| MBeanProcessController(final MBeanControllerParameters arguments, final int pid) { |
| notNull(arguments, "Invalid arguments '" + arguments + "' specified"); |
| isTrue(pid > 0, "Invalid pid '" + pid + "' specified"); |
| |
| this.pid = pid; |
| this.arguments = arguments; |
| } |
| |
| @Override |
| public int getProcessId() { |
| return pid; |
| } |
| |
| @Override |
| public String status() throws UnableToControlProcessException, ConnectionFailedException, |
| IOException, MBeanInvocationFailedException { |
| return status(arguments.getNamePattern(), arguments.getPidAttribute(), |
| arguments.getStatusMethod(), arguments.getAttributes(), arguments.getValues()); |
| } |
| |
| @Override |
| public void stop() throws UnableToControlProcessException, ConnectionFailedException, IOException, |
| MBeanInvocationFailedException { |
| stop(arguments.getNamePattern(), arguments.getPidAttribute(), arguments.getStopMethod(), |
| arguments.getAttributes(), arguments.getValues()); |
| } |
| |
| @Override |
| public void checkPidSupport() { |
| // nothing |
| } |
| |
| /** |
| * Connects to the process and tells it to shut down. |
| * |
| * @param namePattern the name pattern of the MBean to use for stopping |
| * @param pidAttribute the name of the MBean attribute with the process id to compare against |
| * @param stopMethod the name of the MBean operation to invoke |
| * @param attributes the names of the MBean attributes to compare with expected values |
| * @param values the expected values of the specified MBean attributes |
| * |
| * @throws ConnectionFailedException if there was a failure to connect to the local JMX connector |
| * in the process |
| * @throws IOException if a communication problem occurred when talking to the MBean server |
| * @throws MBeanInvocationFailedException if failed to invoke stop on the MBean for any reason |
| */ |
| private void stop(final ObjectName namePattern, final String pidAttribute, |
| final String stopMethod, final String[] attributes, final Object[] values) |
| throws ConnectionFailedException, IOException, MBeanInvocationFailedException { |
| invokeOperationOnTargetMBean(namePattern, pidAttribute, stopMethod, attributes, values); |
| } |
| |
| /** |
| * Connects to the process and acquires its status. |
| * |
| * @param namePattern the name pattern of the MBean to use for stopping |
| * @param pidAttribute the name of the MBean attribute with the process id to compare against |
| * @param statusMethod the name of the MBean operation to invoke |
| * @param attributes the names of the MBean attributes to compare with expected values |
| * @param values the expected values of the specified MBean attributes |
| * |
| * @return string describing the status of the process |
| * |
| * @throws ConnectionFailedException if there was a failure to connect to the local JMX connector |
| * in the process |
| * @throws IOException if a communication problem occurred when talking to the MBean server |
| * @throws MBeanInvocationFailedException if failed to invoke stop on the MBean for any reason |
| */ |
| private String status(final ObjectName namePattern, final String pidAttribute, |
| final String statusMethod, final String[] attributes, final Object[] values) |
| throws ConnectionFailedException, IOException, MBeanInvocationFailedException { |
| return invokeOperationOnTargetMBean(namePattern, pidAttribute, statusMethod, attributes, values) |
| .toString(); |
| } |
| |
| /** |
| * Connects to the process and use its MBean to stop it. |
| * |
| * @param namePattern the name pattern of the MBean to use for stopping |
| * @param pidAttribute the name of the MBean attribute with the process id to compare against |
| * @param methodName the name of the MBean operation to invoke |
| * @param attributes the names of the MBean attributes to compare with expected values |
| * @param values the expected values of the specified MBean attributes |
| * |
| * @throws ConnectionFailedException if there was a failure to connect to the local JMX connector |
| * in the process |
| * @throws IOException if a communication problem occurred when talking to the MBean server |
| * @throws MBeanInvocationFailedException if failed to invoke stop on the MBean for any reason |
| */ |
| private Object invokeOperationOnTargetMBean(final ObjectName namePattern, |
| final String pidAttribute, final String methodName, final String[] attributes, |
| final Object[] values) |
| throws ConnectionFailedException, IOException, MBeanInvocationFailedException { |
| ObjectName objectName = namePattern; |
| connect(); |
| try { |
| QueryExp constraint = buildQueryExp(pidAttribute, attributes, values); |
| Set<ObjectName> mbeanNames = server.queryNames(namePattern, constraint); |
| |
| if (mbeanNames.isEmpty()) { |
| throw new MBeanInvocationFailedException("Failed to find mbean matching '" + namePattern |
| + "' with attribute '" + pidAttribute + "' of value '" + pid + "'"); |
| } |
| if (mbeanNames.size() > 1) { |
| throw new MBeanInvocationFailedException("Found more than one mbean matching '" |
| + namePattern + "' with attribute '" + pidAttribute + "' of value '" + pid + "'"); |
| } |
| |
| objectName = mbeanNames.iterator().next(); |
| return invoke(objectName, methodName); |
| } catch (InstanceNotFoundException | MBeanException | ReflectionException e) { |
| throw new MBeanInvocationFailedException( |
| "Failed to invoke " + methodName + " on " + objectName, e); |
| } finally { |
| disconnect(); |
| } |
| } |
| |
| /** |
| * Connects to the JMX agent in the local process. |
| * |
| * @throws ConnectionFailedException if there was a failure to connect to the local JMX connector |
| * in the process |
| * @throws IOException if the JDK management agent cannot be found and loaded |
| */ |
| private void connect() throws ConnectionFailedException, IOException { |
| try { |
| JMXServiceURL jmxUrl = getJMXServiceURL(); |
| jmxc = JMXConnectorFactory.connect(jmxUrl); |
| server = jmxc.getMBeanServerConnection(); |
| } catch (AttachNotSupportedException e) { |
| throw new ConnectionFailedException("Failed to connect to process '" + pid + "'", e); |
| } |
| } |
| |
| /** |
| * Disconnects from the JMX agent in the local process. |
| */ |
| private void disconnect() { |
| server = null; |
| if (jmxc != null) { |
| try { |
| jmxc.close(); |
| } catch (IOException ignored) { |
| // ignore |
| } |
| } |
| jmxc = null; |
| } |
| |
| /** |
| * Uses the Attach API to connect to the local process and ensures that it has loaded the JMX |
| * management agent. The JMXServiceURL identifying the local connector address for the JMX agent |
| * in the process is returned. |
| * |
| * @return the address of the JMX API connector server for connecting to the local process |
| * |
| * @throws AttachNotSupportedException if unable to use the Attach API to connect to the process |
| * @throws IOException if the JDK management agent cannot be found and loaded |
| */ |
| private JMXServiceURL getJMXServiceURL() throws AttachNotSupportedException, IOException { |
| String connectorAddress; |
| VirtualMachine vm = VirtualMachine.attach(String.valueOf(pid)); |
| try { |
| Properties agentProps = vm.getAgentProperties(); |
| connectorAddress = agentProps.getProperty(PROPERTY_LOCAL_CONNECTOR_ADDRESS); |
| |
| if (connectorAddress == null) { |
| // make sure the local management agent is started and this will ensure |
| // localConnectorAddress is present in the agent properties. |
| vm.startLocalManagementAgent(); |
| // get the connector address |
| agentProps = vm.getAgentProperties(); |
| connectorAddress = agentProps.getProperty(PROPERTY_LOCAL_CONNECTOR_ADDRESS); |
| } |
| } finally { |
| vm.detach(); |
| } |
| |
| if (connectorAddress == null) { |
| // should never reach here |
| throw new IOException("Failed to find address to attach to process"); |
| } |
| |
| return new JMXServiceURL(connectorAddress); |
| } |
| |
| /** |
| * Builds the QueryExp used to identify the target MBean. |
| * |
| * @param pidAttribute the name of the MBean attribute with the process id to compare against |
| * @param attributes the names of additional MBean attributes to compare with expected values |
| * @param values the expected values of the specified MBean attributes |
| * |
| * @return the main QueryExp for matching the target MBean |
| */ |
| private QueryExp buildQueryExp(final String pidAttribute, final String[] attributes, |
| final Object[] values) { |
| QueryExp optionalAttributes = buildOptionalQueryExp(attributes, values); |
| QueryExp constraint; |
| if (optionalAttributes != null) { |
| constraint = |
| Query.and(optionalAttributes, Query.eq(Query.attr(pidAttribute), Query.value(pid))); |
| } else { |
| constraint = Query.eq(Query.attr(pidAttribute), Query.value(pid)); |
| } |
| return constraint; |
| } |
| |
| /** |
| * Builds an optional QueryExp to aid in matching the correct MBean using additional attributes |
| * with the specified values. Returns null if no attributes and values were specified during |
| * construction. |
| * |
| * @param attributes the names of additional MBean attributes to compare with expected values |
| * @param values the expected values of the specified MBean attributes |
| * |
| * @return optional QueryExp to aid in matching the correct MBean |
| */ |
| private QueryExp buildOptionalQueryExp(final String[] attributes, final Object[] values) { |
| QueryExp queryExp = null; |
| for (int i = 0; i < attributes.length; i++) { |
| if (values[i] instanceof Boolean) { |
| if (queryExp == null) { |
| queryExp = Query.eq(Query.attr(attributes[i]), Query.value((Boolean) values[i])); |
| } else { |
| queryExp = Query.and(queryExp, |
| Query.eq(Query.attr(attributes[i]), Query.value((Boolean) values[i]))); |
| } |
| } else if (values[i] instanceof Number) { |
| if (queryExp == null) { |
| queryExp = Query.eq(Query.attr(attributes[i]), Query.value((Number) values[i])); |
| } else { |
| queryExp = Query.and(queryExp, |
| Query.eq(Query.attr(attributes[i]), Query.value((Number) values[i]))); |
| } |
| } else if (values[i] instanceof String) { |
| if (queryExp == null) { |
| queryExp = Query.eq(Query.attr(attributes[i]), Query.value((String) values[i])); |
| } else { |
| queryExp = Query.and(queryExp, |
| Query.eq(Query.attr(attributes[i]), Query.value((String) values[i]))); |
| } |
| } |
| } |
| return queryExp; |
| } |
| |
| /** |
| * Invokes an operation on the specified MBean. |
| * |
| * @param objectName identifies the MBean |
| * @param method the name of the operation method invoke |
| * |
| * @return the result of invoking the operation on the MBean specified or null |
| * |
| * @throws InstanceNotFoundException if the specified MBean is not registered in the MBean server |
| * @throws IOException if a communication problem occurred when talking to the MBean server |
| * @throws MBeanException if the MBean operation throws an exception |
| * @throws ReflectionException if the MBean does not have the specified operation |
| */ |
| private Object invoke(final ObjectName objectName, final String method) |
| throws InstanceNotFoundException, IOException, MBeanException, ReflectionException { |
| return server.invoke(objectName, method, PARAMS, SIGNATURE); |
| } |
| } |