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