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