001/*
002 *   Copyright 2024 Vonage
003 *
004 *   Licensed under the Apache License, Version 2.0 (the "License");
005 *   you may not use this file except in compliance with the License.
006 *   You may obtain a copy of the License at
007 *
008 *        http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *   Unless required by applicable law or agreed to in writing, software
011 *   distributed under the License is distributed on an "AS IS" BASIS,
012 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *   See the License for the specific language governing permissions and
014 *   limitations under the License.
015 */
016package com.vonage.client.voice;
017
018import com.fasterxml.jackson.annotation.JsonProperty;
019import com.vonage.client.Jsonable;
020import com.vonage.client.JsonableBaseObject;
021import com.vonage.client.common.HttpMethod;
022import com.vonage.client.voice.ncco.Action;
023import java.util.ArrayList;
024import java.util.Arrays;
025import java.util.Collection;
026
027/**
028 * Call encapsulates the information required to create a call using {@link VoiceClient#createCall(Call)}
029 */
030public class Call extends JsonableBaseObject {
031    private Endpoint[] to;
032    private Endpoint from;
033    private HttpMethod answerMethod = HttpMethod.GET, eventMethod;
034    private String answerUrl, eventUrl;
035    private MachineDetection machineDetection;
036    private AdvancedMachineDetection advancedMachineDetection;
037    private Integer lengthTimer, ringingTimer;
038    private Boolean fromRandomNumber;
039    private Collection<? extends Action> ncco;
040
041    /**
042     * Builder pattern constructor.
043     *
044     * @param builder The builder to instantiate from.
045     * @since 7.3.0
046     */
047    Call(Builder builder) {
048        if ((to = builder.to) == null || to.length == 0 || to[0] == null) {
049            throw new IllegalStateException("At least one recipient should be specified.");
050        }
051        if ((lengthTimer = builder.lengthTimer) != null && (lengthTimer > 7200 || lengthTimer < 1)) {
052            throw new IllegalArgumentException("Length timer must be between 1 and 7200.");
053        }
054        if ((ringingTimer = builder.ringingTimer) != null && (ringingTimer > 120 || ringingTimer < 1)) {
055            throw new IllegalArgumentException("Ringing timer must be between 1 and 120.");
056        }
057        if (builder.answerMethod != null) switch (answerMethod = builder.answerMethod) {
058            case GET: case POST: break;
059            default: throw new IllegalArgumentException("Answer method must be GET or POST");
060        }
061        if ((eventMethod = builder.eventMethod) != null) switch (eventMethod) {
062            case GET: case POST: break;
063            default: throw new IllegalArgumentException("Event method must be GET or POST");
064        }
065        if ((from = builder.from) == null) {
066            fromRandomNumber = true;
067        }
068        else if ((fromRandomNumber = builder.fromRandomNumber) != null && fromRandomNumber) {
069            throw new IllegalStateException("From number shouldn't be set if using random.");
070        }
071        answerUrl = builder.answerUrl;
072        eventUrl = builder.eventUrl;
073        ncco = builder.ncco;
074        advancedMachineDetection = builder.advancedMachineDetection;
075        if ((machineDetection = builder.machineDetection) != null && advancedMachineDetection != null) {
076            throw new IllegalStateException("Cannot set both machineDetection and advancedMachineDetection.");
077        }
078    }
079
080    Call() {}
081
082    public Call(String to, String from, String answerUrl) {
083        this(new PhoneEndpoint(to), new PhoneEndpoint(from), answerUrl);
084    }
085
086    public Call(Endpoint to, Endpoint from, String answerUrl) {
087        this(new Endpoint[]{to}, from, answerUrl);
088    }
089
090    public Call(String to, String from, Collection<? extends Action> ncco) {
091        this(new PhoneEndpoint(to), new PhoneEndpoint(from), ncco);
092    }
093
094    public Call(Endpoint to, Endpoint from, Collection<? extends Action> ncco) {
095        this(new Endpoint[]{to}, from, ncco);
096    }
097
098    public Call(Endpoint[] to, Endpoint from, String answerUrl) {
099        this(builder().to(to).from(from).answerUrl(answerUrl));
100    }
101
102    public Call(Endpoint[] to, Endpoint from, Collection<? extends Action> ncco) {
103        this(builder().to(to).from(from).ncco(ncco));
104    }
105
106    @JsonProperty("to")
107    public Endpoint[] getTo() {
108        return to;
109    }
110
111    @JsonProperty("from")
112    public Endpoint getFrom() {
113        return from;
114    }
115
116    @JsonProperty("answer_url")
117    public String[] getAnswerUrl() {
118        return (answerUrl != null) ? new String[]{answerUrl} : null;
119    }
120
121    @JsonProperty("answer_method")
122    public String getAnswerMethod() {
123        // Hide the answer method if the answer url isn't defined
124        if (answerUrl == null || answerMethod == null) return null;
125        return answerMethod.toString();
126    }
127
128    @JsonProperty("event_url")
129    public String[] getEventUrl() {
130        if (eventUrl == null) {
131            return null;
132        }
133        return new String[]{eventUrl};
134    }
135
136    @JsonProperty("event_method")
137    public String getEventMethod() {
138        return eventMethod != null ? eventMethod.toString() : null;
139    }
140
141    @JsonProperty("machine_detection")
142    public MachineDetection getMachineDetection() {
143        return machineDetection;
144    }
145
146    /**
147     * Premium machine detection, overrides {@link #getMachineDetection()} if both are set.
148     *
149     * @return The advanced machine detection settings, or {@code null} if not set.
150     *
151     * @since 7.4.0
152     */
153    @JsonProperty("advanced_machine_detection")
154    public AdvancedMachineDetection getAdvancedMachineDetection() {
155        return advancedMachineDetection;
156    }
157
158    @JsonProperty("length_timer")
159    public Integer getLengthTimer() {
160        return lengthTimer;
161    }
162
163    @JsonProperty("ringing_timer")
164    public Integer getRingingTimer() {
165        return ringingTimer;
166    }
167
168    @JsonProperty("random_from_number")
169    public Boolean getFromRandomNumber() {
170        return fromRandomNumber;
171    }
172
173    /**
174     *
175     * @return The NCCO actions.
176     */
177    @JsonProperty("ncco")
178    public Collection<? extends Action> getNcco() {
179        return ncco;
180    }
181
182    /**
183     * Creates an instance of this class from a JSON payload.
184     *
185     * @param json The JSON string to parse.
186     *
187     * @return An instance of this class with the fields populated, if present.
188     */
189    public static Call fromJson(String json) {
190        return Jsonable.fromJson(json);
191    }
192
193    /**
194     * Entrypoint for constructing an instance of this class.
195     *
196     * @return A new Builder.
197     * @since 7.3.0
198     */
199    public static Builder builder() {
200        return new Builder();
201    }
202
203    /**
204     * Builder for constructing a Call object.
205     *
206     * @since 7.3.0
207     */
208    public static class Builder {
209        private Endpoint[] to;
210        private Endpoint from;
211        private HttpMethod answerMethod, eventMethod;
212        private String answerUrl, eventUrl;
213        private MachineDetection machineDetection;
214        private AdvancedMachineDetection advancedMachineDetection;
215        private Integer lengthTimer, ringingTimer;
216        private Boolean fromRandomNumber;
217        private Collection<Action> ncco;
218
219        Builder() {}
220
221        /**
222         * Sets the endpoints (recipients) of the call.
223         *
224         * @param endpoints The recipients of the call in order.
225         *
226         * @return This builder.
227         */
228        public Builder to(Endpoint... endpoints) {
229            to = endpoints;
230            return this;
231        }
232
233        /**
234         * Connect to a Phone (PSTN) number.
235         *
236         * @param number The number to place the call from in E.164 format.
237         *
238         * @return This builder.
239         */
240        public Builder from(String number) {
241            return from(new PhoneEndpoint(number));
242        }
243
244        /**
245         * Sets the outbound caller.
246         *
247         * @param from The caller endpoint.
248         *
249         * @return This builder.
250         */
251        Builder from(Endpoint from) {
252            this.from = from;
253            return this;
254        }
255
256        /**
257         * The HTTP method used to send event information to {@code event_url}.
258         *
259         * @param eventMethod The method type (must be {@code GET} or {@code POST}).
260         *
261         * @return This builder.
262         */
263        public Builder eventMethod(HttpMethod eventMethod) {
264            this.eventMethod = eventMethod;
265            return this;
266        }
267
268        /**
269         * The HTTP method used to send event information to {@code answer_url}.
270         *
271         * @param answerMethod The method type (must be {@code GET} or {@code POST}).
272         *
273         * @return This builder.
274         */
275        public Builder answerMethod(HttpMethod answerMethod) {
276            this.answerMethod = answerMethod;
277            return this;
278        }
279
280        /**
281         * The webhook endpoint where call progress events are sent to.
282         * For more information about the values sent, see the
283         * <a href=https://developer.vonage.com/en/voice/voice-api/webhook-reference#event-webhook>
284         * Event webhook documentation</a>.
285         *
286         * @param eventUrl The event updates URL.
287         *
288         * @return This builder.
289         */
290        public Builder eventUrl(String eventUrl) {
291            this.eventUrl = eventUrl;
292            return this;
293        }
294
295        /**
296         * The webhook endpoint where you provide the Nexmo Call Control Object that governs this call.
297         *
298         * @param answerUrl The NCCO answer URL.
299         *
300         * @return This builder.
301         */
302        public Builder answerUrl(String answerUrl) {
303            this.answerUrl = answerUrl;
304            return this;
305        }
306
307        /**
308         * Configure the behavior when Vonage detects that the call is answered by voicemail. If
309         * {@linkplain MachineDetection#CONTINUE}, Vonage sends an HTTP request to {@code event_url} with the Call
310         * event machine. If {@linkplain MachineDetection#HANGUP}, Vonage ends the call.
311         *
312         * @param machineDetection The machine detection mode.
313         *
314         * @return This builder.
315         */
316        public Builder machineDetection(MachineDetection machineDetection) {
317            this.machineDetection = machineDetection;
318            return this;
319        }
320
321        /**
322         * Configure the behavior of Vonage's advanced machine detection. This overrides the
323         * {@link #machineDetection(MachineDetection)}, so you cannot set both.
324         *
325         * @param advancedMachineDetection The advanced machine detection settings.
326         *
327         * @return This builder.
328         *
329         * @since 7.4.0
330         */
331        public Builder advancedMachineDetection(AdvancedMachineDetection advancedMachineDetection) {
332            this.advancedMachineDetection = advancedMachineDetection;
333            return this;
334        }
335
336        /**
337         * Sets the number of seconds that elapse before Vonage hangs up after the call is answered.
338         *
339         * @param lengthTimer The call length in seconds. The default and maximum is 7200.
340         *
341         * @return This builder.
342         */
343        public Builder lengthTimer(int lengthTimer) {
344            this.lengthTimer = lengthTimer;
345            return this;
346        }
347
348        /**
349         * Sets the number of seconds that elapse before Vonage hangs up after the call state changes to ‘ringing’.
350         *
351         * @param ringingTimer The time to wait whilst ringing in seconds. Maximum is 120, default is 60.
352         *
353         * @return This builder.
354         */
355        public Builder ringingTimer(int ringingTimer) {
356            this.ringingTimer = ringingTimer;
357            return this;
358        }
359
360        /**
361         * Set to @{code true} to use random phone number as the caller. The number will be selected from the list
362         * of the numbers assigned to the current application. Cannot be used if {@link #from(String)} is set.
363         *
364         * @param fromRandomNumber Whether to use a random number instead of {@code from}.
365         *
366         * @return This builder.
367         */
368        public Builder fromRandomNumber(boolean fromRandomNumber) {
369            this.fromRandomNumber = fromRandomNumber;
370            return this;
371        }
372
373        /**
374         * Sets the actions to take on the call.
375         *
376         * @param actions The actions in order.
377         *
378         * @return This builder.
379         */
380        public Builder ncco(Action... actions) {
381            return ncco(Arrays.asList(actions));
382        }
383
384        /**
385         * Sets the actions to take on the call.
386         *
387         * @param nccos The NCCOs to use for this call.
388         *
389         * @return This builder.
390         */
391        public Builder ncco(Collection<? extends Action> nccos) {
392            ncco = new ArrayList<>(nccos);
393            return this;
394        }
395
396        /**
397         * Builds the Call object with this builder's properties.
398         *
399         * @return The constructed Call instance.
400         */
401        public Call build() {
402            return new Call(this);
403        }
404    }
405}