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.XMPPConnection;
022import org.jivesoftware.smack.XMPPException.XMPPErrorException;
023import org.jivesoftware.smack.packet.IQ;
024import org.jivesoftware.smack.util.Objects;
025import org.jivesoftware.smackx.commands.packet.AdHocCommandData;
026import org.jivesoftware.smackx.xdata.form.FillableForm;
027import org.jivesoftware.smackx.xdata.form.SubmitForm;
028import org.jivesoftware.smackx.xdata.packet.DataForm;
029
030import org.jxmpp.jid.Jid;
031
032/**
033 * Represents a ad-hoc command invoked on a remote entity. Invoking one of the
034 * {@link #execute()}, {@link #next(SubmitForm)},
035 * {@link #prev()}, {@link #cancel()} or
036 * {@link #complete(SubmitForm)} actions results in executing that
037 * action on the remote entity. In response to that action the internal state
038 * of the this command instance will change. For example, if the command is a
039 * single stage command, then invoking the execute action will execute this
040 * action in the remote location. After that the local instance will have a
041 * state of "completed" and a form or notes that applies.
042 *
043 * @author Gabriel Guardincerri
044 * @author Florian Schmaus
045 *
046 */
047public class AdHocCommand extends AbstractAdHocCommand {
048
049    /**
050     * The connection that is used to execute this command
051     */
052    private final XMPPConnection connection;
053
054    /**
055     * The full JID of the command host
056     */
057    private final Jid jid;
058
059    /**
060     * Creates a new RemoteCommand that uses an specific connection to execute a
061     * command identified by <code>node</code> in the host identified by
062     * <code>jid</code>
063     *
064     * @param connection the connection to use for the execution.
065     * @param node the identifier of the command.
066     * @param jid the JID of the host.
067     */
068    protected AdHocCommand(XMPPConnection connection, String node, Jid jid) {
069        super(node);
070        this.connection = Objects.requireNonNull(connection);
071        this.jid = Objects.requireNonNull(jid);
072    }
073
074    public Jid getOwnerJID() {
075        return jid;
076    }
077
078    @Override
079    public final void cancel() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
080        executeAction(AdHocCommandData.Action.cancel);
081    }
082
083    /**
084     * Executes the command. This is invoked only on the first stage of the
085     * command. It is invoked on every command. If there is a problem executing
086     * the command it throws an XMPPException.
087     *
088     * @return an ad-hoc command result.
089     * @throws NoResponseException if there was no response from the remote entity.
090     * @throws XMPPErrorException if there is an error executing the command.
091     * @throws NotConnectedException if the XMPP connection is not connected.
092     * @throws InterruptedException if the calling thread was interrupted.
093     */
094    public final AdHocCommandResult execute() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
095        return executeAction(AdHocCommandData.Action.execute);
096    }
097
098    /**
099     * Executes the next action of the command with the information provided in
100     * the <code>response</code>. This form must be the answer form of the
101     * previous stage. This method will be only invoked for commands that have one
102     * or more stages. If there is a problem executing the command it throws an
103     * XMPPException.
104     *
105     * @param filledForm the form answer of the previous stage.
106     * @return an ad-hoc command result.
107     * @throws NoResponseException if there was no response from the remote entity.
108     * @throws XMPPErrorException if there is a problem executing the command.
109     * @throws NotConnectedException if the XMPP connection is not connected.
110     * @throws InterruptedException if the calling thread was interrupted.
111     */
112    public final AdHocCommandResult next(SubmitForm filledForm) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
113        return executeAction(AdHocCommandData.Action.next, filledForm.getDataForm());
114    }
115
116    /**
117     * Completes the command execution with the information provided in the
118     * <code>response</code>. This form must be the answer form of the
119     * previous stage. This method will be only invoked for commands that have one
120     * or more stages. If there is a problem executing the command it throws an
121     * XMPPException.
122     *
123     * @param filledForm the form answer of the previous stage.
124     * @return an ad-hoc command result.
125     * @throws NoResponseException if there was no response from the remote entity.
126     * @throws XMPPErrorException if there is a problem executing the command.
127     * @throws NotConnectedException if the XMPP connection is not connected.
128     * @throws InterruptedException if the calling thread was interrupted.
129     */
130    public AdHocCommandResult complete(SubmitForm filledForm) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
131        return executeAction(AdHocCommandData.Action.complete, filledForm.getDataForm());
132    }
133
134    /**
135     * Goes to the previous stage. The requester is asking to re-send the
136     * information of the previous stage. The command must change it state to
137     * the previous one. If there is a problem executing the command it throws
138     * an XMPPException.
139     *
140     * @return an ad-hoc command result.
141     * @throws NoResponseException if there was no response from the remote entity.
142     * @throws XMPPErrorException if there is a problem executing the command.
143     * @throws NotConnectedException if the XMPP connection is not connected.
144     * @throws InterruptedException if the calling thread was interrupted.
145     */
146    public final AdHocCommandResult prev() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
147        return executeAction(AdHocCommandData.Action.prev);
148    }
149
150    /**
151     * Executes the default action of the command with the information provided
152     * in the Form. This form must be the answer form of the previous stage. If
153     * there is a problem executing the command it throws an XMPPException.
154     *
155     * @param form the form answer of the previous stage.
156     * @return an ad-hoc command result.
157     * @throws XMPPErrorException if an error occurs.
158     * @throws NoResponseException if there was no response from the server.
159     * @throws NotConnectedException if the XMPP connection is not connected.
160     * @throws InterruptedException if the calling thread was interrupted.
161     */
162    public final AdHocCommandResult execute(FillableForm form) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
163        return executeAction(AdHocCommandData.Action.execute, form.getDataFormToSubmit());
164    }
165
166    private AdHocCommandResult executeAction(AdHocCommandData.Action action) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
167        return executeAction(action, null);
168    }
169
170    /**
171     * Executes the <code>action</code> with the <code>form</code>.
172     * The action could be any of the available actions. The form must
173     * be the answer of the previous stage. It can be <code>null</code> if it is the first stage.
174     *
175     * @param action the action to execute.
176     * @param form the form with the information.
177     * @throws XMPPErrorException if there is a problem executing the command.
178     * @throws NoResponseException if there was no response from the server.
179     * @throws NotConnectedException if the XMPP connection is not connected.
180     * @throws InterruptedException if the calling thread was interrupted.
181     */
182    private synchronized AdHocCommandResult executeAction(AdHocCommandData.Action action, DataForm form) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
183        AdHocCommandData request = AdHocCommandData.builder(getNode(), connection)
184                        .ofType(IQ.Type.set)
185                        .to(getOwnerJID())
186                        .setSessionId(getSessionId())
187                        .setAction(action)
188                        .setForm(form)
189                        .build();
190
191        addRequest(request);
192
193        AdHocCommandData response = connection.sendIqRequestAndWaitForResponse(request);
194
195        // The Ad-Hoc service ("server") may have generated a session id for us.
196        String sessionId = response.getSessionId();
197        if (sessionId != null) {
198            setSessionId(sessionId);
199        }
200
201        AdHocCommandResult result = AdHocCommandResult.from(response);
202        addResult(result);
203        return result;
204    }
205
206}