001/** 002 * 003 * Copyright 2005-2007 Jive Software. 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * 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.jivesoftware.smackx.commands; 018 019import java.util.ArrayList; 020import java.util.List; 021import java.util.Set; 022 023import org.jivesoftware.smack.SmackException.NoResponseException; 024import org.jivesoftware.smack.SmackException.NotConnectedException; 025import org.jivesoftware.smack.XMPPException.XMPPErrorException; 026import org.jivesoftware.smack.packet.StanzaError; 027import org.jivesoftware.smack.util.StringUtils; 028 029import org.jivesoftware.smackx.commands.packet.AdHocCommandData; 030import org.jivesoftware.smackx.commands.packet.AdHocCommandData.Action; 031import org.jivesoftware.smackx.commands.packet.AdHocCommandData.AllowedAction; 032import org.jivesoftware.smackx.commands.packet.AdHocCommandData.Status; 033 034/** 035 * An ad-hoc command is responsible for executing the provided service and 036 * storing the result of the execution. Each new request will create a new 037 * instance of the command, allowing information related to executions to be 038 * stored in it. For example suppose that a command that retrieves the list of 039 * users on a server is implemented. When the command is executed it gets that 040 * list and the result is stored as a form in the command instance, i.e. the 041 * <code>getForm</code> method retrieves a form with all the users. 042 * <p> 043 * Each command has a <code>node</code> that should be unique within a given JID. 044 * </p> 045 * <p> 046 * Commands may have zero or more stages. Each stage is usually used for 047 * gathering information required for the command execution. Users are able to 048 * move forward or backward across the different stages. Commands may not be 049 * cancelled while they are being executed. However, users may request the 050 * "cancel" action when submitting a stage response indicating that the command 051 * execution should be aborted. Thus, releasing any collected information. 052 * Commands that require user interaction (i.e. have more than one stage) will 053 * have to provide the data forms the user must complete in each stage and the 054 * allowed actions the user might perform during each stage (e.g. go to the 055 * previous stage or go to the next stage). 056 * </p> 057 * All the actions may throw an XMPPException if there is a problem executing 058 * them. The <code>XMPPError</code> of that exception may have some specific 059 * information about the problem. The possible extensions are: 060 * <ul> 061 * <li><i>malformed-action</i>. Extension of a <i>bad-request</i> error.</li> 062 * <li><i>bad-action</i>. Extension of a <i>bad-request</i> error.</li> 063 * <li><i>bad-locale</i>. Extension of a <i>bad-request</i> error.</li> 064 * <li><i>bad-payload</i>. Extension of a <i>bad-request</i> error.</li> 065 * <li><i>bad-sessionid</i>. Extension of a <i>bad-request</i> error.</li> 066 * <li><i>session-expired</i>. Extension of a <i>not-allowed</i> error.</li> 067 * </ul> 068 * <p> 069 * See the <code>SpecificErrorCondition</code> class for detailed description 070 * of each one. 071 * </p> 072 * Use the <code>getSpecificErrorConditionFrom</code> to obtain the specific 073 * information from an <code>XMPPError</code>. 074 * 075 * @author Gabriel Guardincerri 076 * @author Florian Schmaus 077 * 078 */ 079public abstract class AbstractAdHocCommand { 080 private final List<AdHocCommandData> requests = new ArrayList<>(); 081 private final List<AdHocCommandResult> results = new ArrayList<>(); 082 083 private final String node; 084 085 private final String name; 086 087 /** 088 * The session ID of this execution. 089 */ 090 private String sessionId; 091 092 protected AbstractAdHocCommand(String node, String name) { 093 this.node = StringUtils.requireNotNullNorEmpty(node, "Ad-Hoc command node must be given"); 094 this.name = name; 095 } 096 097 protected AbstractAdHocCommand(String node) { 098 this(node, null); 099 } 100 101 void addRequest(AdHocCommandData request) { 102 requests.add(request); 103 } 104 105 void addResult(AdHocCommandResult result) { 106 results.add(result); 107 } 108 109 /** 110 * Returns the specific condition of the <code>error</code> or <code>null</code> if the 111 * error doesn't have any. 112 * 113 * @param error the error the get the specific condition from. 114 * @return the specific condition of this error, or null if it doesn't have 115 * any. 116 */ 117 public static SpecificErrorCondition getSpecificErrorCondition(StanzaError error) { 118 // This method is implemented to provide an easy way of getting a packet 119 // extension of the XMPPError. 120 for (SpecificErrorCondition condition : SpecificErrorCondition.values()) { 121 if (error.getExtension(condition.toString(), 122 AdHocCommandData.SpecificError.namespace) != null) { 123 return condition; 124 } 125 } 126 return null; 127 } 128 129 /** 130 * Returns the human readable name of the command. 131 * 132 * @return the human readable name of the command 133 */ 134 public String getName() { 135 return name; 136 } 137 138 /** 139 * Returns the unique identifier of the command. It is unique for the 140 * <code>OwnerJID</code>. 141 * 142 * @return the unique identifier of the command. 143 */ 144 public String getNode() { 145 return node; 146 } 147 148 public String getSessionId() { 149 return sessionId; 150 } 151 152 protected void setSessionId(String sessionId) { 153 assert this.sessionId == null || this.sessionId.equals(sessionId); 154 this.sessionId = StringUtils.requireNotNullNorEmpty(sessionId, "Must provide a session ID"); 155 } 156 157 public AdHocCommandData getLastRequest() { 158 if (requests.isEmpty()) return null; 159 return requests.get(requests.size() - 1); 160 } 161 162 public AdHocCommandResult getLastResult() { 163 if (results.isEmpty()) return null; 164 return results.get(results.size() - 1); 165 } 166 167 /** 168 * Returns the notes that the command has at the current stage. 169 * 170 * @return a list of notes. 171 */ 172 public List<AdHocCommandNote> getNotes() { 173 AdHocCommandResult result = getLastResult(); 174 if (result == null) return null; 175 176 return result.getResponse().getNotes(); 177 } 178 179 /** 180 * Cancels the execution of the command. This can be invoked on any stage of 181 * the execution. If there is a problem executing the command it throws an 182 * XMPPException. 183 * 184 * @throws NoResponseException if there was no response from the remote entity. 185 * @throws XMPPErrorException if there is a problem executing the command. 186 * @throws NotConnectedException if the XMPP connection is not connected. 187 * @throws InterruptedException if the calling thread was interrupted. 188 */ 189 public abstract void cancel() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException; 190 191 /** 192 * Returns a collection with the allowed actions based on the current stage. 193 * Possible actions are: {@link AllowedAction#prev prev}, {@link AllowedAction#next next} and 194 * {@link AllowedAction#complete complete}. This method will be only invoked for commands that 195 * have one or more stages. 196 * 197 * @return a collection with the allowed actions based on the current stage 198 * as defined in the SessionData. 199 */ 200 public final Set<AllowedAction> getActions() { 201 AdHocCommandResult result = getLastResult(); 202 if (result == null) return null; 203 204 return result.getResponse().getActions(); 205 } 206 207 /** 208 * Returns the action available for the current stage which is 209 * considered the equivalent to "execute". When the requester sends his 210 * reply, if no action was defined in the command then the action will be 211 * assumed "execute" thus assuming the action returned by this method. This 212 * method will never be invoked for commands that have no stages. 213 * 214 * @return the action available for the current stage which is considered 215 * the equivalent to "execute". 216 */ 217 protected AllowedAction getExecuteAction() { 218 AdHocCommandResult result = getLastResult(); 219 if (result == null) return null; 220 221 return result.getResponse().getExecuteAction(); 222 } 223 224 /** 225 * Returns the status of the current stage. 226 * 227 * @return the current status. 228 */ 229 public Status getStatus() { 230 AdHocCommandResult result = getLastResult(); 231 if (result == null) return null; 232 233 return result.getResponse().getStatus(); 234 } 235 236 /** 237 * Check if this command has been completed successfully. 238 * 239 * @return <code>true</code> if this command is completed. 240 * @since 4.2 241 */ 242 public boolean isCompleted() { 243 return getStatus() == AdHocCommandData.Status.completed; 244 } 245 246 /** 247 * Returns true if the <code>action</code> is available in the current stage. 248 * The {@link Action#cancel cancel} action is always allowed. To define the 249 * available actions use the <code>addActionAvailable</code> method. 250 * 251 * @param action The action to check if it is available. 252 * @return True if the action is available for the current stage. 253 */ 254 public final boolean isValidAction(Action action) { 255 if (action == Action.cancel) { 256 return true; 257 } 258 259 final AllowedAction executeAction; 260 if (action == Action.execute) { 261 AdHocCommandResult result = getLastResult(); 262 executeAction = result.getResponse().getExecuteAction(); 263 264 // This is basically the case that was clarified with 265 // https://github.com/xsf/xeps/commit/fdaee2da8ffd34b5b5151e90ef1df8b396a06531 and 266 // https://github.com/xsf/xeps/pull/591. 267 if (executeAction == null) { 268 return false; 269 } 270 } else { 271 executeAction = action.allowedAction; 272 assert executeAction != null; 273 } 274 275 Set<AllowedAction> actions = getActions(); 276 return actions != null && actions.contains(executeAction); 277 } 278}