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