Click To Call with Java and Servlets

Download the Code

Wish your users could get in touch as easily as they can surf? It's your lucky day!

Let's go over the steps necessary to implement click-to-call in a Java and Servlets application.

Click to Call

  1. A website visitor submits a web form with a phone number.
  2. Your web application receives the submission and initiates an HTTP request to Twilio asking to initiate an outbound call.
  3. Twilio receives the request and initiates a call to the user's phone number.
  4. The user picks up the call.
  5. After the call connects, we provide TwiML instructions to connect the user to our sales or support teams.

*Check out how iAdvize uses Twilio Click-to-call to connect online shoppers with customer support representatives.*

What We Will Learn

This tutorial demonstrates how to initialize a call using the Twilio REST API and how to create a call using the TwiML Say verb.

Ready to rock?  Click the button below to continue.

Setup Our Environment

Before we create our click-to-call application we need to setup our environment first.

Let's put our Twilio credentials in a place where our application can access them. For the purposes of this tutorial, we'll store them in environment variables that our application can read.

You can find your Twilio credentials in the console.

Retrieve Your Twilio Credentials

For more instructions on how to run the application refer to the app's readme file.

Loading Code Samples...
Language
SDK Version:
  • env
# Twilio API credentials
# Found at https://www.twilio.com/user/account/voice
export TWILIO_ACCOUNT_SID=ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
export TWILIO_AUTH_TOKEN=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

# Twilio phone number
# Purchase one at https://www.twilio.com/user/account/phone-numbers
export TWILIO_NUMBER=+15552737123
.env.example
Environment Configuration file

.env.example

Next, let's look at making a friendly web form.

The Web Form

Before we can get click-to-call hooked up we'll need a form where the user can enter a phone number.

No need to overthink this; the real goal is to POST the user's and sales team's phone numbers to your controller.

Loading Code Samples...
Language
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Click To Call Tutorial</title>
    <link rel="icon" type="image/png" href="/img/favicon.ico">
    <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css"
          type="text/css">
    <link rel="stylesheet"
          href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap-theme.min.css"
          type="text/css">
    <link rel="stylesheet" media="screen" href="/intl-phone/css/intlTelInput.css"/>
</head>
<body>
<div class="container">
    <h1>Click To Call</h1>

    <p>Click To Call converts your website's users into engaged customers by creating an easy way
        for your customers to contact your sales and support teams right on your website.</p>

    <p>Here's an example of how it's done!</p>
    <hr>
    <div class="row">
        <div class="col-md-12">
            <form id="contactform" role="form" action="#" method="POST">
                <div class="form-group">
                    <h3>Call Sales</h3>

                    <p class="help-block">
                        Are you interested in impressing your friends and confounding your enemies?
                        Enter your phone number below, and our team will contact you right away.
                    </p>
                </div>
                <label>Your number</label>
                <div class="form-group">
                    <input class="form-control" type="text" name="userNumber" id="userNumber"
                           placeholder="(651) 555-7889">
                </div>
                <label>Sales team number</label>
                <div class="form-group">
                    <input class="form-control" type="text" name="salesNumber" id="salesNumber"
                           placeholder="(651) 555-7889">
                </div>
                <button type="submit" class="btn btn-default">Contact Sales</button>
            </form>

        </div>
    </div>
</div>
<!-- /page -->
</body>
<script src="//code.jquery.com/jquery-2.1.4.min.js"></script>
<script src="/intl-phone/js/intlTelInput.js" data-turbolinks-track="true"></script>
<script src="/intl-phone/libphonenumber/build/utils.js" data-turbolinks-track="true"></script>
<script src="/js/clicktocall.js" data-turbolinks-track="true"></script>
</html>
src/main/webapp/index.html
The user facing web form

src/main/webapp/index.html

So what does this form need?

  • An input for the phone number
  • An input for the sales team number
  • A submit button

Since the page doesn't need to render new content after clicking, we decided to implement the POST action via AJAX using jQuery. Let's take a look at that part next.

Submit the Form

To make the click to call feature more seamless we used Ajax to send the form asynchronously.

This code shows one way you could implement this functionality using jQuery:

  • Watch for the user "submitting" the form element
  • Submit the form's data to our controller
  • Let the user know if the submission was successful or not

This is a common implementation of jQuery's $.ajax() method. Notice that we are returning the response message when the call has connected.

Loading Code Samples...
Language
// Execute JavaScript on page load
$(function() {
    $('#userNumber, #salesNumber').intlTelInput({
        responsiveDropdown: true,
        autoFormat: true
    });
    var $form = $('#contactform'),
        $submit = $('#contactform input[type=submit]');

    // Intercept form submission
    $form.on('submit', function(e) {
        // Prevent form submission and repeat clicks
        e.preventDefault();
        $submit.attr('disabled', 'disabled');

        // Submit the form via ajax
        $.ajax({
            url:'/call',
            method:'POST',
            data: $form.serialize()
        }).done(function(data) {
            content = JSON.parse(data);
            alert(content.message);
        }).fail(function() {
            alert('There was a problem calling you - please try again later.');
        }).always(function() {
            $submit.removeAttr('disabled');
        });

    });
});
src/main/webapp/js/clicktocall.js
Submit a form with AJAX

src/main/webapp/js/clicktocall.js

Now that we have the front end done let's build the back end that will receive this data.

Make a Phone Call

Making a phone call using Twilio in Java is very easy.

First we instantiate a client object with our Account SID and Auth Token. This is essentially our Java REST API handler, which we could use to send SMSes (or a myriad of other things). For now, we just need it to get access to a CallFactory which is what we're going to use to create phone calls.

Loading Code Samples...
Language
package com.twilio.clicktocall.servlet;

import com.twilio.clicktocall.exceptions.UndefinedEnvironmentVariableException;
import com.twilio.clicktocall.lib.AppSetup;
import com.twilio.clicktocall.lib.TwilioCallCreator;
import com.twilio.exception.TwilioException;
import com.twilio.http.TwilioRestClient;
import org.json.JSONObject;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;


@WebServlet("/call")
public class CallServlet extends HttpServlet {
    private final TwilioCallCreator twilioCallCreator;
    private final String twilioNumber;

    @SuppressWarnings("unused")
    public CallServlet()
            throws UndefinedEnvironmentVariableException {
        AppSetup appSetup = new AppSetup();
        TwilioRestClient client = new TwilioRestClient.Builder(
                appSetup.getAccountSid(), appSetup.getAuthToken()).build();

        this.twilioNumber = appSetup.getTwilioNumber();
        this.twilioCallCreator = new TwilioCallCreator(client);
    }

    public CallServlet(AppSetup appSetup, TwilioCallCreator twilioCallCreator)
            throws UndefinedEnvironmentVariableException {
        this.twilioNumber = appSetup.getTwilioNumber();
        this.twilioCallCreator = twilioCallCreator;
    }

    /**
     * Method that triggers a call to the specified number in the request
     * @param request incoming servlet request object
     * @param response servlet response object
     * @throws ServletException
     * @throws IOException
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String userNumber = request.getParameter("userNumber");
        String salesNumber = request.getParameter("salesNumber");
        if (userNumber == null || salesNumber == null) {
            response.getOutputStream()
                    .write(getJSONResponse("Both phone numbers need to be provided").getBytes());
            return;
        }

        // Full URL to the end point that will respond with the call TwiML.
        String encodedSalesNumber = URLEncoder.encode(salesNumber, "UTF-8");
        String url = request.getRequestURL().toString().replace(request.getRequestURI(), "");
        url += "/connect/" + encodedSalesNumber;

        try {
            twilioCallCreator.create(this.twilioNumber, userNumber, new URI(url));
        } catch (TwilioException e) {
            String message =
                    "Twilio rest client error: " + e.getMessage() + "\n" +
                    "Remember not to use localhost to access this app, use your ngrok URL";

            response.getOutputStream()
                    .write(getJSONResponse(message).getBytes());
        } catch (URISyntaxException e) {
            e.printStackTrace();
        }

        response.getOutputStream()
                .write(getJSONResponse("Phone call incoming!").getBytes());
    }

    private String getJSONResponse(String message) {
        JSONObject obj = new JSONObject();
        obj.put("message", message);
        obj.put("status", "ok");

        return obj.toString();
    }
}
src/main/java/com/twilio/clicktocall/servlet/CallServlet.java
Trigger a call on an incoming POST

src/main/java/com/twilio/clicktocall/servlet/CallServlet.java

Next we'll use the REST API to make an outgoing phone call.  This requires us to pass a From number, a To number and a URL Parameter that tells Twilio what to do after it connects the call to our user. In this case, Twilio needs to DIAL in the Agent once the call has been placed. We'll discuss this more in future steps.

Validate Twilio's Request

Twilio makes a request to our application when the call is created using the REST API. That means we've got an end point that is publicly available for internet requests.

Since TwiML sometimes contains sensitive information such as phone numbers, we don't want just anybody accessing the TwiML.

That in mind, first we check if the request originated from Twilio before we return any TwiML. We use an instance of RequestValidator and call the validate method using the the required parameters.

Loading Code Samples...
Language
package com.twilio.clicktocall.servlet;

import com.twilio.clicktocall.exceptions.UndefinedEnvironmentVariableException;
import com.twilio.clicktocall.lib.AppSetup;
import com.twilio.security.RequestValidator;
import com.twilio.twiml.*;
import com.twilio.twiml.Number;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

@WebServlet("/connect/*")
public class ConnectServlet extends HttpServlet {
    private RequestValidator requestValidator;
    private ResponseWriter responseWriter;

    @SuppressWarnings("unused")
    public ConnectServlet() throws UndefinedEnvironmentVariableException {
        this.responseWriter = new ResponseWriter();
        this.requestValidator = new RequestValidator(new AppSetup().getAuthToken());
    }

    public ConnectServlet(RequestValidator requestValidator, ResponseWriter responseWriter) {
        this.requestValidator = requestValidator;
        this.responseWriter = responseWriter;
    }

    /**
     * Method that handles /connect request and responds with the TwiML after validating
     * the authenticity of the request
     *
     * @param request  incoming servlet request object
     * @param response servlet response object
     * @throws ServletException
     * @throws IOException
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        if (isValidRequest(request)) {
            String salesNumber = extractSalesNumber(request);
            String r = getXMLResponse(salesNumber);
            responseWriter.writeIn(response, r);
        } else {
            responseWriter.writeIn(response, "Invalid twilio request");
        }
    }

    /**
     * Generates the TwiML with a Say and Hangout verb
     *
     * @param salesNumber
     * @return String with the TwiML
     */
    private String getXMLResponse(String salesNumber) {
        Number number = new Number.Builder(salesNumber).build();
        Dial dial = new Dial.Builder()
                .number(number)
                .build();
        VoiceResponse response = new VoiceResponse.Builder()
                .say(new Say.Builder(
                        "Thanks for contacting our sales department. Our " +
                                "next available representative will take your call.").build())
                .dial(dial)
                .build();

        try {
            return response.toXml();
        } catch (TwiMLException e) {
            e.printStackTrace();
        }

        return "";
    }


    /**
     * Uses TwilioUtils to validate that the incoming request comes from Twilio automated services
     *
     * @param request passed servlet request to extract parameters necessary for validation
     * @return boolean determining validity of the request
     */
    private boolean isValidRequest(HttpServletRequest request) {
        String url = request.getRequestURL().toString();
        Map<String, String> params = new HashMap<>();

        Enumeration<String> names = request.getParameterNames();
        while (names.hasMoreElements()) {
            String currentName = names.nextElement();
            params.put(currentName, request.getParameter(currentName));
        }

        String signature = request.getHeader("X-Twilio-Signature");

        return requestValidator.validate(url, params, signature);
    }

    /**
     * Returns the sales number that was passed as a path parameter
     * @param request
     * @return
     */
    private String extractSalesNumber(HttpServletRequest request) {
        String[] pathElements = request.getPathInfo().split("/");
        if(pathElements.length > 0) {
            return pathElements[pathElements.length - 1];
        }
        return "";
    }

}
src/main/java/com/twilio/clicktocall/servlet/ConnectServlet.java
Validate you are talking to Twilio

src/main/java/com/twilio/clicktocall/servlet/ConnectServlet.java

Now that we know a request originates from Twilio, let's look at crafting the TwiML.

Generating TwiML

TwiML is a set of verbs and nouns written in XML that Twilio reads as instructions.

In this case our instructions inform Twilio to SAY something to the user and then DIAL the support agent's number so the customer can talk to him or her.

In order to make writing TwiML even easier, the helper libraries have methods that generate TwiML for you. We use twilio-java to create a TwiML response that will instruct Twilio to SAY something.

Loading Code Samples...
Language
package com.twilio.clicktocall.servlet;

import com.twilio.clicktocall.exceptions.UndefinedEnvironmentVariableException;
import com.twilio.clicktocall.lib.AppSetup;
import com.twilio.security.RequestValidator;
import com.twilio.twiml.*;
import com.twilio.twiml.Number;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

@WebServlet("/connect/*")
public class ConnectServlet extends HttpServlet {
    private RequestValidator requestValidator;
    private ResponseWriter responseWriter;

    @SuppressWarnings("unused")
    public ConnectServlet() throws UndefinedEnvironmentVariableException {
        this.responseWriter = new ResponseWriter();
        this.requestValidator = new RequestValidator(new AppSetup().getAuthToken());
    }

    public ConnectServlet(RequestValidator requestValidator, ResponseWriter responseWriter) {
        this.requestValidator = requestValidator;
        this.responseWriter = responseWriter;
    }

    /**
     * Method that handles /connect request and responds with the TwiML after validating
     * the authenticity of the request
     *
     * @param request  incoming servlet request object
     * @param response servlet response object
     * @throws ServletException
     * @throws IOException
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        if (isValidRequest(request)) {
            String salesNumber = extractSalesNumber(request);
            String r = getXMLResponse(salesNumber);
            responseWriter.writeIn(response, r);
        } else {
            responseWriter.writeIn(response, "Invalid twilio request");
        }
    }

    /**
     * Generates the TwiML with a Say and Hangout verb
     *
     * @param salesNumber
     * @return String with the TwiML
     */
    private String getXMLResponse(String salesNumber) {
        Number number = new Number.Builder(salesNumber).build();
        Dial dial = new Dial.Builder()
                .number(number)
                .build();
        VoiceResponse response = new VoiceResponse.Builder()
                .say(new Say.Builder(
                        "Thanks for contacting our sales department. Our " +
                                "next available representative will take your call.").build())
                .dial(dial)
                .build();

        try {
            return response.toXml();
        } catch (TwiMLException e) {
            e.printStackTrace();
        }

        return "";
    }


    /**
     * Uses TwilioUtils to validate that the incoming request comes from Twilio automated services
     *
     * @param request passed servlet request to extract parameters necessary for validation
     * @return boolean determining validity of the request
     */
    private boolean isValidRequest(HttpServletRequest request) {
        String url = request.getRequestURL().toString();
        Map<String, String> params = new HashMap<>();

        Enumeration<String> names = request.getParameterNames();
        while (names.hasMoreElements()) {
            String currentName = names.nextElement();
            params.put(currentName, request.getParameter(currentName));
        }

        String signature = request.getHeader("X-Twilio-Signature");

        return requestValidator.validate(url, params, signature);
    }

    /**
     * Returns the sales number that was passed as a path parameter
     * @param request
     * @return
     */
    private String extractSalesNumber(HttpServletRequest request) {
        String[] pathElements = request.getPathInfo().split("/");
        if(pathElements.length > 0) {
            return pathElements[pathElements.length - 1];
        }
        return "";
    }

}
src/main/java/com/twilio/clicktocall/servlet/ConnectServlet.java
Generate proper TwiML

src/main/java/com/twilio/clicktocall/servlet/ConnectServlet.java

And that's a wrap!  You've now helped us get a working click-to-call form on our web page and are ready to integrate it into your own application.

On the next pane, we'll look at some other tutorials you're sure to enjoy.

Where to Next?

We love Java here at Twilio!  Here are a couple of tutorials that prove it:

Automated Survey

Instantly collect structured data from your users with a survey conducted over a voice call or SMS text messages.

SMS and MMS Notifications

Send SMS alerts to a list of system administrators when something goes wrong on your server.

Did this help?

Thanks for checking this tutorial out! Tweet to us @twilio and let us know what you're building!

Paul Kamp
Andrew Baker
Agustin Camino

Need some help?

We all do sometimes; code is hard. Get help now from our support team, or lean on the wisdom of the crowd browsing the Twilio tag on Stack Overflow.

1 / 1
Loading Code Samples...
SDK Version:
  • env
# Twilio API credentials
# Found at https://www.twilio.com/user/account/voice
export TWILIO_ACCOUNT_SID=ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
export TWILIO_AUTH_TOKEN=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

# Twilio phone number
# Purchase one at https://www.twilio.com/user/account/phone-numbers
export TWILIO_NUMBER=+15552737123
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Click To Call Tutorial</title>
    <link rel="icon" type="image/png" href="/img/favicon.ico">
    <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css"
          type="text/css">
    <link rel="stylesheet"
          href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap-theme.min.css"
          type="text/css">
    <link rel="stylesheet" media="screen" href="/intl-phone/css/intlTelInput.css"/>
</head>
<body>
<div class="container">
    <h1>Click To Call</h1>

    <p>Click To Call converts your website's users into engaged customers by creating an easy way
        for your customers to contact your sales and support teams right on your website.</p>

    <p>Here's an example of how it's done!</p>
    <hr>
    <div class="row">
        <div class="col-md-12">
            <form id="contactform" role="form" action="#" method="POST">
                <div class="form-group">
                    <h3>Call Sales</h3>

                    <p class="help-block">
                        Are you interested in impressing your friends and confounding your enemies?
                        Enter your phone number below, and our team will contact you right away.
                    </p>
                </div>
                <label>Your number</label>
                <div class="form-group">
                    <input class="form-control" type="text" name="userNumber" id="userNumber"
                           placeholder="(651) 555-7889">
                </div>
                <label>Sales team number</label>
                <div class="form-group">
                    <input class="form-control" type="text" name="salesNumber" id="salesNumber"
                           placeholder="(651) 555-7889">
                </div>
                <button type="submit" class="btn btn-default">Contact Sales</button>
            </form>

        </div>
    </div>
</div>
<!-- /page -->
</body>
<script src="//code.jquery.com/jquery-2.1.4.min.js"></script>
<script src="/intl-phone/js/intlTelInput.js" data-turbolinks-track="true"></script>
<script src="/intl-phone/libphonenumber/build/utils.js" data-turbolinks-track="true"></script>
<script src="/js/clicktocall.js" data-turbolinks-track="true"></script>
</html>
// Execute JavaScript on page load
$(function() {
    $('#userNumber, #salesNumber').intlTelInput({
        responsiveDropdown: true,
        autoFormat: true
    });
    var $form = $('#contactform'),
        $submit = $('#contactform input[type=submit]');

    // Intercept form submission
    $form.on('submit', function(e) {
        // Prevent form submission and repeat clicks
        e.preventDefault();
        $submit.attr('disabled', 'disabled');

        // Submit the form via ajax
        $.ajax({
            url:'/call',
            method:'POST',
            data: $form.serialize()
        }).done(function(data) {
            content = JSON.parse(data);
            alert(content.message);
        }).fail(function() {
            alert('There was a problem calling you - please try again later.');
        }).always(function() {
            $submit.removeAttr('disabled');
        });

    });
});
package com.twilio.clicktocall.servlet;

import com.twilio.clicktocall.exceptions.UndefinedEnvironmentVariableException;
import com.twilio.clicktocall.lib.AppSetup;
import com.twilio.clicktocall.lib.TwilioCallCreator;
import com.twilio.exception.TwilioException;
import com.twilio.http.TwilioRestClient;
import org.json.JSONObject;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;


@WebServlet("/call")
public class CallServlet extends HttpServlet {
    private final TwilioCallCreator twilioCallCreator;
    private final String twilioNumber;

    @SuppressWarnings("unused")
    public CallServlet()
            throws UndefinedEnvironmentVariableException {
        AppSetup appSetup = new AppSetup();
        TwilioRestClient client = new TwilioRestClient.Builder(
                appSetup.getAccountSid(), appSetup.getAuthToken()).build();

        this.twilioNumber = appSetup.getTwilioNumber();
        this.twilioCallCreator = new TwilioCallCreator(client);
    }

    public CallServlet(AppSetup appSetup, TwilioCallCreator twilioCallCreator)
            throws UndefinedEnvironmentVariableException {
        this.twilioNumber = appSetup.getTwilioNumber();
        this.twilioCallCreator = twilioCallCreator;
    }

    /**
     * Method that triggers a call to the specified number in the request
     * @param request incoming servlet request object
     * @param response servlet response object
     * @throws ServletException
     * @throws IOException
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String userNumber = request.getParameter("userNumber");
        String salesNumber = request.getParameter("salesNumber");
        if (userNumber == null || salesNumber == null) {
            response.getOutputStream()
                    .write(getJSONResponse("Both phone numbers need to be provided").getBytes());
            return;
        }

        // Full URL to the end point that will respond with the call TwiML.
        String encodedSalesNumber = URLEncoder.encode(salesNumber, "UTF-8");
        String url = request.getRequestURL().toString().replace(request.getRequestURI(), "");
        url += "/connect/" + encodedSalesNumber;

        try {
            twilioCallCreator.create(this.twilioNumber, userNumber, new URI(url));
        } catch (TwilioException e) {
            String message =
                    "Twilio rest client error: " + e.getMessage() + "\n" +
                    "Remember not to use localhost to access this app, use your ngrok URL";

            response.getOutputStream()
                    .write(getJSONResponse(message).getBytes());
        } catch (URISyntaxException e) {
            e.printStackTrace();
        }

        response.getOutputStream()
                .write(getJSONResponse("Phone call incoming!").getBytes());
    }

    private String getJSONResponse(String message) {
        JSONObject obj = new JSONObject();
        obj.put("message", message);
        obj.put("status", "ok");

        return obj.toString();
    }
}
package com.twilio.clicktocall.servlet;

import com.twilio.clicktocall.exceptions.UndefinedEnvironmentVariableException;
import com.twilio.clicktocall.lib.AppSetup;
import com.twilio.security.RequestValidator;
import com.twilio.twiml.*;
import com.twilio.twiml.Number;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

@WebServlet("/connect/*")
public class ConnectServlet extends HttpServlet {
    private RequestValidator requestValidator;
    private ResponseWriter responseWriter;

    @SuppressWarnings("unused")
    public ConnectServlet() throws UndefinedEnvironmentVariableException {
        this.responseWriter = new ResponseWriter();
        this.requestValidator = new RequestValidator(new AppSetup().getAuthToken());
    }

    public ConnectServlet(RequestValidator requestValidator, ResponseWriter responseWriter) {
        this.requestValidator = requestValidator;
        this.responseWriter = responseWriter;
    }

    /**
     * Method that handles /connect request and responds with the TwiML after validating
     * the authenticity of the request
     *
     * @param request  incoming servlet request object
     * @param response servlet response object
     * @throws ServletException
     * @throws IOException
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        if (isValidRequest(request)) {
            String salesNumber = extractSalesNumber(request);
            String r = getXMLResponse(salesNumber);
            responseWriter.writeIn(response, r);
        } else {
            responseWriter.writeIn(response, "Invalid twilio request");
        }
    }

    /**
     * Generates the TwiML with a Say and Hangout verb
     *
     * @param salesNumber
     * @return String with the TwiML
     */
    private String getXMLResponse(String salesNumber) {
        Number number = new Number.Builder(salesNumber).build();
        Dial dial = new Dial.Builder()
                .number(number)
                .build();
        VoiceResponse response = new VoiceResponse.Builder()
                .say(new Say.Builder(
                        "Thanks for contacting our sales department. Our " +
                                "next available representative will take your call.").build())
                .dial(dial)
                .build();

        try {
            return response.toXml();
        } catch (TwiMLException e) {
            e.printStackTrace();
        }

        return "";
    }


    /**
     * Uses TwilioUtils to validate that the incoming request comes from Twilio automated services
     *
     * @param request passed servlet request to extract parameters necessary for validation
     * @return boolean determining validity of the request
     */
    private boolean isValidRequest(HttpServletRequest request) {
        String url = request.getRequestURL().toString();
        Map<String, String> params = new HashMap<>();

        Enumeration<String> names = request.getParameterNames();
        while (names.hasMoreElements()) {
            String currentName = names.nextElement();
            params.put(currentName, request.getParameter(currentName));
        }

        String signature = request.getHeader("X-Twilio-Signature");

        return requestValidator.validate(url, params, signature);
    }

    /**
     * Returns the sales number that was passed as a path parameter
     * @param request
     * @return
     */
    private String extractSalesNumber(HttpServletRequest request) {
        String[] pathElements = request.getPathInfo().split("/");
        if(pathElements.length > 0) {
            return pathElements[pathElements.length - 1];
        }
        return "";
    }

}
package com.twilio.clicktocall.servlet;

import com.twilio.clicktocall.exceptions.UndefinedEnvironmentVariableException;
import com.twilio.clicktocall.lib.AppSetup;
import com.twilio.security.RequestValidator;
import com.twilio.twiml.*;
import com.twilio.twiml.Number;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

@WebServlet("/connect/*")
public class ConnectServlet extends HttpServlet {
    private RequestValidator requestValidator;
    private ResponseWriter responseWriter;

    @SuppressWarnings("unused")
    public ConnectServlet() throws UndefinedEnvironmentVariableException {
        this.responseWriter = new ResponseWriter();
        this.requestValidator = new RequestValidator(new AppSetup().getAuthToken());
    }

    public ConnectServlet(RequestValidator requestValidator, ResponseWriter responseWriter) {
        this.requestValidator = requestValidator;
        this.responseWriter = responseWriter;
    }

    /**
     * Method that handles /connect request and responds with the TwiML after validating
     * the authenticity of the request
     *
     * @param request  incoming servlet request object
     * @param response servlet response object
     * @throws ServletException
     * @throws IOException
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        if (isValidRequest(request)) {
            String salesNumber = extractSalesNumber(request);
            String r = getXMLResponse(salesNumber);
            responseWriter.writeIn(response, r);
        } else {
            responseWriter.writeIn(response, "Invalid twilio request");
        }
    }

    /**
     * Generates the TwiML with a Say and Hangout verb
     *
     * @param salesNumber
     * @return String with the TwiML
     */
    private String getXMLResponse(String salesNumber) {
        Number number = new Number.Builder(salesNumber).build();
        Dial dial = new Dial.Builder()
                .number(number)
                .build();
        VoiceResponse response = new VoiceResponse.Builder()
                .say(new Say.Builder(
                        "Thanks for contacting our sales department. Our " +
                                "next available representative will take your call.").build())
                .dial(dial)
                .build();

        try {
            return response.toXml();
        } catch (TwiMLException e) {
            e.printStackTrace();
        }

        return "";
    }


    /**
     * Uses TwilioUtils to validate that the incoming request comes from Twilio automated services
     *
     * @param request passed servlet request to extract parameters necessary for validation
     * @return boolean determining validity of the request
     */
    private boolean isValidRequest(HttpServletRequest request) {
        String url = request.getRequestURL().toString();
        Map<String, String> params = new HashMap<>();

        Enumeration<String> names = request.getParameterNames();
        while (names.hasMoreElements()) {
            String currentName = names.nextElement();
            params.put(currentName, request.getParameter(currentName));
        }

        String signature = request.getHeader("X-Twilio-Signature");

        return requestValidator.validate(url, params, signature);
    }

    /**
     * Returns the sales number that was passed as a path parameter
     * @param request
     * @return
     */
    private String extractSalesNumber(HttpServletRequest request) {
        String[] pathElements = request.getPathInfo().split("/");
        if(pathElements.length > 0) {
            return pathElements[pathElements.length - 1];
        }
        return "";
    }

}