001/*
002 * Copyright (c) 2013 Nu Echo Inc. All rights reserved.
003 */
004package com.nuecho.rivr.voicexml.turn.output;
005
006import static com.nuecho.rivr.core.util.Assert.*;
007import static com.nuecho.rivr.voicexml.rendering.voicexml.VoiceXmlDomUtil.*;
008import static java.util.Collections.*;
009
010import java.util.*;
011import java.util.Map.Entry;
012
013import javax.json.*;
014
015import org.w3c.dom.*;
016
017import com.nuecho.rivr.core.util.*;
018import com.nuecho.rivr.voicexml.dialogue.*;
019import com.nuecho.rivr.voicexml.rendering.voicexml.*;
020import com.nuecho.rivr.voicexml.turn.*;
021import com.nuecho.rivr.voicexml.turn.output.fetch.*;
022import com.nuecho.rivr.voicexml.util.json.*;
023
024/**
025 * A {@link SubdialogueCall} is a {@link VoiceXmlOutputTurn} that invokes
026 * another external dialogue.
027 * <p>
028 * Parameters may be passed to the subdialogue and return values may be
029 * retrieved if the invoked subdialogue ends with a <code>&lt;return&gt;</code>
030 * instruction.
031 * 
032 * @author Nu Echo Inc.
033 * @see Parameter
034 * @see <a
035 *      href="https://www.w3.org/TR/voicexml20/#dml2.3.4">https://www.w3.org/TR/voicexml20/#dml2.3.4</a>
036 */
037public class SubdialogueCall extends VoiceXmlOutputTurn {
038    public static final String SUBDIALOGUE_RESULT_VARIABLE_NAME = "subdialogue";
039
040    private static final String SUBDIALOGUE_INVOCATION_TURN_TYPE = "subdialogue";
041
042    private static final String POST_DIALOGUE_SCRIPT_PROPERTY = "postDialogueScript";
043    private static final String SUBDIALOGUE_PARAMETERS_PROPERTY = "subdialogueParameters";
044    private static final String SUBMIT_URI_PROPERTY = "uri";
045    private static final String SUBMIT_PARAMETERS_PROPERTY = "submitParameters";
046    private static final String SUBMIT_METHOD_PROPERTY = "submitMethod";
047    private static final String FETCH_CONFIGURATION_PROPERTY = "fetchConfiguration";
048
049    private final String mUri;
050    private Collection<Parameter> mParameters = Collections.emptyList();
051    private VariableList mSubmitParameters = new VariableList();
052    private SubmitMethod mMethod = SubmitMethod.get;
053    private DocumentFetchConfiguration mFetchConfiguration;
054    private String mPostDialogueScript;
055
056    /**
057     * @param name The name of this turn. Not empty.
058     * @param uri The URI of the subdialogue. Not empty.
059     */
060    public SubdialogueCall(String name, String uri) {
061        super(name);
062        Assert.notEmpty(uri, "uri");
063        mUri = uri;
064    }
065
066    /**
067     * @param parameters A list of {@link Parameter} that will be passed to the
068     *            subdialogue. Not null.
069     */
070    public final void setSubdialogueParameters(Collection<Parameter> parameters) {
071        Assert.noNullValues(parameters, "parameters");
072        mParameters = new ArrayList<Parameter>(parameters);
073    }
074
075    /**
076     * @param subdialogueParameters A list of {@link Parameter} that will be
077     *            passed to the subdialogue. Not null.
078     */
079    public final void setSubdialogueParameters(Parameter... subdialogueParameters) {
080        setSubdialogueParameters(asListChecked(subdialogueParameters));
081    }
082
083    /**
084     * @param submitParameters A list of variable to submit when invoking the
085     *            URI. Not null.
086     */
087    public final void setSubmitParameters(VariableList submitParameters) {
088        Assert.notNull(submitParameters, "submitParameters");
089        mSubmitParameters = submitParameters;
090    }
091
092    /**
093     * @param method The HTTP method used to invoke the subdialogue (GET or
094     *            POST). <code>null</code> to use the VoiceXML platform default
095     * @see SubmitMethod
096     */
097    public final void setMethod(SubmitMethod method) {
098        mMethod = method;
099    }
100
101    /**
102     * @param fetchConfiguration The {@link DocumentFetchConfiguration}.
103     *            <code>null</code> to use the VoiceXML platform default.
104     */
105    public final void setFetchConfiguration(DocumentFetchConfiguration fetchConfiguration) {
106        mFetchConfiguration = fetchConfiguration;
107    }
108
109    /**
110     * @param postDialogueScript The ECMAScript script to execute after
111     *            subdialogue invocation.
112     */
113    public final void setPostDialogueScript(String postDialogueScript) {
114        mPostDialogueScript = postDialogueScript;
115    }
116
117    public final String getUri() {
118        return mUri;
119    }
120
121    public final Collection<Parameter> getVoiceXmlParameters() {
122        return unmodifiableCollection(mParameters);
123    }
124
125    public final VariableList getSubmitParameters() {
126        return mSubmitParameters;
127    }
128
129    public final SubmitMethod getMethod() {
130        return mMethod;
131    }
132
133    public final String getPostDialogueScript() {
134        return mPostDialogueScript;
135    }
136
137    public final DocumentFetchConfiguration getFetchConfiguration() {
138        return mFetchConfiguration;
139    }
140
141    @Override
142    protected final String getOuputTurnType() {
143        return SUBDIALOGUE_INVOCATION_TURN_TYPE;
144    }
145
146    @Override
147    protected void addTurnProperties(JsonObjectBuilder builder) {
148        JsonUtils.add(builder, SUBMIT_URI_PROPERTY, mUri);
149        JsonUtils.add(builder, SUBMIT_METHOD_PROPERTY, mMethod.name());
150        JsonUtils.add(builder, SUBMIT_PARAMETERS_PROPERTY, mSubmitParameters);
151        JsonUtils.add(builder, SUBDIALOGUE_PARAMETERS_PROPERTY, JsonUtils.toJson(mParameters));
152        JsonUtils.add(builder, FETCH_CONFIGURATION_PROPERTY, mFetchConfiguration);
153        JsonUtils.add(builder, POST_DIALOGUE_SCRIPT_PROPERTY, getPostDialogueScript());
154    }
155
156    @Override
157    protected void fillVoiceXmlDocument(Document document, Element formElement, VoiceXmlDialogueContext dialogueContext)
158            throws VoiceXmlDocumentRenderingException {
159
160        List<String> submitNameList = new ArrayList<String>();
161        VariableList submitVariableList = mSubmitParameters;
162        if (submitVariableList != null) {
163            addVariables(formElement, submitVariableList);
164
165            for (Entry<String, String> entry : mSubmitParameters) {
166                submitNameList.add(entry.getKey());
167            }
168        }
169
170        Element subdialogueElement = DomUtils.appendNewElement(formElement, SUBDIALOG_ELEMENT);
171        subdialogueElement.setAttribute(NAME_ATTRIBUTE, SUBDIALOGUE_FORM_ITEM_NAME);
172        subdialogueElement.setAttribute(SRC_ATTRIBUTE, mUri);
173
174        if (!submitNameList.isEmpty()) {
175            subdialogueElement.setAttribute(NAME_LIST_ATTRIBUTE, StringUtils.join(submitNameList, " "));
176        }
177
178        for (Parameter parameter : mParameters) {
179            Element paramElement = DomUtils.appendNewElement(subdialogueElement, PARAM_ELEMENT);
180            paramElement.setAttribute(NAME_ATTRIBUTE, parameter.getName());
181            setAttribute(paramElement, VALUE_ATTRIBUTE, parameter.getValue());
182            setAttribute(paramElement, EXPR_ATTRIBUTE, parameter.getExpression());
183        }
184
185        SubmitMethod submitMethod = mMethod;
186        if (submitMethod != null) {
187            subdialogueElement.setAttribute(METHOD_ATTRIBUTE, submitMethod.name());
188        }
189
190        DocumentFetchConfiguration fetchConfiguration = mFetchConfiguration;
191        if (fetchConfiguration != null) {
192            applyFetchAudio(subdialogueElement, fetchConfiguration.getFetchAudio());
193            applyRessourceFetchConfiguration(subdialogueElement, fetchConfiguration);
194        }
195
196        Element filledElement = DomUtils.appendNewElement(subdialogueElement, FILLED_ELEMENT);
197
198        createVarElement(filledElement, SUBDIALOGUE_RESULT_VARIABLE_NAME, "dialog." + SUBDIALOGUE_FORM_ITEM_NAME);
199
200        if (mPostDialogueScript != null) {
201            createScript(filledElement, mPostDialogueScript);
202        }
203
204        createScript(filledElement, RIVR_SCOPE_OBJECT + ".addValueResult(" + SUBDIALOGUE_RESULT_VARIABLE_NAME + ");");
205        createGotoSubmit(filledElement);
206    }
207
208    /**
209     * {@link SubdialogueCall} parameter, can be created with a string value, a
210     * {@link JsonValue} or an expression.
211     */
212    public static final class Parameter implements JsonSerializable {
213        private static final String NAME_PROPERTY = "name";
214        private static final String VALUE_PROPERTY = "value";
215        private static final String EXPRESSION_PROPERTY = "expression";
216
217        private final String mName;
218        private String mExpression;
219        private String mValue;
220
221        /**
222         * @param name The name of the parameter. Not empty.
223         * @param value The string value of the parameter. Not null.
224         * @return The newly created subdialogue parameter
225         */
226        public static Parameter createWithValue(String name, String value) {
227            Assert.notNull(value, "value");
228
229            Parameter parameter = new Parameter(name);
230            parameter.mValue = value;
231            return parameter;
232        }
233
234        /**
235         * @param name The name of the parameter. Not empty.
236         * @param json The JSON value of the parameter. Not null.
237         * @return The newly created subdialogue parameter
238         */
239        public static Parameter createWithJson(String name, JsonValue json) {
240            Assert.notNull(json, "json");
241            return createWithExpression(name, json.toString());
242        }
243
244        /**
245         * @param name The name of the parameter. Not empty.
246         * @param expression The ECMAScript expression of the parameter. Not
247         *            null.
248         * @return The newly created subdialogue parameter
249         */
250        public static Parameter createWithExpression(String name, String expression) {
251            Assert.notNull(expression, "expression");
252
253            Parameter parameter = new Parameter(name);
254            parameter.mExpression = expression;
255            return parameter;
256        }
257
258        private Parameter(String name) {
259            Assert.notEmpty(name, "name");
260            mName = name;
261        }
262
263        public String getName() {
264            return mName;
265        }
266
267        public String getExpression() {
268            return mExpression;
269        }
270
271        public String getValue() {
272            return mValue;
273        }
274
275        @Override
276        public JsonValue asJson() {
277            JsonObjectBuilder builder = JsonUtils.createObjectBuilder();
278            JsonUtils.add(builder, NAME_PROPERTY, mName);
279            JsonUtils.add(builder, EXPRESSION_PROPERTY, mExpression);
280            JsonUtils.add(builder, VALUE_PROPERTY, mValue);
281            return builder.build();
282        }
283    }
284
285    /**
286     * Builder used to ease the creation of instances of {@link SubdialogueCall}
287     * .
288     */
289    public static class Builder {
290
291        private final String mName;
292        private String mUri;
293        private final List<Parameter> mParameters = Collections.emptyList();
294        private final VariableList mSubmitParameters = new VariableList();
295        private SubmitMethod mMethod = SubmitMethod.get;
296        private DocumentFetchConfiguration mFetchConfiguration;
297        private String mPostDialogueScript;
298
299        public Builder(String name) {
300            mName = name;
301        }
302
303        public Builder uri(String uri) {
304            Assert.notEmpty(uri, "uri");
305            mUri = uri;
306            return this;
307        }
308
309        public Builder addVoiceXmlParameter(Parameter parameter) {
310            Assert.notNull(parameter, "parameter");
311            mParameters.add(parameter);
312            return this;
313        }
314
315        public Builder addSubmitParameterExpression(String name, String expression) {
316            Assert.notEmpty(name, "name");
317            mSubmitParameters.addWithExpression(name, expression);
318            return this;
319        }
320
321        public Builder addSubmitParameterString(String name, String string) {
322            Assert.notEmpty(name, "name");
323            mSubmitParameters.addWithString(name, string);
324            return this;
325        }
326
327        public Builder setMethod(SubmitMethod method) {
328            Assert.notNull(method, "method");
329            mMethod = method;
330            return this;
331        }
332
333        public Builder setFetchConfiguration(DocumentFetchConfiguration fetchConfiguration) {
334            mFetchConfiguration = fetchConfiguration;
335            return this;
336        }
337
338        public Builder setPostDialogueScript(String postDialogueScript) {
339            mPostDialogueScript = postDialogueScript;
340            return this;
341        }
342
343        public SubdialogueCall build() {
344            SubdialogueCall subdialogueCall = new SubdialogueCall(mName, mUri);
345            subdialogueCall.setMethod(mMethod);
346            subdialogueCall.setPostDialogueScript(mPostDialogueScript);
347            subdialogueCall.setFetchConfiguration(mFetchConfiguration);
348            subdialogueCall.setSubdialogueParameters(mParameters);
349            subdialogueCall.setSubmitParameters(mSubmitParameters);
350            return subdialogueCall;
351        }
352    }
353
354    @Override
355    public int hashCode() {
356        final int prime = 31;
357        int result = super.hashCode();
358        result = prime * result + ((mFetchConfiguration == null) ? 0 : mFetchConfiguration.hashCode());
359        result = prime * result + ((mMethod == null) ? 0 : mMethod.hashCode());
360        result = prime * result + ((mParameters == null) ? 0 : mParameters.hashCode());
361        result = prime * result + ((mPostDialogueScript == null) ? 0 : mPostDialogueScript.hashCode());
362        result = prime * result + ((mSubmitParameters == null) ? 0 : mSubmitParameters.hashCode());
363        result = prime * result + ((mUri == null) ? 0 : mUri.hashCode());
364        return result;
365    }
366
367    @Override
368    public boolean equals(Object obj) {
369        if (this == obj) return true;
370        if (!super.equals(obj)) return false;
371        if (getClass() != obj.getClass()) return false;
372        SubdialogueCall other = (SubdialogueCall) obj;
373        if (mFetchConfiguration == null) {
374            if (other.mFetchConfiguration != null) return false;
375        } else if (!mFetchConfiguration.equals(other.mFetchConfiguration)) return false;
376        if (mMethod != other.mMethod) return false;
377        if (mParameters == null) {
378            if (other.mParameters != null) return false;
379        } else if (!mParameters.equals(other.mParameters)) return false;
380        if (mPostDialogueScript == null) {
381            if (other.mPostDialogueScript != null) return false;
382        } else if (!mPostDialogueScript.equals(other.mPostDialogueScript)) return false;
383        if (mSubmitParameters == null) {
384            if (other.mSubmitParameters != null) return false;
385        } else if (!mSubmitParameters.equals(other.mSubmitParameters)) return false;
386        if (mUri == null) {
387            if (other.mUri != null) return false;
388        } else if (!mUri.equals(other.mUri)) return false;
389        return true;
390    }
391
392}