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}