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        // Query for the mbeans to add
120        Map<Object, List> addMBeans = JmxMBeansUtil.queryMBeansAsMap(createJmxConnection(), queryAddObjects, queryViews);
121        // Query for the mbeans to sub
122        if (querySubObjects.size() > 0) {
123            Map<Object, List> subMBeans = JmxMBeansUtil.queryMBeansAsMap(createJmxConnection(), querySubObjects, queryViews);
124            addMBeans.keySet().removeAll(subMBeans.keySet());
125        }
126
127        if (opAndParams.isEmpty()) {
128            context.printMBean(JmxMBeansUtil.filterMBeansView(new ArrayList(addMBeans.values()), queryViews));
129        } else {
130            context.print(doInvoke(addMBeans.keySet(), opAndParams));
131        }
132    }
133
134    private Collection doInvoke(Set<Object> mBeans, List<String> opAndParams) throws Exception {
135        LinkedList<String> results = new LinkedList<>();
136        for (Object objectName : mBeans) {
137            Object result = createJmxConnection().invoke((ObjectName) objectName, opAndParams.get(0),
138                    params(opAndParams), stringSignature(opAndParams));
139            results.add("[" + objectName + "]." + opAndParams.get(0) + " = " + result);
140        }
141        return results;
142    }
143
144    private Object[] params(List<String> opAndParams) {
145        if (opAndParams.size() > 1) {
146            return opAndParams.subList(1, opAndParams.size()).toArray();
147        } else {
148            return null;
149        }
150    }
151
152    private String[] stringSignature(List<String> opAndParams) {
153        if (opAndParams.size() > 1) {
154            String[] sig = new String[opAndParams.size() - 1];
155            Arrays.fill(sig, String.class.getName());
156            return sig;
157        } else {
158            return null;
159        }
160    }
161
162
163    /**
164     * Handle the -Q, -xQ, --objname, --xobjname, --view --invoke options.
165     * 
166     * @param token - option token to handle
167     * @param tokens - succeeding command arguments
168     * @throws Exception
169     */
170    protected void handleOption(String token, List<String> tokens) throws Exception {
171        // If token is a additive predefined query define option
172        if (token.startsWith("-Q")) {
173            String key = token.substring(2);
174            String value = "";
175            int pos = key.indexOf("=");
176            if (pos >= 0) {
177                value = key.substring(pos + 1);
178                key = key.substring(0, pos);
179            }
180
181            // If additive query
182            String predefQuery = PREDEFINED_OBJNAME_QUERY.getProperty(key);
183            if (predefQuery == null) {
184                context.printException(new IllegalArgumentException("Unknown query object type: " + key));
185                return;
186            }
187            String queryStr = JmxMBeansUtil.createQueryString(predefQuery, value);
188            StringTokenizer queryTokens = new StringTokenizer(queryStr, COMMAND_OPTION_DELIMETER);
189            while (queryTokens.hasMoreTokens()) {
190                queryAddObjects.add(queryTokens.nextToken());
191            }
192            normaliseObjectName(queryAddObjects);
193        } else if (token.startsWith("-xQ")) {
194            // If token is a substractive predefined query define option
195            String key = token.substring(3);
196            String value = "";
197            int pos = key.indexOf("=");
198            if (pos >= 0) {
199                value = key.substring(pos + 1);
200                key = key.substring(0, pos);
201            }
202
203            // If subtractive query
204            String predefQuery = PREDEFINED_OBJNAME_QUERY.getProperty(key);
205            if (predefQuery == null) {
206                context.printException(new IllegalArgumentException("Unknown query object type: " + key));
207                return;
208            }
209            String queryStr = JmxMBeansUtil.createQueryString(predefQuery, value);
210            StringTokenizer queryTokens = new StringTokenizer(queryStr, COMMAND_OPTION_DELIMETER);
211            while (queryTokens.hasMoreTokens()) {
212                querySubObjects.add(queryTokens.nextToken());
213            }
214            normaliseObjectName(querySubObjects);
215        } else if (token.startsWith("--objname")) {
216            // If token is an additive object name query option
217
218            // If no object name query is specified, or next token is a new
219            // option
220            if (tokens.isEmpty() || ((String)tokens.get(0)).startsWith("-")) {
221                context.printException(new IllegalArgumentException("Object name query not specified"));
222                return;
223            }
224
225            StringTokenizer queryTokens = new StringTokenizer((String)tokens.remove(0), COMMAND_OPTION_DELIMETER);
226            while (queryTokens.hasMoreTokens()) {
227                queryAddObjects.add(queryTokens.nextToken());
228            }
229        } else if (token.startsWith("--xobjname")) {
230            // If token is a substractive object name query option
231
232            // If no object name query is specified, or next token is a new
233            // option
234            if (tokens.isEmpty() || ((String)tokens.get(0)).startsWith("-")) {
235                context.printException(new IllegalArgumentException("Object name query not specified"));
236                return;
237            }
238
239            StringTokenizer queryTokens = new StringTokenizer((String)tokens.remove(0), COMMAND_OPTION_DELIMETER);
240            while (queryTokens.hasMoreTokens()) {
241                querySubObjects.add(queryTokens.nextToken());
242            }
243        } else if (token.startsWith("--view")) {
244            // If token is a view option
245
246            // If no view specified, or next token is a new option
247            if (tokens.isEmpty() || ((String)tokens.get(0)).startsWith("-")) {
248                context.printException(new IllegalArgumentException("Attributes to view not specified"));
249                return;
250            }
251
252            // Add the attributes to view
253            Enumeration viewTokens = new StringTokenizer((String)tokens.remove(0), COMMAND_OPTION_DELIMETER);
254            while (viewTokens.hasMoreElements()) {
255                queryViews.add(viewTokens.nextElement());
256            }
257        } else if (token.startsWith("--invoke")) {
258
259            if (tokens.isEmpty() || ((String)tokens.get(0)).startsWith("-")) {
260                context.printException(new IllegalArgumentException("operation to invoke is not specified"));
261                return;
262            }
263
264            // add op and params
265            Enumeration viewTokens = new StringTokenizer((String)tokens.remove(0), COMMAND_OPTION_DELIMETER);
266            while (viewTokens.hasMoreElements()) {
267                opAndParams.add((String)viewTokens.nextElement());
268            }
269
270        } else {
271            // Let super class handle unknown option
272            super.handleOption(token, tokens);
273        }
274    }
275
276    private void normaliseObjectName(List<String> queryAddObjects) {
277        ensurePresent(queryAddObjects, "type", "Broker");
278        ensurePresent(queryAddObjects, "brokerName", "*");
279
280        // -QQueue && -QTopic
281        ensureUnique(queryAddObjects, "destinationType", "?????");
282        ensureUnique(queryAddObjects, "destinationName", "*");
283    }
284
285    private void ensurePresent(List<String> queryAddObjects, String id, String wildcard) {
286        List<String> matches = findMatchingKeys(queryAddObjects, id);
287        if (matches.size() == 0) {
288            queryAddObjects.add(id + "=" + wildcard);
289        }
290    }
291
292    private void ensureUnique(List<String> queryAddObjects, String id, String wildcard) {
293        List<String> matches = findMatchingKeys(queryAddObjects, id);
294        if (matches.size() > 1) {
295            queryAddObjects.removeAll(matches);
296            queryAddObjects.add(id + "=" + wildcard);
297        }
298    }
299
300    private List<String> findMatchingKeys(List<String> queryAddObjects, String id) {
301        List<String> matches = new LinkedList<>();
302        for (String prop : queryAddObjects) {
303            String[] keyValue = prop.split("=");
304            if (keyValue.length == 2 && keyValue[0].equals(id)) {
305                matches.add(prop);
306            }
307        }
308        return matches;
309    }
310
311    /**
312     * Print the help messages for the browse command
313     */
314    protected void printHelp() {
315        context.printHelp(helpFile);
316    }
317
318}