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.List; 020 021import org.jivesoftware.smack.SmackException.NoResponseException; 022import org.jivesoftware.smack.SmackException.NotConnectedException; 023import org.jivesoftware.smack.XMPPException.XMPPErrorException; 024import org.jivesoftware.smack.packet.StanzaError; 025 026import org.jivesoftware.smackx.commands.packet.AdHocCommandData; 027import org.jivesoftware.smackx.xdata.form.FillableForm; 028import org.jivesoftware.smackx.xdata.packet.DataForm; 029 030import org.jxmpp.jid.Jid; 031 032/** 033 * An ad-hoc command is responsible for executing the provided service and 034 * storing the result of the execution. Each new request will create a new 035 * instance of the command, allowing information related to executions to be 036 * stored in it. For example suppose that a command that retrieves the list of 037 * users on a server is implemented. When the command is executed it gets that 038 * list and the result is stored as a form in the command instance, i.e. the 039 * <code>getForm</code> method retrieves a form with all the users. 040 * <p> 041 * Each command has a <code>node</code> that should be unique within a given JID. 042 * </p> 043 * <p> 044 * Commands may have zero or more stages. Each stage is usually used for 045 * gathering information required for the command execution. Users are able to 046 * move forward or backward across the different stages. Commands may not be 047 * cancelled while they are being executed. However, users may request the 048 * "cancel" action when submitting a stage response indicating that the command 049 * execution should be aborted. Thus, releasing any collected information. 050 * Commands that require user interaction (i.e. have more than one stage) will 051 * have to provide the data forms the user must complete in each stage and the 052 * allowed actions the user might perform during each stage (e.g. go to the 053 * previous stage or go to the next stage). 054 * </p> 055 * All the actions may throw an XMPPException if there is a problem executing 056 * them. The <code>XMPPError</code> of that exception may have some specific 057 * information about the problem. The possible extensions are: 058 * <ul> 059 * <li><i>malformed-action</i>. Extension of a <i>bad-request</i> error.</li> 060 * <li><i>bad-action</i>. Extension of a <i>bad-request</i> error.</li> 061 * <li><i>bad-locale</i>. Extension of a <i>bad-request</i> error.</li> 062 * <li><i>bad-payload</i>. Extension of a <i>bad-request</i> error.</li> 063 * <li><i>bad-sessionid</i>. Extension of a <i>bad-request</i> error.</li> 064 * <li><i>session-expired</i>. Extension of a <i>not-allowed</i> error.</li> 065 * </ul> 066 * <p> 067 * See the <code>SpecificErrorCondition</code> class for detailed description 068 * of each one. 069 * </p> 070 * Use the <code>getSpecificErrorConditionFrom</code> to obtain the specific 071 * information from an <code>XMPPError</code>. 072 * 073 * @author Gabriel Guardincerri 074 * 075 */ 076public abstract class AdHocCommand { 077 // TODO: Analyze the redesign of command by having an ExecutionResponse as a 078 // TODO: result to the execution of every action. That result should have all the 079 // TODO: information related to the execution, e.g. the form to fill. Maybe this 080 // TODO: design is more intuitive and simpler than the current one that has all in 081 // TODO: one class. 082 083 private AdHocCommandData data; 084 085 public AdHocCommand() { 086 super(); 087 data = new AdHocCommandData(); 088 } 089 090 /** 091 * Returns the specific condition of the <code>error</code> or <code>null</code> if the 092 * error doesn't have any. 093 * 094 * @param error the error the get the specific condition from. 095 * @return the specific condition of this error, or null if it doesn't have 096 * any. 097 */ 098 public static SpecificErrorCondition getSpecificErrorCondition(StanzaError error) { 099 // This method is implemented to provide an easy way of getting a packet 100 // extension of the XMPPError. 101 for (SpecificErrorCondition condition : SpecificErrorCondition.values()) { 102 if (error.getExtension(condition.toString(), 103 AdHocCommandData.SpecificError.namespace) != null) { 104 return condition; 105 } 106 } 107 return null; 108 } 109 110 /** 111 * Set the the human readable name of the command, usually used for 112 * displaying in a UI. 113 * 114 * @param name the name. 115 */ 116 public void setName(String name) { 117 data.setName(name); 118 } 119 120 /** 121 * Returns the human readable name of the command. 122 * 123 * @return the human readable name of the command 124 */ 125 public String getName() { 126 return data.getName(); 127 } 128 129 /** 130 * Sets the unique identifier of the command. This value must be unique for 131 * the <code>OwnerJID</code>. 132 * 133 * @param node the unique identifier of the command. 134 */ 135 public void setNode(String node) { 136 data.setNode(node); 137 } 138 139 /** 140 * Returns the unique identifier of the command. It is unique for the 141 * <code>OwnerJID</code>. 142 * 143 * @return the unique identifier of the command. 144 */ 145 public String getNode() { 146 return data.getNode(); 147 } 148 149 /** 150 * Returns the full JID of the owner of this command. This JID is the "to" of a 151 * execution request. 152 * 153 * @return the owner JID. 154 */ 155 public abstract Jid getOwnerJID(); 156 157 /** 158 * Returns the notes that the command has at the current stage. 159 * 160 * @return a list of notes. 161 */ 162 public List<AdHocCommandNote> getNotes() { 163 return data.getNotes(); 164 } 165 166 /** 167 * Adds a note to the current stage. This should be used when setting a 168 * response to the execution of an action. All the notes added here are 169 * returned by the {@link #getNotes} method during the current stage. 170 * Once the stage changes all the notes are discarded. 171 * 172 * @param note the note. 173 */ 174 protected void addNote(AdHocCommandNote note) { 175 data.addNote(note); 176 } 177 178 public String getRaw() { 179 return data.getChildElementXML().toString(); 180 } 181 182 /** 183 * Returns the form of the current stage. Usually it is the form that must 184 * be answered to execute the next action. If that is the case it should be 185 * used by the requester to fill all the information that the executor needs 186 * to continue to the next stage. It can also be the result of the 187 * execution. 188 * 189 * @return the form of the current stage to fill out or the result of the 190 * execution. 191 */ 192 public DataForm getForm() { 193 return data.getForm(); 194 } 195 196 /** 197 * Sets the form of the current stage. This should be used when setting a 198 * response. It could be a form to fill out the information needed to go to 199 * the next stage or the result of an execution. 200 * 201 * @param form the form of the current stage to fill out or the result of the 202 * execution. 203 */ 204 protected void setForm(DataForm form) { 205 data.setForm(form); 206 } 207 208 /** 209 * Executes the command. This is invoked only on the first stage of the 210 * command. It is invoked on every command. If there is a problem executing 211 * the command it throws an XMPPException. 212 * 213 * @throws NoResponseException if there was no response from the remote entity. 214 * @throws XMPPErrorException if there is an error executing the command. 215 * @throws NotConnectedException if the XMPP connection is not connected. 216 * @throws InterruptedException if the calling thread was interrupted. 217 */ 218 public abstract void execute() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException; 219 220 /** 221 * Executes the next action of the command with the information provided in 222 * the <code>response</code>. This form must be the answer form of the 223 * previous stage. This method will be only invoked for commands that have one 224 * or more stages. If there is a problem executing the command it throws an 225 * XMPPException. 226 * 227 * @param response the form answer of the previous stage. 228 * @throws NoResponseException if there was no response from the remote entity. 229 * @throws XMPPErrorException if there is a problem executing the command. 230 * @throws NotConnectedException if the XMPP connection is not connected. 231 * @throws InterruptedException if the calling thread was interrupted. 232 */ 233 public abstract void next(FillableForm response) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException; 234 235 /** 236 * Completes the command execution with the information provided in the 237 * <code>response</code>. This form must be the answer form of the 238 * previous stage. This method will be only invoked for commands that have one 239 * or more stages. If there is a problem executing the command it throws an 240 * XMPPException. 241 * 242 * @param response the form answer of the previous stage. 243 * 244 * @throws NoResponseException if there was no response from the remote entity. 245 * @throws XMPPErrorException if there is a problem executing the command. 246 * @throws NotConnectedException if the XMPP connection is not connected. 247 * @throws InterruptedException if the calling thread was interrupted. 248 */ 249 public abstract void complete(FillableForm response) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException; 250 251 /** 252 * Goes to the previous stage. The requester is asking to re-send the 253 * information of the previous stage. The command must change it state to 254 * the previous one. If there is a problem executing the command it throws 255 * an XMPPException. 256 * 257 * @throws NoResponseException if there was no response from the remote entity. 258 * @throws XMPPErrorException if there is a problem executing the command. 259 * @throws NotConnectedException if the XMPP connection is not connected. 260 * @throws InterruptedException if the calling thread was interrupted. 261 */ 262 public abstract void prev() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException; 263 264 /** 265 * Cancels the execution of the command. This can be invoked on any stage of 266 * the execution. If there is a problem executing the command it throws an 267 * XMPPException. 268 * 269 * @throws NoResponseException if there was no response from the remote entity. 270 * @throws XMPPErrorException if there is a problem executing the command. 271 * @throws NotConnectedException if the XMPP connection is not connected. 272 * @throws InterruptedException if the calling thread was interrupted. 273 */ 274 public abstract void cancel() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException; 275 276 /** 277 * Returns a collection with the allowed actions based on the current stage. 278 * Possible actions are: {@link Action#prev prev}, {@link Action#next next} and 279 * {@link Action#complete complete}. This method will be only invoked for commands that 280 * have one or more stages. 281 * 282 * @return a collection with the allowed actions based on the current stage 283 * as defined in the SessionData. 284 */ 285 protected List<Action> getActions() { 286 return data.getActions(); 287 } 288 289 /** 290 * Add an action to the current stage available actions. This should be used 291 * when creating a response. 292 * 293 * @param action the action. 294 */ 295 protected void addActionAvailable(Action action) { 296 data.addAction(action); 297 } 298 299 /** 300 * Returns the action available for the current stage which is 301 * considered the equivalent to "execute". When the requester sends his 302 * reply, if no action was defined in the command then the action will be 303 * assumed "execute" thus assuming the action returned by this method. This 304 * method will never be invoked for commands that have no stages. 305 * 306 * @return the action available for the current stage which is considered 307 * the equivalent to "execute". 308 */ 309 protected Action getExecuteAction() { 310 return data.getExecuteAction(); 311 } 312 313 /** 314 * Sets which of the actions available for the current stage is 315 * considered the equivalent to "execute". This should be used when setting 316 * a response. When the requester sends his reply, if no action was defined 317 * in the command then the action will be assumed "execute" thus assuming 318 * the action returned by this method. 319 * 320 * @param action the action. 321 */ 322 protected void setExecuteAction(Action action) { 323 data.setExecuteAction(action); 324 } 325 326 /** 327 * Returns the status of the current stage. 328 * 329 * @return the current status. 330 */ 331 public Status getStatus() { 332 return data.getStatus(); 333 } 334 335 /** 336 * Check if this command has been completed successfully. 337 * 338 * @return <code>true</code> if this command is completed. 339 * @since 4.2 340 */ 341 public boolean isCompleted() { 342 return getStatus() == Status.completed; 343 } 344 345 /** 346 * Sets the data of the current stage. This should not used. 347 * 348 * @param data the data. 349 */ 350 void setData(AdHocCommandData data) { 351 this.data = data; 352 } 353 354 /** 355 * Gets the data of the current stage. This should not used. 356 * 357 * @return the data. 358 */ 359 AdHocCommandData getData() { 360 return data; 361 } 362 363 /** 364 * Returns true if the <code>action</code> is available in the current stage. 365 * The {@link Action#cancel cancel} action is always allowed. To define the 366 * available actions use the <code>addActionAvailable</code> method. 367 * 368 * @param action TODO javadoc me please 369 * The action to check if it is available. 370 * @return True if the action is available for the current stage. 371 */ 372 protected boolean isValidAction(Action action) { 373 return getActions().contains(action) || Action.cancel.equals(action); 374 } 375 376 /** 377 * The status of the stage in the adhoc command. 378 */ 379 public enum Status { 380 381 /** 382 * The command is being executed. 383 */ 384 executing, 385 386 /** 387 * The command has completed. The command session has ended. 388 */ 389 completed, 390 391 /** 392 * The command has been canceled. The command session has ended. 393 */ 394 canceled 395 } 396 397 public enum Action { 398 399 /** 400 * The command should be executed or continue to be executed. This is 401 * the default value. 402 */ 403 execute, 404 405 /** 406 * The command should be canceled. 407 */ 408 cancel, 409 410 /** 411 * The command should be digress to the previous stage of execution. 412 */ 413 prev, 414 415 /** 416 * The command should progress to the next stage of execution. 417 */ 418 next, 419 420 /** 421 * The command should be completed (if possible). 422 */ 423 complete, 424 425 /** 426 * The action is unknown. This is used when a received message has an 427 * unknown action. It must not be used to send an execution request. 428 */ 429 unknown 430 } 431 432 public enum SpecificErrorCondition { 433 434 /** 435 * The responding JID cannot accept the specified action. 436 */ 437 badAction("bad-action"), 438 439 /** 440 * The responding JID does not understand the specified action. 441 */ 442 malformedAction("malformed-action"), 443 444 /** 445 * The responding JID cannot accept the specified language/locale. 446 */ 447 badLocale("bad-locale"), 448 449 /** 450 * The responding JID cannot accept the specified payload (e.g. the data 451 * form did not provide one or more required fields). 452 */ 453 badPayload("bad-payload"), 454 455 /** 456 * The responding JID cannot accept the specified sessionid. 457 */ 458 badSessionid("bad-sessionid"), 459 460 /** 461 * The requesting JID specified a sessionid that is no longer active 462 * (either because it was completed, canceled, or timed out). 463 */ 464 sessionExpired("session-expired"); 465 466 private final String value; 467 468 SpecificErrorCondition(String value) { 469 this.value = value; 470 } 471 472 @Override 473 public String toString() { 474 return value; 475 } 476 } 477}