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; 023import javax.management.MBeanServerInvocationHandler; 024import javax.management.ObjectInstance; 025import javax.management.ObjectName; 026 027import org.apache.activemq.broker.jmx.QueueViewMBean; 028import org.apache.activemq.console.util.JmxMBeansUtil; 029 030public class PurgeCommand extends AbstractJmxCommand { 031 032 protected String[] helpFile = new String[] { 033 "Task Usage: Main purge [browse-options] <destinations>", 034 "Description: Delete selected destination's messages that matches the message selector.", 035 "", 036 "Purge Options:", 037 " --msgsel <msgsel1,msglsel2> Add to the search list messages matched by the query similar to", 038 " the messages selector format.", 039 " --reset After the purge operation, reset the destination statistics.", 040 " --jmxurl <url> Set the JMX URL to connect to.", 041 " --pid <pid> Set the pid to connect to (only on Sun JVM).", 042 " --jmxuser <user> Set the JMX user used for authenticating.", 043 " --jmxpassword <password> Set the JMX password used for authenticating.", 044 " --jmxlocal Use the local JMX server instead of a remote one.", 045 " --version Display the version information.", 046 " -h,-?,--help Display the browse broker help information.", 047 "", 048 "Examples:", 049 " Main purge FOO.BAR", 050 " - Delete all the messages in queue FOO.BAR", 051 052 " Main purge --msgsel \"JMSMessageID='*:10',JMSPriority>5\" FOO.*", 053 " - Delete all the messages in the destinations that matches FOO.* and has a JMSMessageID in", 054 " the header field that matches the wildcard *:10, and has a JMSPriority field > 5 in the", 055 " queue FOO.BAR.", 056 " SLQ92 syntax is also supported.", 057 " * To use wildcard queries, the field must be a string and the query enclosed in ''", 058 " Use double quotes \"\" around the entire message selector string.", 059 "" 060 }; 061 062 private final List<String> queryAddObjects = new ArrayList<String>(10); 063 private final List<String> querySubObjects = new ArrayList<String>(10); 064 private boolean resetStatistics; 065 066 @Override 067 public String getName() { 068 return "purge"; 069 } 070 071 @Override 072 public String getOneLineDescription() { 073 return "Delete selected destination's messages that matches the message selector"; 074 } 075 076 /** 077 * Execute the purge command, which allows you to purge the messages in a 078 * given JMS destination 079 * 080 * @param tokens - command arguments 081 * @throws Exception 082 */ 083 @Override 084 protected void runTask(List<String> tokens) throws Exception { 085 try { 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 = convertToSQL92(queryAddObjects); 122 removed = proxy.removeMatchingMessages(sqlQuery); 123 context.printInfo("Removed: " + removed 124 + " messages for message selector " + sqlQuery); 125 126 if (resetStatistics) { 127 proxy.resetStatistics(); 128 } 129 } 130 } 131 } 132 } catch (Exception e) { 133 context.printException(new RuntimeException("Failed to execute purge task. Reason: " + e)); 134 throw new Exception(e); 135 } 136 } 137 138 139 /** 140 * Purge all the messages in the queue 141 * 142 * @param queue - ObjectName of the queue to purge 143 * @throws Exception 144 */ 145 public void purgeQueue(ObjectName queue) throws Exception { 146 context.printInfo("Purging all messages in queue: " + queue.getKeyProperty("destinationName")); 147 createJmxConnection().invoke(queue, "purge", new Object[] {}, new String[] {}); 148 if (resetStatistics) { 149 createJmxConnection().invoke(queue, "resetStatistics", new Object[] {}, new String[] {}); 150 } 151 } 152 153 /** 154 * Handle the --msgsel, --xmsgsel. 155 * 156 * @param token - option token to handle 157 * @param tokens - succeeding command arguments 158 * @throws Exception 159 */ 160 @Override 161 protected void handleOption(String token, List<String> tokens) throws Exception { 162 // If token is an additive message selector option 163 if (token.startsWith("--msgsel")) { 164 165 // If no message selector is specified, or next token is a new 166 // option 167 if (tokens.isEmpty() || tokens.get(0).startsWith("-")) { 168 context.printException(new IllegalArgumentException("Message selector not specified")); 169 return; 170 } 171 172 StringTokenizer queryTokens = new StringTokenizer(tokens.remove(0), COMMAND_OPTION_DELIMETER); 173 while (queryTokens.hasMoreTokens()) { 174 queryAddObjects.add(queryTokens.nextToken()); 175 } 176 } else if (token.startsWith("--xmsgsel")) { 177 // If token is a substractive message selector option 178 179 // If no message selector is specified, or next token is a new 180 // option 181 if (tokens.isEmpty() || tokens.get(0).startsWith("-")) { 182 context.printException(new IllegalArgumentException("Message selector not specified")); 183 return; 184 } 185 186 StringTokenizer queryTokens = new StringTokenizer(tokens.remove(0), COMMAND_OPTION_DELIMETER); 187 while (queryTokens.hasMoreTokens()) { 188 querySubObjects.add(queryTokens.nextToken()); 189 } 190 } else if (token.startsWith("--reset")) { 191 resetStatistics = true; 192 } else { 193 // Let super class handle unknown option 194 super.handleOption(token, tokens); 195 } 196 } 197 198 /** 199 * Converts the message selector as provided on command line 200 * argument to activem-admin into an SQL-92 conform string. 201 * E.g. 202 * "JMSMessageID='*:10',JMSPriority>5" 203 * gets converted into 204 * "(JMSMessageID='%:10') AND (JMSPriority>5)" 205 * 206 * @param tokens - List of message selector query parameters 207 * @return SQL-92 string of that query. 208 */ 209 public String convertToSQL92(List<String> tokens) { 210 StringBuilder selector = new StringBuilder(); 211 212 boolean isFirstToken = true; 213 for (Iterator i = tokens.iterator(); i.hasNext(); ) { 214 String token = i.next().toString(); 215 if (token.matches("^[^=]*='.*[\\*\\?].*'$")) { 216 token = token.replace('?', '_') 217 .replace('*', '%') 218 .replaceFirst("=", " LIKE "); 219 } 220 if (isFirstToken) { 221 isFirstToken = false; 222 } else { 223 selector.append(" AND "); 224 } 225 selector.append('(') 226 .append(token) 227 .append(')'); 228 } 229 return selector.toString(); 230 } 231 232 /** 233 * Print the help messages for the browse command 234 */ 235 @Override 236 protected void printHelp() { 237 context.printHelp(helpFile); 238 } 239 240}