001/**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.activemq.console.command;
018
019import javax.management.ObjectName;
020import org.apache.activemq.console.util.JmxMBeansUtil;
021
022import java.util.*;
023
024public class QueryCommand extends AbstractJmxCommand {
025    // Predefined type=identifier query
026    private static final Properties PREDEFINED_OBJNAME_QUERY = new Properties();
027
028    static {
029        PREDEFINED_OBJNAME_QUERY.setProperty("Broker", "brokerName=%1");
030        PREDEFINED_OBJNAME_QUERY.setProperty("Connection", "connector=clientConnectors,connectionViewType=*,connectionName=%1,*");
031        PREDEFINED_OBJNAME_QUERY.setProperty("Connector", "connector=clientConnectors,connectorName=%1");
032        PREDEFINED_OBJNAME_QUERY.setProperty("NetworkConnector", "connector=networkConnectors,networkConnectorName=%1");
033        PREDEFINED_OBJNAME_QUERY.setProperty("Queue", "destinationType=Queue,destinationName=%1");
034        PREDEFINED_OBJNAME_QUERY.setProperty("Topic", "destinationType=Topic,destinationName=%1");
035    };
036
037    protected String[] helpFile = new String[] {
038        "Task Usage: Main query [query-options]",
039        "Description: Display selected broker component's attributes and statistics.",
040        "",
041        "Query Options:",
042        "    -Q<type>=<name>               Add to the search list the specific object type matched",
043        "                                  by the defined object identifier.",
044        "    -xQ<type>=<name>              Remove from the search list the specific object type",
045        "                                  matched by the object identifier.",
046        "    --objname <query>             Add to the search list objects matched by the query similar",
047        "                                  to the JMX object name format.",
048        "    --xobjname <query>            Remove from the search list objects matched by the query",
049        "                                  similar to the JMX object name format.",
050        "    --view <attr1>,<attr2>,...    Select the specific attribute of the object to view.",
051        "                                  By default all attributes will be displayed.",
052        "    --invoke <operation>          Specify the operation to invoke on matching objects",
053        "    --jmxurl <url>                Set the JMX URL to connect to.",
054        "    --pid <pid>                   Set the pid to connect to (only on Sun JVM).",            
055        "    --jmxuser <user>              Set the JMX user used for authenticating.",
056        "    --jmxpassword <password>      Set the JMX password used for authenticating.",
057        "    --jmxlocal                    Use the local JMX server instead of a remote one.",
058        "    --version                     Display the version information.",
059        "    -h,-?,--help                  Display the query broker help information.",
060        "", "Examples:",
061        "    query",
062        "        - Print all the attributes of all registered objects queues, topics, connections, etc).",
063        "",
064        "    query -QQueue=TEST.FOO",
065        "        - Print all the attributes of the queue with destination name TEST.FOO.",
066        "",
067        "    query -QTopic=*",
068        "        - Print all the attributes of all registered topics.",
069        "",
070        "    query --view EnqueueCount,DequeueCount", 
071        "        - Print the attributes EnqueueCount and DequeueCount of all registered objects.",
072        "",
073        "    query -QTopic=* --view EnqueueCount,DequeueCount",
074        "        - Print the attributes EnqueueCount and DequeueCount of all registered topics.",
075        "",
076        "    query -QTopic=* -QQueue=* --view EnqueueCount,DequeueCount",
077        "        - Print the attributes EnqueueCount and DequeueCount of all registered topics and",
078        "          queues.",
079        "",
080        "    query -QTopic=* -xQTopic=ActiveMQ.Advisory.*", 
081        "        - Print all attributes of all topics except those that has a name that begins",
082        "          with \"ActiveMQ.Advisory\".",
083        "",
084        "    query --objname type=Broker,brokerName=*,connector=clientConnectors,connectorName=* -xQNetworkConnector=*",
085        "        - Print all attributes of all connectors, connections excluding network connectors",
086        "          that belongs to the broker that begins with local.", 
087        "", 
088        "    query -QQueue=* -xQQueue=????", 
089        "        - Print all attributes of all queues except those that are 4 letters long.",
090        "",
091        "    query -QQueue=* --invoke pause",
092        "        - Pause all queues.",
093        "",
094
095    };
096
097    private final List<String> queryAddObjects = new ArrayList<String>(10);
098    private final List<String> querySubObjects = new ArrayList<String>(10);
099    private final Set queryViews = new LinkedHashSet();
100    private final List<String> opAndParams = new ArrayList<String>(10);
101
102    @Override
103    public String getName() {
104        return "query";
105    }
106
107    @Override
108    public String getOneLineDescription() {
109        return "Display selected broker component's attributes and statistics.";
110    }
111
112    /**
113     * Queries the mbeans registered in the specified JMX context
114     * 
115     * @param tokens - command arguments
116     * @throws Exception
117     */
118    protected void runTask(List<String> tokens) throws Exception {
119        try {
120            // Query for the mbeans to add
121            Map<Object,List> addMBeans = JmxMBeansUtil.queryMBeansAsMap(createJmxConnection(), queryAddObjects, queryViews);
122            // Query for the mbeans to sub
123            if (querySubObjects.size() > 0) {
124                Map<Object,List> subMBeans = JmxMBeansUtil.queryMBeansAsMap(createJmxConnection(), querySubObjects, queryViews);
125                addMBeans.keySet().removeAll(subMBeans.keySet());
126            }
127
128            if (opAndParams.isEmpty()) {
129                context.printMBean(JmxMBeansUtil.filterMBeansView(new ArrayList(addMBeans.values()), queryViews));
130            } else {
131                context.print(doInvoke(addMBeans.keySet(), opAndParams));
132            }
133        } catch (Exception e) {
134            context.printException(new RuntimeException("Failed to execute query task. Reason: " + e));
135            throw new Exception(e);
136        }
137    }
138
139    private Collection doInvoke(Set<Object> mBeans, List<String> opAndParams) throws Exception {
140        LinkedList<String> results = new LinkedList<>();
141        for (Object objectName : mBeans) {
142            Object result = createJmxConnection().invoke((ObjectName) objectName, opAndParams.get(0),
143                    params(opAndParams), stringSignature(opAndParams));
144            results.add("[" + objectName + "]." + opAndParams.get(0) + " = " + result);
145        }
146        return results;
147    }
148
149    private Object[] params(List<String> opAndParams) {
150        if (opAndParams.size() > 1) {
151            return opAndParams.subList(1, opAndParams.size()).toArray();
152        } else {
153            return null;
154        }
155    }
156
157    private String[] stringSignature(List<String> opAndParams) {
158        if (opAndParams.size() > 1) {
159            String[] sig = new String[opAndParams.size() - 1];
160            Arrays.fill(sig, String.class.getName());
161            return sig;
162        } else {
163            return null;
164        }
165    }
166
167
168    /**
169     * Handle the -Q, -xQ, --objname, --xobjname, --view --invoke options.
170     * 
171     * @param token - option token to handle
172     * @param tokens - succeeding command arguments
173     * @throws Exception
174     */
175    protected void handleOption(String token, List<String> tokens) throws Exception {
176        // If token is a additive predefined query define option
177        if (token.startsWith("-Q")) {
178            String key = token.substring(2);
179            String value = "";
180            int pos = key.indexOf("=");
181            if (pos >= 0) {
182                value = key.substring(pos + 1);
183                key = key.substring(0, pos);
184            }
185
186            // If additive query
187            String predefQuery = PREDEFINED_OBJNAME_QUERY.getProperty(key);
188            if (predefQuery == null) {
189                context.printException(new IllegalArgumentException("Unknown query object type: " + key));
190                return;
191            }
192            String queryStr = JmxMBeansUtil.createQueryString(predefQuery, value);
193            StringTokenizer queryTokens = new StringTokenizer(queryStr, COMMAND_OPTION_DELIMETER);
194            while (queryTokens.hasMoreTokens()) {
195                queryAddObjects.add(queryTokens.nextToken());
196            }
197            normaliseObjectName(queryAddObjects);
198        } else if (token.startsWith("-xQ")) {
199            // If token is a substractive predefined query define option
200            String key = token.substring(3);
201            String value = "";
202            int pos = key.indexOf("=");
203            if (pos >= 0) {
204                value = key.substring(pos + 1);
205                key = key.substring(0, pos);
206            }
207
208            // If subtractive query
209            String predefQuery = PREDEFINED_OBJNAME_QUERY.getProperty(key);
210            if (predefQuery == null) {
211                context.printException(new IllegalArgumentException("Unknown query object type: " + key));
212                return;
213            }
214            String queryStr = JmxMBeansUtil.createQueryString(predefQuery, value);
215            StringTokenizer queryTokens = new StringTokenizer(queryStr, COMMAND_OPTION_DELIMETER);
216            while (queryTokens.hasMoreTokens()) {
217                querySubObjects.add(queryTokens.nextToken());
218            }
219            normaliseObjectName(querySubObjects);
220        } else if (token.startsWith("--objname")) {
221            // If token is an additive object name query option
222
223            // If no object name query is specified, or next token is a new
224            // option
225            if (tokens.isEmpty() || ((String)tokens.get(0)).startsWith("-")) {
226                context.printException(new IllegalArgumentException("Object name query not specified"));
227                return;
228            }
229
230            StringTokenizer queryTokens = new StringTokenizer((String)tokens.remove(0), COMMAND_OPTION_DELIMETER);
231            while (queryTokens.hasMoreTokens()) {
232                queryAddObjects.add(queryTokens.nextToken());
233            }
234        } else if (token.startsWith("--xobjname")) {
235            // If token is a substractive object name query option
236
237            // If no object name query is specified, or next token is a new
238            // option
239            if (tokens.isEmpty() || ((String)tokens.get(0)).startsWith("-")) {
240                context.printException(new IllegalArgumentException("Object name query not specified"));
241                return;
242            }
243
244            StringTokenizer queryTokens = new StringTokenizer((String)tokens.remove(0), COMMAND_OPTION_DELIMETER);
245            while (queryTokens.hasMoreTokens()) {
246                querySubObjects.add(queryTokens.nextToken());
247            }
248        } else if (token.startsWith("--view")) {
249            // If token is a view option
250
251            // If no view specified, or next token is a new option
252            if (tokens.isEmpty() || ((String)tokens.get(0)).startsWith("-")) {
253                context.printException(new IllegalArgumentException("Attributes to view not specified"));
254                return;
255            }
256
257            // Add the attributes to view
258            Enumeration viewTokens = new StringTokenizer((String)tokens.remove(0), COMMAND_OPTION_DELIMETER);
259            while (viewTokens.hasMoreElements()) {
260                queryViews.add(viewTokens.nextElement());
261            }
262        } else if (token.startsWith("--invoke")) {
263
264            if (tokens.isEmpty() || ((String)tokens.get(0)).startsWith("-")) {
265                context.printException(new IllegalArgumentException("operation to invoke is not specified"));
266                return;
267            }
268
269            // add op and params
270            Enumeration viewTokens = new StringTokenizer((String)tokens.remove(0), COMMAND_OPTION_DELIMETER);
271            while (viewTokens.hasMoreElements()) {
272                opAndParams.add((String)viewTokens.nextElement());
273            }
274
275        } else {
276            // Let super class handle unknown option
277            super.handleOption(token, tokens);
278        }
279    }
280
281    private void normaliseObjectName(List<String> queryAddObjects) {
282        ensurePresent(queryAddObjects, "type", "Broker");
283        ensurePresent(queryAddObjects, "brokerName", "*");
284
285        // -QQueue && -QTopic
286        ensureUnique(queryAddObjects, "destinationType", "?????");
287        ensureUnique(queryAddObjects, "destinationName", "*");
288    }
289
290    private void ensurePresent(List<String> queryAddObjects, String id, String wildcard) {
291        List<String> matches = findMatchingKeys(queryAddObjects, id);
292        if (matches.size() == 0) {
293            queryAddObjects.add(id + "=" + wildcard);
294        }
295    }
296
297    private void ensureUnique(List<String> queryAddObjects, String id, String wildcard) {
298        List<String> matches = findMatchingKeys(queryAddObjects, id);
299        if (matches.size() > 1) {
300            queryAddObjects.removeAll(matches);
301            queryAddObjects.add(id + "=" + wildcard);
302        }
303    }
304
305    private List<String> findMatchingKeys(List<String> queryAddObjects, String id) {
306        List<String> matches = new LinkedList<>();
307        for (String prop : queryAddObjects) {
308            String[] keyValue = prop.split("=");
309            if (keyValue.length == 2 && keyValue[0].equals(id)) {
310                matches.add(prop);
311            }
312        }
313        return matches;
314    }
315
316    /**
317     * Print the help messages for the browse command
318     */
319    protected void printHelp() {
320        context.printHelp(helpFile);
321    }
322
323}