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