summaryrefslogtreecommitdiffstats
blob: 5bccb31dcc69801e81c64d2c1293d3b0a6f3ab9c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
/*
 * Copyright (c) 2012 Isode Limited, London, England.
 * All rights reserved.
 */
/*
 * Copyright (c) 2010-2011 Kevin Smith
 * All rights reserved.
 */

package com.isode.stroke.adhoc;

import java.util.HashMap;
import java.util.List;

import com.isode.stroke.elements.Command;
import com.isode.stroke.elements.ErrorPayload;
import com.isode.stroke.elements.Form;
import com.isode.stroke.elements.IQ;
import com.isode.stroke.elements.Command.Action;
import com.isode.stroke.elements.Command.Status;
import com.isode.stroke.jid.JID;
import com.isode.stroke.queries.GenericRequest;
import com.isode.stroke.queries.IQRouter;
import com.isode.stroke.signals.Signal1;
import com.isode.stroke.signals.Slot2;

/**
 * This class maintains the session between the client and the server for an
 * Ad-Hoc command.
 */
public class OutgoingAdHocCommandSession {
    /**
     * Availability of action.
     */
    public enum ActionState {
        /**
         * Action isn't applicable to this command
         */
        ABSENT,
        /**
         * Action is applicable to this command but not currently available
         */
        PRESENT,
        /**
         * Action is currently available (not used in
         * {@link OutgoingAdHocCommandSession})
         */
        ENABLED,
        /**
         * Action is applicable and currently available
         */
        ENABLED_AND_PRESENT
    };

    /**
     * Emitted when the form for the next stage is available. The caller should
     * add a listener to this signal which will be invoked when the target sends
     * a form.
     */
    public final Signal1<Command> onNextStageReceived = new Signal1<Command>();

    /**
     * Emitted on error. The caller should add a listener to this signal which
     * will be invoked when the target sends an error.
     */
    public final Signal1<ErrorPayload> onError = new Signal1<ErrorPayload>();

    private JID to_;
    private String commandNode_;
    private IQRouter iqRouter_;
    private boolean isMultiStage_;
    private String sessionID_;
    private HashMap<Action, ActionState> actionStates_ = new HashMap<Action, ActionState>();

    /**
     * Create an Ad-Hoc command session. The initial command will be sent to the
     * server on calling {@link #start()}.
     *
     * @param to JID of the entity that the Ad-Hoc command is executed on (user
     *            JID, server JID, etc.), must not be null
     * @param commandNode Node part of the Ad-Hoc command as published by the
     *            target (e.g. "http://isode.com/xmpp/commands#test"), must not
     *            be null
     * @param iqRouter IQ router to be used for the session, must not be null
     */
    public OutgoingAdHocCommandSession(JID to, String commandNode,
            IQRouter iqRouter) {
        if (to == null) {
            throw new NullPointerException("'to' must not be null");
        }
        if (commandNode == null) {
            throw new NullPointerException("'commandNode' must not be null");
        }
        if (iqRouter == null) {
            throw new NullPointerException("'iqRouter' must not be null");
        }

        to_ = to;
        commandNode_ = commandNode;
        iqRouter_ = iqRouter;
        isMultiStage_ = false;
    }

    private void handleResponse(Command payload, ErrorPayload error) {
        if (error != null) {
            onError.emit(error);
        } else {
            List<Action> actions = payload.getAvailableActions();
            actionStates_.clear();
            if (payload.getStatus() == Status.EXECUTING) {
                actionStates_.put(Action.CANCEL,
                        ActionState.ENABLED_AND_PRESENT);
                actionStates_.put(Action.COMPLETE, ActionState.PRESENT);
                if (actions.contains(Action.COMPLETE)) {
                    actionStates_.put(Action.COMPLETE,
                            ActionState.ENABLED_AND_PRESENT);
                }

                if (getIsMultiStage()) {
                    actionStates_.put(Action.NEXT, ActionState.PRESENT);
                    actionStates_.put(Action.PREV, ActionState.PRESENT);
                }

                if (actions.contains(Action.NEXT)) {
                    actionStates_.put(Action.NEXT,
                            ActionState.ENABLED_AND_PRESENT);
                }

                if (actions.contains(Action.PREV)) {
                    actionStates_.put(Action.PREV,
                            ActionState.ENABLED_AND_PRESENT);
                }
            }

            sessionID_ = payload.getSessionID();
            if (actions.contains(Action.NEXT) || actions.contains(Action.PREV)) {
                isMultiStage_ = true;
            }
            onNextStageReceived.emit(payload);
        }
    }

    /**
     * @return true if the form is multi-stage. Will return a valid result only
     *         after the first response is received from the server so should be
     *         called only after the listener for {@link #onNextStageReceived}
     *         has been called at least once.
     */
    public boolean getIsMultiStage() {
        return isMultiStage_;
    }

    /**
     * Send initial request to the target.
     */
    public void start() {
        GenericRequest<Command> commandRequest = new GenericRequest<Command>(
                IQ.Type.Set, to_, new Command(commandNode_), iqRouter_);
        commandRequest.onResponse.connect(new Slot2<Command, ErrorPayload>() {
            public void call(Command payload, ErrorPayload error) {
                handleResponse(payload, error);
            }
        });
        commandRequest.send();
    }

    /**
     * Cancel command session with the target.
     */
    public void cancel() {
        if (sessionID_.length() != 0) {
            submitForm(null, Action.CANCEL);
        }
    }

    /**
     * Return to the previous stage.
     */
    public void goBack() {
        submitForm(null, Action.PREV);
    }

    /**
     * Send the form to complete the command.
     * 
     * @param form Form for submission - if null, the command will be submitted
     *            with no form.
     */
    public void complete(Form form) {
        submitForm(form, Action.COMPLETE);
    }

    /**
     * Send the form to advance to the next stage of the command.
     * 
     * @param form Form for submission - if null, the command will be submitted
     *            with no form.
     */
    public void goNext(Form form) {
        submitForm(form, Action.NEXT);
    }

    private void submitForm(Form form, Action action) {
        Command command = new Command(commandNode_, sessionID_, action);
        command.setForm(form);

        GenericRequest<Command> commandRequest = new GenericRequest<Command>(
                IQ.Type.Set, to_, command, iqRouter_);
        commandRequest.onResponse.connect(new Slot2<Command, ErrorPayload>() {
            public void call(Command payload, ErrorPayload error) {
                handleResponse(payload, error);
            }
        });
        commandRequest.send();
    }

    /**
     * Get the state of a given action. This is useful for a UI to determine
     * which buttons should be visible, and which enabled. If no actions are
     * {@link ActionState#ENABLED_AND_PRESENT}, the command has completed.
     * 
     * <p>
     * Will return a valid result for the current stage only after the response
     * is received from the server so should be called only after the listener
     * for {@link #onNextStageReceived} has been called for that stage.
     * 
     * @param action Action for which the state needs to be determined for the
     *            current form. Useful for Next, Prev, Cancel and Complete only,
     *            for other values and null, {@link ActionState#ABSENT} will be
     *            returned.
     * @return state of the requested action, will never be null
     */
    public ActionState getActionState(Action action) {
        ActionState actionState = actionStates_.get(action);
        if (actionState == null) {
            actionState = ActionState.ABSENT;
        }

        return actionState;
    }

    @Override
    public String toString() {
        return OutgoingAdHocCommandSession.class.getSimpleName() + "\nto: "
                + to_ + "\ncommand node: " + commandNode_ + "\nsession ID: "
                + sessionID_ + "\nis multi-stage: " + isMultiStage_;
    }
}