From 1787edaf3e0d44bbce36b0cc22644cfe6f1e661f Mon Sep 17 00:00:00 2001 From: gtully Date: Tue, 16 Jun 2015 15:03:04 +0100 Subject: [PATCH] https://issues.apache.org/jira/browse/AMQ-5724 - fix up mbean object name property clashes and attribute selection. add --invoke flag to invoke simple mbean operations with void or string params, Additional test that verifies help suggestions --- .../console/command/QueryCommand.java | 113 +++++++++-- .../activemq/console/util/JmxMBeansUtil.java | 6 +- .../activemq/console/QueryCommandTest.java | 183 ++++++++++++++++++ 3 files changed, 288 insertions(+), 14 deletions(-) create mode 100644 activemq-console/src/test/java/org/apache/activemq/console/QueryCommandTest.java diff --git a/activemq-console/src/main/java/org/apache/activemq/console/command/QueryCommand.java b/activemq-console/src/main/java/org/apache/activemq/console/command/QueryCommand.java index bdb6b886cf..0a5ee17684 100644 --- a/activemq-console/src/main/java/org/apache/activemq/console/command/QueryCommand.java +++ b/activemq-console/src/main/java/org/apache/activemq/console/command/QueryCommand.java @@ -16,6 +16,7 @@ */ package org.apache.activemq.console.command; +import javax.management.ObjectName; import org.apache.activemq.console.util.JmxMBeansUtil; import java.util.*; @@ -25,12 +26,12 @@ public class QueryCommand extends AbstractJmxCommand { private static final Properties PREDEFINED_OBJNAME_QUERY = new Properties(); static { - PREDEFINED_OBJNAME_QUERY.setProperty("Broker", "type=Broker,brokerName=%1"); - PREDEFINED_OBJNAME_QUERY.setProperty("Connection", "type=Broker,connector=clientConnectors,connectionName=%1,*"); - PREDEFINED_OBJNAME_QUERY.setProperty("Connector", "type=Broker,brokerName=*,connector=clientConnectors,connectorName=%1"); - PREDEFINED_OBJNAME_QUERY.setProperty("NetworkConnector", "type=Broker,brokerName=%1,connector=networkConnectors,networkConnectorName=*"); - PREDEFINED_OBJNAME_QUERY.setProperty("Queue", "type=Broker,brokerName=*,destinationType=Queue,destinationName=%1"); - PREDEFINED_OBJNAME_QUERY.setProperty("Topic", "type=Broker,brokerName=*,destinationType=Topic,destinationName=%1,*"); + PREDEFINED_OBJNAME_QUERY.setProperty("Broker", "brokerName=%1"); + PREDEFINED_OBJNAME_QUERY.setProperty("Connection", "connector=clientConnectors,connectionViewType=*,connectionName=%1,*"); + PREDEFINED_OBJNAME_QUERY.setProperty("Connector", "connector=clientConnectors,connectorName=%1"); + PREDEFINED_OBJNAME_QUERY.setProperty("NetworkConnector", "connector=networkConnectors,networkConnectorName=%1"); + PREDEFINED_OBJNAME_QUERY.setProperty("Queue", "destinationType=Queue,destinationName=%1"); + PREDEFINED_OBJNAME_QUERY.setProperty("Topic", "destinationType=Topic,destinationName=%1"); }; protected String[] helpFile = new String[] { @@ -48,6 +49,7 @@ public class QueryCommand extends AbstractJmxCommand { " similar to the JMX object name format.", " --view ,,... Select the specific attribute of the object to view.", " By default all attributes will be displayed.", + " --invoke Specify the operation to invoke on matching objects", " --jmxurl Set the JMX URL to connect to.", " --pid Set the pid to connect to (only on Sun JVM).", " --jmxuser Set the JMX user used for authenticating.", @@ -79,18 +81,23 @@ public class QueryCommand extends AbstractJmxCommand { " - Print all attributes of all topics except those that has a name that begins", " with \"ActiveMQ.Advisory\".", "", - " query --objname Type=*Connect*,BrokerName=local* -xQNetworkConnector=*", + " query --objname type=Broker,brokerName=*,connector=clientConnectors,connectorName=* -xQNetworkConnector=*", " - Print all attributes of all connectors, connections excluding network connectors", " that belongs to the broker that begins with local.", "", " query -QQueue=* -xQQueue=????", " - Print all attributes of all queues except those that are 4 letters long.", "", + " query -QQueue=* --invoke pause", + " - Pause all queues.", + "", + }; private final List queryAddObjects = new ArrayList(10); private final List querySubObjects = new ArrayList(10); private final Set queryViews = new LinkedHashSet(); + private final List opAndParams = new ArrayList(10); @Override public String getName() { @@ -111,21 +118,55 @@ public class QueryCommand extends AbstractJmxCommand { protected void runTask(List tokens) throws Exception { try { // Query for the mbeans to add - Map addMBeans = JmxMBeansUtil.queryMBeansAsMap(createJmxConnection(), queryAddObjects, queryViews); + Map addMBeans = JmxMBeansUtil.queryMBeansAsMap(createJmxConnection(), queryAddObjects, queryViews); // Query for the mbeans to sub if (querySubObjects.size() > 0) { - Map subMBeans = JmxMBeansUtil.queryMBeansAsMap(createJmxConnection(), querySubObjects, queryViews); + Map subMBeans = JmxMBeansUtil.queryMBeansAsMap(createJmxConnection(), querySubObjects, queryViews); addMBeans.keySet().removeAll(subMBeans.keySet()); } - context.printMBean(JmxMBeansUtil.filterMBeansView(new ArrayList(addMBeans.values()), queryViews)); + + if (opAndParams.isEmpty()) { + context.printMBean(JmxMBeansUtil.filterMBeansView(new ArrayList(addMBeans.values()), queryViews)); + } else { + context.print(doInvoke(addMBeans.keySet(), opAndParams)); + } } catch (Exception e) { context.printException(new RuntimeException("Failed to execute query task. Reason: " + e)); throw new Exception(e); } } + private Collection doInvoke(Set mBeans, List opAndParams) throws Exception { + LinkedList results = new LinkedList<>(); + for (Object objectName : mBeans) { + Object result = createJmxConnection().invoke((ObjectName) objectName, opAndParams.get(0), + params(opAndParams), stringSignature(opAndParams)); + results.add("[" + objectName + "]." + opAndParams.get(0) + " = " + result); + } + return results; + } + + private Object[] params(List opAndParams) { + if (opAndParams.size() > 1) { + return opAndParams.subList(1, opAndParams.size()).toArray(); + } else { + return null; + } + } + + private String[] stringSignature(List opAndParams) { + if (opAndParams.size() > 1) { + String[] sig = new String[opAndParams.size() - 1]; + Arrays.fill(sig, String.class.getName()); + return sig; + } else { + return null; + } + } + + /** - * Handle the -Q, -xQ, --objname, --xobjname, --view options. + * Handle the -Q, -xQ, --objname, --xobjname, --view --invoke options. * * @param token - option token to handle * @param tokens - succeeding command arguments @@ -153,6 +194,7 @@ public class QueryCommand extends AbstractJmxCommand { while (queryTokens.hasMoreTokens()) { queryAddObjects.add(queryTokens.nextToken()); } + normaliseObjectName(queryAddObjects); } else if (token.startsWith("-xQ")) { // If token is a substractive predefined query define option String key = token.substring(3); @@ -174,6 +216,7 @@ public class QueryCommand extends AbstractJmxCommand { while (queryTokens.hasMoreTokens()) { querySubObjects.add(queryTokens.nextToken()); } + normaliseObjectName(querySubObjects); } else if (token.startsWith("--objname")) { // If token is an additive object name query option @@ -216,12 +259,60 @@ public class QueryCommand extends AbstractJmxCommand { while (viewTokens.hasMoreElements()) { queryViews.add(viewTokens.nextElement()); } + } else if (token.startsWith("--invoke")) { + + if (tokens.isEmpty() || ((String)tokens.get(0)).startsWith("-")) { + context.printException(new IllegalArgumentException("operation to invoke is not specified")); + return; + } + + // add op and params + Enumeration viewTokens = new StringTokenizer((String)tokens.remove(0), COMMAND_OPTION_DELIMETER); + while (viewTokens.hasMoreElements()) { + opAndParams.add((String)viewTokens.nextElement()); + } + } else { // Let super class handle unknown option super.handleOption(token, tokens); } } + private void normaliseObjectName(List queryAddObjects) { + ensurePresent(queryAddObjects, "type", "Broker"); + ensurePresent(queryAddObjects, "brokerName", "*"); + + // -QQueue && -QTopic + ensureUnique(queryAddObjects, "destinationType", "?????"); + ensureUnique(queryAddObjects, "destinationName", "*"); + } + + private void ensurePresent(List queryAddObjects, String id, String wildcard) { + List matches = findMatchingKeys(queryAddObjects, id); + if (matches.size() == 0) { + queryAddObjects.add(id + "=" + wildcard); + } + } + + private void ensureUnique(List queryAddObjects, String id, String wildcard) { + List matches = findMatchingKeys(queryAddObjects, id); + if (matches.size() > 1) { + queryAddObjects.removeAll(matches); + queryAddObjects.add(id + "=" + wildcard); + } + } + + private List findMatchingKeys(List queryAddObjects, String id) { + List matches = new LinkedList<>(); + for (String prop : queryAddObjects) { + String[] keyValue = prop.split("="); + if (keyValue.length == 2 && keyValue[0].equals(id)) { + matches.add(prop); + } + } + return matches; + } + /** * Print the help messages for the browse command */ diff --git a/activemq-console/src/main/java/org/apache/activemq/console/util/JmxMBeansUtil.java b/activemq-console/src/main/java/org/apache/activemq/console/util/JmxMBeansUtil.java index baa40be892..24fe46b2a9 100644 --- a/activemq-console/src/main/java/org/apache/activemq/console/util/JmxMBeansUtil.java +++ b/activemq-console/src/main/java/org/apache/activemq/console/util/JmxMBeansUtil.java @@ -57,12 +57,12 @@ public final class JmxMBeansUtil { } } - public static Map queryMBeansAsMap(MBeanServerConnection jmxConnection, List queryList, Set attributes) throws Exception { - Map answer = new HashMap(); + public static Map queryMBeansAsMap(MBeanServerConnection jmxConnection, List queryList, Set attributes) throws Exception { + Map answer = new HashMap(); List mbeans = queryMBeans(jmxConnection, queryList, attributes); for (AttributeList mbean : mbeans) { for(Attribute attr: mbean.asList()) { - if (attr.getName().equals("Name")) { + if (attr.getName().equals(MBeansAttributeQueryFilter.KEY_OBJECT_NAME_ATTRIBUTE)) { answer.put(attr.getValue(), mbean); } } diff --git a/activemq-console/src/test/java/org/apache/activemq/console/QueryCommandTest.java b/activemq-console/src/test/java/org/apache/activemq/console/QueryCommandTest.java new file mode 100644 index 0000000000..b6a154ee47 --- /dev/null +++ b/activemq-console/src/test/java/org/apache/activemq/console/QueryCommandTest.java @@ -0,0 +1,183 @@ +/** + * 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.activemq.console; + +import java.io.ByteArrayOutputStream; +import java.util.Arrays; +import java.util.LinkedList; +import javax.jms.Connection; +import org.apache.activemq.ActiveMQConnectionFactory; +import org.apache.activemq.broker.BrokerService; +import org.apache.activemq.command.ActiveMQDestination; +import org.apache.activemq.command.ActiveMQQueue; +import org.apache.activemq.command.ActiveMQTopic; +import org.apache.activemq.console.command.QueryCommand; +import org.apache.activemq.console.formatter.CommandShellOutputFormatter; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.LoggerFactory; + + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class QueryCommandTest { + private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(QueryCommandTest.class); + + final String CONNECTOR_NAME="tcp-openWire"; + final String CLIENT_ID="some-id"; + + BrokerService brokerService; + + + @Before + public void createBroker() throws Exception { + brokerService = new BrokerService(); + brokerService.getManagementContext().setCreateConnector(false); + brokerService.setPersistent(false); + brokerService.setDestinations(new ActiveMQDestination[]{new ActiveMQQueue("Q1"), new ActiveMQQueue("Q2"), new ActiveMQTopic("T1")}); + brokerService.addConnector("tcp://0.0.0.0:0").setName(CONNECTOR_NAME); + brokerService.start(); + } + + @After + public void stopBroker() throws Exception { + if (brokerService != null) { + brokerService.stop(); + } + } + + @Test + public void tryQuery() throws Exception { + + String result = executeQuery("-QQueue=* --view destinationName,EnqueueCount,DequeueCount"); + assertTrue("Output valid", result.contains("Q1")); + assertTrue("Output valid", result.contains("Q2")); + assertFalse("Output valid", result.contains("T1")); + + result = executeQuery("-QQueue=Q2 --view destinationName,QueueSize"); + assertTrue("size present", result.contains("QueueSize")); + assertTrue("Output valid", result.contains("Q2")); + assertFalse("Output valid", result.contains("Q1")); + assertFalse("Output valid", result.contains("T1")); + + result = executeQuery("-QQueue=* -xQQueue=Q1 --view destinationName,QueueSize"); + assertTrue("size present", result.contains("QueueSize")); + assertTrue("q2", result.contains("Q2")); + assertFalse("!q1: " + result, result.contains("Q1")); + assertFalse("!t1", result.contains("T1")); + + result = executeQuery("-QTopic=* -QQueue=* --view destinationName"); + assertTrue("got Q1", result.contains("Q1")); + assertTrue("got Q2", result.contains("Q2")); + assertTrue("got T1", result.contains("T1")); + + result = executeQuery("-QQueue=*"); + assertTrue("got Q1", result.contains("Q1")); + assertTrue("got Q2", result.contains("Q2")); + assertFalse("!T1", result.contains("T1")); + + result = executeQuery("-QBroker=*"); + assertTrue("got localhost", result.contains("localhost")); + + result = executeQuery("--view destinationName"); + // all mbeans with a destinationName attribute + assertTrue("got Q1", result.contains("Q1")); + assertTrue("got Q2", result.contains("Q2")); + assertTrue("got T1", result.contains("T1")); + + result = executeQuery("--objname type=Broker,brokerName=*,destinationType=Queue,destinationName=*"); + assertTrue("got Q1", result.contains("Q1")); + assertTrue("got Q2", result.contains("Q2")); + assertFalse("!T1", result.contains("T1")); + + result = executeQuery("--objname type=Broker,brokerName=*,destinationType=*,destinationName=* --xobjname type=Broker,brokerName=*,destinationType=Queue,destinationName=Q1"); + assertFalse("!Q1", result.contains("Q1")); + assertTrue("got Q2", result.contains("Q2")); + assertTrue("T1", result.contains("T1")); + + } + + @Test + public void testConnection() throws Exception { + + ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(brokerService.getTransportConnectors().get(0).getPublishableConnectURI()); + Connection connection = connectionFactory.createConnection(); + connection.setClientID(CLIENT_ID); + connection.start(); + + String result = executeQuery("-QConnection=* --view ClientId"); + assertTrue("got client id", result.contains(CLIENT_ID)); + + result = executeQuery("--objname type=Broker,brokerName=*,connector=clientConnectors,connectorName=* -xQNetworkConnector=*"); + assertTrue("got named", result.contains(CONNECTOR_NAME)); + + result = executeQuery("-QConnector=*"); + assertTrue("got named", result.contains(CONNECTOR_NAME)); + } + + + @Test + public void testInvoke() throws Exception { + + String result = executeQuery("-QQueue=Q* --view Paused"); + assertTrue("got pause status", result.contains("Paused = false")); + + result = executeQuery("-QQueue=* --invoke pause"); + LOG.info("result of invoke: " + result); + assertTrue("invoked", result.contains("Q1")); + assertTrue("invoked", result.contains("Q2")); + + result = executeQuery("-QQueue=Q2 --view Paused"); + assertTrue("got pause status", result.contains("Paused = true")); + + result = executeQuery("-QQueue=Q2 --invoke resume"); + LOG.info("result of invoke: " + result); + assertTrue("invoked", result.contains("Q2")); + + result = executeQuery("-QQueue=Q2 --view Paused"); + assertTrue("pause status", result.contains("Paused = false")); + + result = executeQuery("-QQueue=Q1 --view Paused"); + assertTrue("pause status", result.contains("Paused = true")); + + // op with string param + result = executeQuery("-QQueue=Q2 --invoke sendTextMessage,hi"); + LOG.info("result of invoke: " + result); + assertTrue("invoked", result.contains("Q2")); + + result = executeQuery("-QQueue=Q2 --view EnqueueCount"); + assertTrue("enqueueCount", result.contains("EnqueueCount = 1")); + } + + private String executeQuery(String query) throws Exception { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(1024); + CommandContext context = new CommandContext(); + context.setFormatter(new CommandShellOutputFormatter(byteArrayOutputStream)); + + QueryCommand queryCommand = new QueryCommand(); + queryCommand.setJmxUseLocal(true); + queryCommand.setCommandContext(context); + + LinkedList args = new LinkedList<>(); + args.addAll(Arrays.asList(query.split(" "))); + queryCommand.execute(args); + + return byteArrayOutputStream.toString(); + } +}