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 // If there is no queue name specified, let's select all 086 if (tokens.isEmpty()) { 087 tokens.add("*"); 088 } 089 090 // Iterate through the queue names 091 for (Iterator<String> i = tokens.iterator(); i.hasNext(); ) { 092 List queueList = JmxMBeansUtil.queryMBeans(createJmxConnection(), "type=Broker,brokerName=*,destinationType=Queue,destinationName=" + i.next()); 093 094 for (Iterator j = queueList.iterator(); j.hasNext(); ) { 095 ObjectName queueName = ((ObjectInstance) j.next()).getObjectName(); 096 if (queryAddObjects.isEmpty()) { 097 purgeQueue(queueName); 098 } else { 099 100 QueueViewMBean proxy = MBeanServerInvocationHandler. 101 newProxyInstance(createJmxConnection(), 102 queueName, 103 QueueViewMBean.class, 104 true); 105 int removed = 0; 106 107 // AMQ-3404: We support two syntaxes for the message 108 // selector query: 109 // 1) AMQ specific: 110 // "JMSPriority>2,MyHeader='Foo'" 111 // 112 // 2) SQL-92 syntax: 113 // "(JMSPriority>2) AND (MyHeader='Foo')" 114 // 115 // If syntax style 1) is used, the comma separated 116 // criterias are broken into List<String> elements. 117 // We then need to construct the SQL-92 query out of 118 // this list. 119 120 String sqlQuery = convertToSQL92(queryAddObjects); 121 removed = proxy.removeMatchingMessages(sqlQuery); 122 context.printInfo("Removed: " + removed 123 + " messages for message selector " + sqlQuery); 124 125 if (resetStatistics) { 126 proxy.resetStatistics(); 127 } 128 } 129 } 130 } 131 } 132 133 134 /** 135 * Purge all the messages in the queue 136 * 137 * @param queue - ObjectName of the queue to purge 138 * @throws Exception 139 */ 140 public void purgeQueue(ObjectName queue) throws Exception { 141 context.printInfo("Purging all messages in queue: " + queue.getKeyProperty("destinationName")); 142 createJmxConnection().invoke(queue, "purge", new Object[] {}, new String[] {}); 143 if (resetStatistics) { 144 createJmxConnection().invoke(queue, "resetStatistics", new Object[] {}, new String[] {}); 145 } 146 } 147 148 /** 149 * Handle the --msgsel, --xmsgsel. 150 * 151 * @param token - option token to handle 152 * @param tokens - succeeding command arguments 153 * @throws Exception 154 */ 155 @Override 156 protected void handleOption(String token, List<String> tokens) throws Exception { 157 // If token is an additive message selector option 158 if (token.startsWith("--msgsel")) { 159 160 // If no message selector is specified, or next token is a new 161 // option 162 if (tokens.isEmpty() || tokens.get(0).startsWith("-")) { 163 context.printException(new IllegalArgumentException("Message selector not specified")); 164 return; 165 } 166 167 StringTokenizer queryTokens = new StringTokenizer(tokens.remove(0), COMMAND_OPTION_DELIMETER); 168 while (queryTokens.hasMoreTokens()) { 169 queryAddObjects.add(queryTokens.nextToken()); 170 } 171 } else if (token.startsWith("--xmsgsel")) { 172 // If token is a substractive message selector option 173 174 // If no message selector is specified, or next token is a new 175 // option 176 if (tokens.isEmpty() || tokens.get(0).startsWith("-")) { 177 context.printException(new IllegalArgumentException("Message selector not specified")); 178 return; 179 } 180 181 StringTokenizer queryTokens = new StringTokenizer(tokens.remove(0), COMMAND_OPTION_DELIMETER); 182 while (queryTokens.hasMoreTokens()) { 183 querySubObjects.add(queryTokens.nextToken()); 184 } 185 } else if (token.startsWith("--reset")) { 186 resetStatistics = true; 187 } else { 188 // Let super class handle unknown option 189 super.handleOption(token, tokens); 190 } 191 } 192 193 /** 194 * Converts the message selector as provided on command line 195 * argument to activem-admin into an SQL-92 conform string. 196 * E.g. 197 * "JMSMessageID='*:10',JMSPriority>5" 198 * gets converted into 199 * "(JMSMessageID='%:10') AND (JMSPriority>5)" 200 * 201 * @param tokens - List of message selector query parameters 202 * @return SQL-92 string of that query. 203 */ 204 public String convertToSQL92(List<String> tokens) { 205 StringBuilder selector = new StringBuilder(); 206 207 boolean isFirstToken = true; 208 for (Iterator i = tokens.iterator(); i.hasNext(); ) { 209 String token = i.next().toString(); 210 if (token.matches("^[^=]*='.*[\\*\\?].*'$")) { 211 token = token.replace('?', '_') 212 .replace('*', '%') 213 .replaceFirst("=", " LIKE "); 214 } 215 if (isFirstToken) { 216 isFirstToken = false; 217 } else { 218 selector.append(" AND "); 219 } 220 selector.append('(') 221 .append(token) 222 .append(')'); 223 } 224 return selector.toString(); 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}