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 java.util.ArrayList;
020import java.util.Iterator;
021import java.util.List;
022import java.util.StringTokenizer;
023
024import javax.management.MBeanServerInvocationHandler;
025import javax.management.ObjectInstance;
026import javax.management.ObjectName;
027
028import org.apache.activemq.broker.jmx.QueueViewMBean;
029import org.apache.activemq.console.util.JmxMBeansUtil;
030
031public class PurgeCommand extends AbstractJmxCommand {
032
033    protected String[] helpFile = new String[] {
034        "Task Usage: Main purge [browse-options] <destinations>",
035        "Description: Delete selected destination's messages that matches the message selector.",
036        "",
037        "Purge Options:",
038        "    --msgsel <msgsel1,msglsel2>   Add to the search list messages matched by the query similar to",
039        "                                  the messages selector format.",
040        "    --reset                       After the purge operation, reset the destination statistics.",
041        "    --jmxurl <url>                Set the JMX URL to connect to.",
042        "    --pid <pid>                   Set the pid to connect to (only on Sun JVM).",
043        "    --jmxuser <user>              Set the JMX user used for authenticating.",
044        "    --jmxpassword <password>      Set the JMX password used for authenticating.",
045        "    --jmxlocal                    Use the local JMX server instead of a remote one.",
046        "    --version                     Display the version information.",
047        "    -h,-?,--help                  Display the browse broker help information.",
048        "",
049        "Examples:",
050        "    Main purge FOO.BAR",
051        "        - Delete all the messages in queue FOO.BAR",
052
053        "    Main purge --msgsel \"JMSMessageID='*:10',JMSPriority>5\" FOO.*",
054        "        - Delete all the messages in the destinations that matches FOO.* and has a JMSMessageID in",
055        "          the header field that matches the wildcard *:10, and has a JMSPriority field > 5 in the",
056        "          queue FOO.BAR.",
057        "          SLQ92 syntax is also supported.",
058        "        * To use wildcard queries, the field must be a string and the query enclosed in ''",
059        "          Use double quotes \"\" around the entire message selector string.",
060        ""
061    };
062
063    private final List<String> queryAddObjects = new ArrayList<String>(10);
064    private final List<String> querySubObjects = new ArrayList<String>(10);
065    private boolean resetStatistics;
066
067    @Override
068    public String getName() {
069        return "purge";
070    }
071
072    @Override
073    public String getOneLineDescription() {
074        return "Delete selected destination's messages that matches the message selector";
075    }
076
077    /**
078     * Execute the purge command, which allows you to purge the messages in a
079     * given JMS destination
080     *
081     * @param tokens - command arguments
082     * @throws Exception
083     */
084    @Override
085    protected void runTask(List<String> tokens) throws Exception {
086        // If there is no queue name specified, let's select all
087        if (tokens.isEmpty()) {
088            tokens.add("*");
089        }
090
091        // Iterate through the queue names
092        for (Iterator<String> i = tokens.iterator(); i.hasNext(); ) {
093            List queueList = JmxMBeansUtil.queryMBeans(createJmxConnection(), "type=Broker,brokerName=*,destinationType=Queue,destinationName=" + i.next());
094
095            for (Iterator j = queueList.iterator(); j.hasNext(); ) {
096                ObjectName queueName = ((ObjectInstance) j.next()).getObjectName();
097                if (queryAddObjects.isEmpty()) {
098                    purgeQueue(queueName);
099                } else {
100
101                    QueueViewMBean proxy = MBeanServerInvocationHandler.
102                            newProxyInstance(createJmxConnection(),
103                                    queueName,
104                                    QueueViewMBean.class,
105                                    true);
106                    int removed = 0;
107
108                    // AMQ-3404: We support two syntaxes for the message
109                    // selector query:
110                    // 1) AMQ specific:
111                    //    "JMSPriority>2,MyHeader='Foo'"
112                    //
113                    // 2) SQL-92 syntax:
114                    //    "(JMSPriority>2) AND (MyHeader='Foo')"
115                    //
116                    // If syntax style 1) is used, the comma separated
117                    // criterias are broken into List<String> elements.
118                    // We then need to construct the SQL-92 query out of
119                    // this list.
120
121                    String sqlQuery = null;
122                    if (queryAddObjects.size() > 1) {
123                        sqlQuery = convertToSQL92(queryAddObjects);
124                    } else {
125                        sqlQuery = queryAddObjects.get(0);
126                    }
127                    removed = proxy.removeMatchingMessages(sqlQuery);
128                    context.printInfo("Removed: " + removed
129                            + " messages for message selector " + sqlQuery.toString());
130
131                    if (resetStatistics) {
132                        proxy.resetStatistics();
133                    }
134                }
135            }
136        }
137    }
138
139
140    /**
141     * Purge all the messages in the queue
142     *
143     * @param queue - ObjectName of the queue to purge
144     * @throws Exception
145     */
146    public void purgeQueue(ObjectName queue) throws Exception {
147        context.printInfo("Purging all messages in queue: " + queue.getKeyProperty("destinationName"));
148        createJmxConnection().invoke(queue, "purge", new Object[] {}, new String[] {});
149        if (resetStatistics) {
150            createJmxConnection().invoke(queue, "resetStatistics", new Object[] {}, new String[] {});
151        }
152    }
153
154    /**
155     * Handle the --msgsel, --xmsgsel.
156     *
157     * @param token - option token to handle
158     * @param tokens - succeeding command arguments
159     * @throws Exception
160     */
161    @Override
162    protected void handleOption(String token, List<String> tokens) throws Exception {
163        // If token is an additive message selector option
164        if (token.startsWith("--msgsel")) {
165
166            // If no message selector is specified, or next token is a new
167            // option
168            if (tokens.isEmpty() || tokens.get(0).startsWith("-")) {
169                context.printException(new IllegalArgumentException("Message selector not specified"));
170                return;
171            }
172
173            StringTokenizer queryTokens = new StringTokenizer(tokens.remove(0), COMMAND_OPTION_DELIMETER);
174            while (queryTokens.hasMoreTokens()) {
175                queryAddObjects.add(queryTokens.nextToken());
176            }
177        } else if (token.startsWith("--xmsgsel")) {
178            // If token is a substractive message selector option
179
180            // If no message selector is specified, or next token is a new
181            // option
182            if (tokens.isEmpty() || tokens.get(0).startsWith("-")) {
183                context.printException(new IllegalArgumentException("Message selector not specified"));
184                return;
185            }
186
187            StringTokenizer queryTokens = new StringTokenizer(tokens.remove(0), COMMAND_OPTION_DELIMETER);
188            while (queryTokens.hasMoreTokens()) {
189                querySubObjects.add(queryTokens.nextToken());
190            }
191        } else if (token.startsWith("--reset")) {
192            resetStatistics = true;
193        } else {
194            // Let super class handle unknown option
195            super.handleOption(token, tokens);
196        }
197    }
198
199    /**
200     * Converts the message selector as provided on command line
201     * argument to activem-admin into an SQL-92 conform string.
202     * E.g.
203     *   "JMSMessageID='*:10',JMSPriority>5"
204     * gets converted into
205     *   "(JMSMessageID='%:10') AND (JMSPriority>5)"
206     *
207     * @param tokens - List of message selector query parameters
208     * @return SQL-92 string of that query.
209     */
210    public String convertToSQL92(List<String> tokens) {
211        String selector = "";
212
213        // Convert to message selector
214        for (Iterator i = tokens.iterator(); i.hasNext(); ) {
215            selector = selector + "(" + i.next().toString() + ") AND ";
216        }
217
218        // Remove last AND and replace '*' with '%'
219        if (!selector.equals("")) {
220            selector = selector.substring(0, selector.length() - 5);
221            selector = selector.replace('*', '%');
222        }
223        return selector;
224    }
225
226
227    /**
228     * Print the help messages for the browse command
229     */
230    @Override
231    protected void printHelp() {
232        context.printHelp(helpFile);
233    }
234
235}