Call Forwarding with Java and Spark

 CallForward landing page 

About this Application

This Spark application uses Twilio to connect incoming phone calls to other phone numbers based on where the caller lives.  When a user dials in, we look up some information based upon their assumed location and trigger actions inside our application.

Your business might use this functionality to automatically route your customers to a regional office or to direct callers to a survey after their interaction with customer service. Our sample application connects callers to the offices of their U.S. senators.

Communication can be a powerful force for change. We’ve seen civic engagement rise as tools for civic engagement become increasingly available to an internet-connected and mobile society. In November of 2016, Emily Ellsworth shared some tips and tricks for getting your Congressperson’s attention with one big takeaway: calling works.

To run this sample app yourself, download the code and follow the README instructions on Github.

Let’s get started!

Configure your Twilio Application

For this application, we’ll be using the Twilio Java Helper Library to help us interact with the Twilio API. Our first step is to set up a Twilio account and the Sinatra application itself.

You’ll need to get a voice-capable Twilio phone number if you don’t already have one.

We’ve provided a sample set of data that can be loaded into your local database for testing and development. In our dataset, we’ve mapped states to senators, and we’ve mapped the senators to a few Twilio phone numbers for testing rather than actual senator phone numbers. Please note: this data set will likely be out of date by the time you use it, so we recommend you roll your own if you want to get the application production-ready.

Loading Code Samples...
Language
package com.twilio.callforwarding.db;

import com.twilio.callforwarding.models.Senator;
import com.twilio.callforwarding.models.State;
import com.twilio.callforwarding.models.Zipcode;
import com.twilio.callforwarding.repositories.SenatorRepository;
import com.twilio.callforwarding.repositories.StateRepository;
import com.twilio.callforwarding.repositories.ZipcodeRepository;

import javax.json.Json;
import javax.json.JsonObject;
import javax.json.JsonString;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class DbSeedHelper {

    private SenatorRepository senatorRepository;
    private StateRepository stateRepository;
    private ZipcodeRepository zipcodeRepository;

    public DbSeedHelper() {
        this.senatorRepository = new SenatorRepository();
        this.stateRepository = new StateRepository();
        this.zipcodeRepository = new ZipcodeRepository();
    }

    public void seedZipcodes() {
        URL zipcodesResource = this.getClass().getResource("/seed/free-zipcode-database.csv");
        InsertBatch<Zipcode> batch = new InsertBatch<>(b -> zipcodeRepository.bulkCreate(b));
        try(Stream<String> stream = Files.lines(Paths.get(zipcodesResource.toURI()))) {
            stream.map(line -> Arrays.stream(line.split(","))
                                     .map(p -> p.replace("\"", ""))
                                     .collect(Collectors.toList()))
                .filter(lineParts -> lineParts.get(0).matches("^[0-9]+$"))
                .forEach(lineParts -> {
                    Integer zipcodeNumber = Integer.valueOf(lineParts.get(0));
                    Zipcode zipcode = new Zipcode(zipcodeNumber, lineParts.get(3));
                    batch.add(zipcode);
                });
            batch.flush();
        } catch (IOException|URISyntaxException e) {
            e.printStackTrace();
        }
    }

    public void seedStatesAndSenators() {
        URL senatorsResource = this.getClass().getResource("/seed/senators.json");
        try {
            InputStream senatorsInputStream = Files.newInputStream(Paths.get(senatorsResource.toURI()));
            JsonObject root = Json.createReader(senatorsInputStream).readObject();

            List<JsonString> states = root.getJsonArray("states")
                                          .getValuesAs(JsonString.class);
            states.stream()
                .filter(state -> root.containsKey(state.getString()))
                .forEach(stateJsonObject -> {
                    String stateName = stateJsonObject.getString();
                    State persistedState = stateRepository.create(new State(stateName));
                    root.getJsonArray(stateName)
                            .getValuesAs(JsonObject.class)
                            .stream()
                            .forEach(senatorJson -> {
                                persistSenator(persistedState, senatorJson);
                            });
                });
        } catch (IOException|URISyntaxException|NullPointerException e) {
            e.printStackTrace();
        }
    }

    private void persistSenator(State persistedState, JsonObject senatorJson) {
        Senator senator = new Senator(
                senatorJson.getString("name"),
                senatorJson.getString("phone"),
                persistedState);
        senatorRepository.create(senator);
    }

    public void seedDb() {
        long countZipcode = zipcodeRepository.count();
        if(countZipcode == 0) {
            seedZipcodes();
        }
        long countSenator = senatorRepository.count();
        if(countSenator == 0) {
            seedStatesAndSenators();
        }
    }
}

class InsertBatch<T> {
    private List<T> batch;
    private Consumer<List<T>> action;

    public InsertBatch(Consumer<List<T>> action) {
        this.action = action;
        this.batch = new ArrayList<>();
    }

    public void add(T element) {
        batch.add(element);
        if(batch.size() > 1000) {
            flush();
        }
    }

    public void flush() {
        action.accept(batch);
        batch = new ArrayList<>();
    }
}
This class parses the seed files and insert the data on DB.
Seed Database with Sample Data

This class parses the seed files and insert the data on DB.

The last piece of the configuration puzzle is to create a webhook endpoint for our application to accept inbound calls. Once your database is configured and your app is up and running, go to the Twilio phone number you wish to use and configure the Voice URL to point to your application. In our sample code, the route is `/callcongress/welcome`.

Twilio webhook configuration

We recommend using ngrok to expose your local development environment to Twilio.

Handle the Inbound Twilio Request

Our Twilio number is now configured to send HTTP requests to the /welcome endpoint on all incoming voice calls. This Twilio request arrives with some useful parameters. For our use case, we’re most concerned with fromState, as it will help us make a best guess at our caller’s state of residence.

Loading Code Samples...
Language
package com.twilio.callforwarding.controllers;

import com.twilio.callforwarding.models.Senator;
import com.twilio.callforwarding.models.Zipcode;
import com.twilio.callforwarding.repositories.SenatorRepository;
import com.twilio.callforwarding.repositories.StateRepository;
import com.twilio.callforwarding.repositories.ZipcodeRepository;
import com.twilio.twiml.*;
import com.twilio.twiml.Number;
import spark.Route;
import spark.utils.StringUtils;

import java.util.List;

public class CallCongressController {

    private static final String APPLICATION_XML = "application/xml";
    private SenatorRepository senatorRepository;
    private StateRepository stateRepository;
    private ZipcodeRepository zipcodeRepository;

    public CallCongressController(SenatorRepository senatorRepository, StateRepository stateRepository, ZipcodeRepository zipcodeRepository) {
        this.senatorRepository = senatorRepository;
        this.stateRepository = stateRepository;
        this.zipcodeRepository = zipcodeRepository;
    }

    public CallCongressController() {
        this.senatorRepository = new SenatorRepository();
        this.stateRepository = new StateRepository();
        this.zipcodeRepository = new ZipcodeRepository();
    }

    // Verify or collect State information.
    public Route welcomeRoute = (request, response) -> {
        String fromState = request.params("FromState");

        VoiceResponse.Builder builder = new VoiceResponse.Builder();
        if (StringUtils.isNotBlank(fromState)) {
            builder.say(new Say.Builder(
                    String.format("Thank you for calling congress! It looks like" +
                            "you\'re calling from %s." +
                            "If this is correct, please press 1. Press 2 if" +
                            "this is not your current state of residence.", fromState)).build());
            builder.gather(new Gather.Builder()
                    .numDigits(1)
                    .action("/callcongress/set-state")
                    .method(Method.POST)
                    .build());
        } else {
            builder.say(new Say.Builder(
                    "Thank you for calling Call Congress! If you wish to" +
                            "call your senators, please enter your 5 - digit zip code.").build());
            builder.gather(new Gather.Builder()
                    .numDigits(5)
                    .action("/callcongress/state-lookup")
                    .method(Method.POST)
                    .build());
        }
        response.type(APPLICATION_XML);
        return builder.build().toXml();
    };

    // If our state guess is wrong, prompt user for zip code.
    public Route collectZipRoute = (request, response) -> {
        VoiceResponse.Builder builder = new VoiceResponse.Builder();
        builder.say(new Say.Builder("If you wish to call your senators, please " +
                "enter your 5-digit zip code.").build());
        builder.gather(new Gather.Builder()
                .numDigits(5)
                .action("/callcongress/state-lookup")
                .method(Method.POST)
                .build());

        response.type(APPLICATION_XML);
        return builder.build().toXml();
    };

    // Look up state from given zipcode.
    // Once state is found, redirect to call_senators for forwarding.
    public Route stateLookupRoute = (request, response) -> {
        String zipcode = request.queryParams("Digits");

        // NB: We don't do any error handling for a missing/erroneous zip code
        // in this sample application. You, gentle reader, should to handle that
        // edge case before deploying this code.
        response.type("application/xml");
        Zipcode zipcodeObject = zipcodeRepository.getFirstResultFilteredByZipcode(zipcode);

        response.redirect("/callcongress/call-senators/" + zipcodeObject.getState());
        return "";
    };

    // Set state for senator call list.
    // Set user's state from confirmation or user-provided Zip.
    // Redirect to call_senators route.
    public Route setStateRoute = (request, response) -> {
        // Get the digit pressed by the user
        String digits = request.params("Digits");

        if(digits.equals("1")) {
            String callerState = request.params("CallerState");
            response.redirect("/callcongress/call-senators/" + callerState);
        } else {
            response.redirect("/callcongress/collect-zip");
        }
        return "";
    };

    // Route for connecting caller to both of their senators.
    public Route callSenatorsRoute = (request, response) -> {
        String state = request.params("state");

        List<Senator> senators = stateRepository
                .findByStateName(state)
                .getSenators();

        VoiceResponse.Builder builder = new VoiceResponse.Builder();
        Senator firstCall = senators.get(0);
        Senator secondCall = senators.get(1);
        String sayMessage = String.format("Connecting you to %s. " +
                        "After the senator's office ends the call, you will " +
                        "be re-directed to %s.",
                firstCall.getName(),
                secondCall.getName());
        builder.say(new Say.Builder(sayMessage).build());
        builder.dial(new Dial.Builder()
                .number(new Number.Builder(firstCall.getPhone()).build())
                .action("/callcongress/call-second-senator/" + secondCall.getId())
                .build());

        response.type(APPLICATION_XML);
        return builder.build().toXml();
    };

    // Forward the caller to their second senator.
    public Route callSecondSenatorRoute = (request, response) -> {
        Senator senator = senatorRepository.find(Long.valueOf(request.params("senatorId")));
        VoiceResponse.Builder builder = new VoiceResponse.Builder();

        String sayMessage = String.format("Connecting you to %s.", senator.getName());
        builder.say(new Say.Builder(sayMessage).build());

        builder.dial(new Dial.Builder()
                .number(new Number.Builder(senator.getPhone()).build())
                .action("/callcongress/goodbye")
                .build());

        response.type(APPLICATION_XML);
        return builder.build().toXml();
    };

    // Thank user & hang up.
    public Route goodbyeRoute = (request, response) -> {
        VoiceResponse.Builder builder = new VoiceResponse.Builder();
        builder.say(new Say.Builder("Thank you for using Call Congress! " +
                "Your voice makes a difference. Goodbye.").build());
        builder.hangup(new Hangup());

        response.type(APPLICATION_XML);
        return builder.build().toXml();
    };
}
Takes the fromState parameter from request if it's present.
Welcome route: getting data from the request

Takes the fromState parameter from request if it's present.

In order to welcome our caller and properly direct them to their senators’ offices, we need to build out a response with TwiML.

Build the TwiML Response

Since the fromState parameter comes from the user’s phone rather than their actual geolocation, we want to make sure that we direct the caller to their state of residence. Let’s break down how we build out a response that both welcomes the caller and asks for confirmation of their state of residence.

Loading Code Samples...
Language
package com.twilio.callforwarding.controllers;

import com.twilio.callforwarding.models.Senator;
import com.twilio.callforwarding.models.Zipcode;
import com.twilio.callforwarding.repositories.SenatorRepository;
import com.twilio.callforwarding.repositories.StateRepository;
import com.twilio.callforwarding.repositories.ZipcodeRepository;
import com.twilio.twiml.*;
import com.twilio.twiml.Number;
import spark.Route;
import spark.utils.StringUtils;

import java.util.List;

public class CallCongressController {

    private static final String APPLICATION_XML = "application/xml";
    private SenatorRepository senatorRepository;
    private StateRepository stateRepository;
    private ZipcodeRepository zipcodeRepository;

    public CallCongressController(SenatorRepository senatorRepository, StateRepository stateRepository, ZipcodeRepository zipcodeRepository) {
        this.senatorRepository = senatorRepository;
        this.stateRepository = stateRepository;
        this.zipcodeRepository = zipcodeRepository;
    }

    public CallCongressController() {
        this.senatorRepository = new SenatorRepository();
        this.stateRepository = new StateRepository();
        this.zipcodeRepository = new ZipcodeRepository();
    }

    // Verify or collect State information.
    public Route welcomeRoute = (request, response) -> {
        String fromState = request.params("FromState");

        VoiceResponse.Builder builder = new VoiceResponse.Builder();
        if (StringUtils.isNotBlank(fromState)) {
            builder.say(new Say.Builder(
                    String.format("Thank you for calling congress! It looks like" +
                            "you\'re calling from %s." +
                            "If this is correct, please press 1. Press 2 if" +
                            "this is not your current state of residence.", fromState)).build());
            builder.gather(new Gather.Builder()
                    .numDigits(1)
                    .action("/callcongress/set-state")
                    .method(Method.POST)
                    .build());
        } else {
            builder.say(new Say.Builder(
                    "Thank you for calling Call Congress! If you wish to" +
                            "call your senators, please enter your 5 - digit zip code.").build());
            builder.gather(new Gather.Builder()
                    .numDigits(5)
                    .action("/callcongress/state-lookup")
                    .method(Method.POST)
                    .build());
        }
        response.type(APPLICATION_XML);
        return builder.build().toXml();
    };

    // If our state guess is wrong, prompt user for zip code.
    public Route collectZipRoute = (request, response) -> {
        VoiceResponse.Builder builder = new VoiceResponse.Builder();
        builder.say(new Say.Builder("If you wish to call your senators, please " +
                "enter your 5-digit zip code.").build());
        builder.gather(new Gather.Builder()
                .numDigits(5)
                .action("/callcongress/state-lookup")
                .method(Method.POST)
                .build());

        response.type(APPLICATION_XML);
        return builder.build().toXml();
    };

    // Look up state from given zipcode.
    // Once state is found, redirect to call_senators for forwarding.
    public Route stateLookupRoute = (request, response) -> {
        String zipcode = request.queryParams("Digits");

        // NB: We don't do any error handling for a missing/erroneous zip code
        // in this sample application. You, gentle reader, should to handle that
        // edge case before deploying this code.
        response.type("application/xml");
        Zipcode zipcodeObject = zipcodeRepository.getFirstResultFilteredByZipcode(zipcode);

        response.redirect("/callcongress/call-senators/" + zipcodeObject.getState());
        return "";
    };

    // Set state for senator call list.
    // Set user's state from confirmation or user-provided Zip.
    // Redirect to call_senators route.
    public Route setStateRoute = (request, response) -> {
        // Get the digit pressed by the user
        String digits = request.params("Digits");

        if(digits.equals("1")) {
            String callerState = request.params("CallerState");
            response.redirect("/callcongress/call-senators/" + callerState);
        } else {
            response.redirect("/callcongress/collect-zip");
        }
        return "";
    };

    // Route for connecting caller to both of their senators.
    public Route callSenatorsRoute = (request, response) -> {
        String state = request.params("state");

        List<Senator> senators = stateRepository
                .findByStateName(state)
                .getSenators();

        VoiceResponse.Builder builder = new VoiceResponse.Builder();
        Senator firstCall = senators.get(0);
        Senator secondCall = senators.get(1);
        String sayMessage = String.format("Connecting you to %s. " +
                        "After the senator's office ends the call, you will " +
                        "be re-directed to %s.",
                firstCall.getName(),
                secondCall.getName());
        builder.say(new Say.Builder(sayMessage).build());
        builder.dial(new Dial.Builder()
                .number(new Number.Builder(firstCall.getPhone()).build())
                .action("/callcongress/call-second-senator/" + secondCall.getId())
                .build());

        response.type(APPLICATION_XML);
        return builder.build().toXml();
    };

    // Forward the caller to their second senator.
    public Route callSecondSenatorRoute = (request, response) -> {
        Senator senator = senatorRepository.find(Long.valueOf(request.params("senatorId")));
        VoiceResponse.Builder builder = new VoiceResponse.Builder();

        String sayMessage = String.format("Connecting you to %s.", senator.getName());
        builder.say(new Say.Builder(sayMessage).build());

        builder.dial(new Dial.Builder()
                .number(new Number.Builder(senator.getPhone()).build())
                .action("/callcongress/goodbye")
                .build());

        response.type(APPLICATION_XML);
        return builder.build().toXml();
    };

    // Thank user & hang up.
    public Route goodbyeRoute = (request, response) -> {
        VoiceResponse.Builder builder = new VoiceResponse.Builder();
        builder.say(new Say.Builder("Thank you for using Call Congress! " +
                "Your voice makes a difference. Goodbye.").build());
        builder.hangup(new Hangup());

        response.type(APPLICATION_XML);
        return builder.build().toXml();
    };
}
Ask the caller to confirm that the state parameter is correct.
Build response: say hello and gather information

Ask the caller to confirm that the state parameter is correct.

We’ll start our TwiML response by reading a welcome message to the caller with <Say>. Then we use <Gather> to ask the user to confirm their state of residence by pressing 1 or 2.

Once Twilio gathers this information, it will POST the caller’s input to our route specified on the action parameter so that we can better route the user through our application.

Handle a Missing State

If for some reason the inbound request to Twilio doesn’t contain a fromState value, we need to get a little more information from the caller before we proceed.

Loading Code Samples...
Language
package com.twilio.callforwarding.controllers;

import com.twilio.callforwarding.models.Senator;
import com.twilio.callforwarding.models.Zipcode;
import com.twilio.callforwarding.repositories.SenatorRepository;
import com.twilio.callforwarding.repositories.StateRepository;
import com.twilio.callforwarding.repositories.ZipcodeRepository;
import com.twilio.twiml.*;
import com.twilio.twiml.Number;
import spark.Route;
import spark.utils.StringUtils;

import java.util.List;

public class CallCongressController {

    private static final String APPLICATION_XML = "application/xml";
    private SenatorRepository senatorRepository;
    private StateRepository stateRepository;
    private ZipcodeRepository zipcodeRepository;

    public CallCongressController(SenatorRepository senatorRepository, StateRepository stateRepository, ZipcodeRepository zipcodeRepository) {
        this.senatorRepository = senatorRepository;
        this.stateRepository = stateRepository;
        this.zipcodeRepository = zipcodeRepository;
    }

    public CallCongressController() {
        this.senatorRepository = new SenatorRepository();
        this.stateRepository = new StateRepository();
        this.zipcodeRepository = new ZipcodeRepository();
    }

    // Verify or collect State information.
    public Route welcomeRoute = (request, response) -> {
        String fromState = request.params("FromState");

        VoiceResponse.Builder builder = new VoiceResponse.Builder();
        if (StringUtils.isNotBlank(fromState)) {
            builder.say(new Say.Builder(
                    String.format("Thank you for calling congress! It looks like" +
                            "you\'re calling from %s." +
                            "If this is correct, please press 1. Press 2 if" +
                            "this is not your current state of residence.", fromState)).build());
            builder.gather(new Gather.Builder()
                    .numDigits(1)
                    .action("/callcongress/set-state")
                    .method(Method.POST)
                    .build());
        } else {
            builder.say(new Say.Builder(
                    "Thank you for calling Call Congress! If you wish to" +
                            "call your senators, please enter your 5 - digit zip code.").build());
            builder.gather(new Gather.Builder()
                    .numDigits(5)
                    .action("/callcongress/state-lookup")
                    .method(Method.POST)
                    .build());
        }
        response.type(APPLICATION_XML);
        return builder.build().toXml();
    };

    // If our state guess is wrong, prompt user for zip code.
    public Route collectZipRoute = (request, response) -> {
        VoiceResponse.Builder builder = new VoiceResponse.Builder();
        builder.say(new Say.Builder("If you wish to call your senators, please " +
                "enter your 5-digit zip code.").build());
        builder.gather(new Gather.Builder()
                .numDigits(5)
                .action("/callcongress/state-lookup")
                .method(Method.POST)
                .build());

        response.type(APPLICATION_XML);
        return builder.build().toXml();
    };

    // Look up state from given zipcode.
    // Once state is found, redirect to call_senators for forwarding.
    public Route stateLookupRoute = (request, response) -> {
        String zipcode = request.queryParams("Digits");

        // NB: We don't do any error handling for a missing/erroneous zip code
        // in this sample application. You, gentle reader, should to handle that
        // edge case before deploying this code.
        response.type("application/xml");
        Zipcode zipcodeObject = zipcodeRepository.getFirstResultFilteredByZipcode(zipcode);

        response.redirect("/callcongress/call-senators/" + zipcodeObject.getState());
        return "";
    };

    // Set state for senator call list.
    // Set user's state from confirmation or user-provided Zip.
    // Redirect to call_senators route.
    public Route setStateRoute = (request, response) -> {
        // Get the digit pressed by the user
        String digits = request.params("Digits");

        if(digits.equals("1")) {
            String callerState = request.params("CallerState");
            response.redirect("/callcongress/call-senators/" + callerState);
        } else {
            response.redirect("/callcongress/collect-zip");
        }
        return "";
    };

    // Route for connecting caller to both of their senators.
    public Route callSenatorsRoute = (request, response) -> {
        String state = request.params("state");

        List<Senator> senators = stateRepository
                .findByStateName(state)
                .getSenators();

        VoiceResponse.Builder builder = new VoiceResponse.Builder();
        Senator firstCall = senators.get(0);
        Senator secondCall = senators.get(1);
        String sayMessage = String.format("Connecting you to %s. " +
                        "After the senator's office ends the call, you will " +
                        "be re-directed to %s.",
                firstCall.getName(),
                secondCall.getName());
        builder.say(new Say.Builder(sayMessage).build());
        builder.dial(new Dial.Builder()
                .number(new Number.Builder(firstCall.getPhone()).build())
                .action("/callcongress/call-second-senator/" + secondCall.getId())
                .build());

        response.type(APPLICATION_XML);
        return builder.build().toXml();
    };

    // Forward the caller to their second senator.
    public Route callSecondSenatorRoute = (request, response) -> {
        Senator senator = senatorRepository.find(Long.valueOf(request.params("senatorId")));
        VoiceResponse.Builder builder = new VoiceResponse.Builder();

        String sayMessage = String.format("Connecting you to %s.", senator.getName());
        builder.say(new Say.Builder(sayMessage).build());

        builder.dial(new Dial.Builder()
                .number(new Number.Builder(senator.getPhone()).build())
                .action("/callcongress/goodbye")
                .build());

        response.type(APPLICATION_XML);
        return builder.build().toXml();
    };

    // Thank user & hang up.
    public Route goodbyeRoute = (request, response) -> {
        VoiceResponse.Builder builder = new VoiceResponse.Builder();
        builder.say(new Say.Builder("Thank you for using Call Congress! " +
                "Your voice makes a difference. Goodbye.").build());
        builder.hangup(new Hangup());

        response.type(APPLICATION_XML);
        return builder.build().toXml();
    };
}
If the fromState parameter is not present, ask the user to provide their zipcode.
Request zip code from caller

If the fromState parameter is not present, ask the user to provide their zipcode.

This code should look familiar to you. If we don’t detect a fromState we utilize <Gather> as we <Say> a message that asks for the caller’s zip code. This time we accept 5 digits (the length of a zip code) and trigger a state lookup by zip code.

Connect the Caller to their First Senator

Now that we know our caller’s state of residence, we can look up their senators and forward the call to the appropriate phone number.

Similar to the previous route, we <Say> a brief message to tell the caller that they’re being connected to a senator. Then, we <Dial> the first senator, making sure to add an action that will route the caller back to our application when the first call ends.

Loading Code Samples...
Language
package com.twilio.callforwarding.controllers;

import com.twilio.callforwarding.models.Senator;
import com.twilio.callforwarding.models.Zipcode;
import com.twilio.callforwarding.repositories.SenatorRepository;
import com.twilio.callforwarding.repositories.StateRepository;
import com.twilio.callforwarding.repositories.ZipcodeRepository;
import com.twilio.twiml.*;
import com.twilio.twiml.Number;
import spark.Route;
import spark.utils.StringUtils;

import java.util.List;

public class CallCongressController {

    private static final String APPLICATION_XML = "application/xml";
    private SenatorRepository senatorRepository;
    private StateRepository stateRepository;
    private ZipcodeRepository zipcodeRepository;

    public CallCongressController(SenatorRepository senatorRepository, StateRepository stateRepository, ZipcodeRepository zipcodeRepository) {
        this.senatorRepository = senatorRepository;
        this.stateRepository = stateRepository;
        this.zipcodeRepository = zipcodeRepository;
    }

    public CallCongressController() {
        this.senatorRepository = new SenatorRepository();
        this.stateRepository = new StateRepository();
        this.zipcodeRepository = new ZipcodeRepository();
    }

    // Verify or collect State information.
    public Route welcomeRoute = (request, response) -> {
        String fromState = request.params("FromState");

        VoiceResponse.Builder builder = new VoiceResponse.Builder();
        if (StringUtils.isNotBlank(fromState)) {
            builder.say(new Say.Builder(
                    String.format("Thank you for calling congress! It looks like" +
                            "you\'re calling from %s." +
                            "If this is correct, please press 1. Press 2 if" +
                            "this is not your current state of residence.", fromState)).build());
            builder.gather(new Gather.Builder()
                    .numDigits(1)
                    .action("/callcongress/set-state")
                    .method(Method.POST)
                    .build());
        } else {
            builder.say(new Say.Builder(
                    "Thank you for calling Call Congress! If you wish to" +
                            "call your senators, please enter your 5 - digit zip code.").build());
            builder.gather(new Gather.Builder()
                    .numDigits(5)
                    .action("/callcongress/state-lookup")
                    .method(Method.POST)
                    .build());
        }
        response.type(APPLICATION_XML);
        return builder.build().toXml();
    };

    // If our state guess is wrong, prompt user for zip code.
    public Route collectZipRoute = (request, response) -> {
        VoiceResponse.Builder builder = new VoiceResponse.Builder();
        builder.say(new Say.Builder("If you wish to call your senators, please " +
                "enter your 5-digit zip code.").build());
        builder.gather(new Gather.Builder()
                .numDigits(5)
                .action("/callcongress/state-lookup")
                .method(Method.POST)
                .build());

        response.type(APPLICATION_XML);
        return builder.build().toXml();
    };

    // Look up state from given zipcode.
    // Once state is found, redirect to call_senators for forwarding.
    public Route stateLookupRoute = (request, response) -> {
        String zipcode = request.queryParams("Digits");

        // NB: We don't do any error handling for a missing/erroneous zip code
        // in this sample application. You, gentle reader, should to handle that
        // edge case before deploying this code.
        response.type("application/xml");
        Zipcode zipcodeObject = zipcodeRepository.getFirstResultFilteredByZipcode(zipcode);

        response.redirect("/callcongress/call-senators/" + zipcodeObject.getState());
        return "";
    };

    // Set state for senator call list.
    // Set user's state from confirmation or user-provided Zip.
    // Redirect to call_senators route.
    public Route setStateRoute = (request, response) -> {
        // Get the digit pressed by the user
        String digits = request.params("Digits");

        if(digits.equals("1")) {
            String callerState = request.params("CallerState");
            response.redirect("/callcongress/call-senators/" + callerState);
        } else {
            response.redirect("/callcongress/collect-zip");
        }
        return "";
    };

    // Route for connecting caller to both of their senators.
    public Route callSenatorsRoute = (request, response) -> {
        String state = request.params("state");

        List<Senator> senators = stateRepository
                .findByStateName(state)
                .getSenators();

        VoiceResponse.Builder builder = new VoiceResponse.Builder();
        Senator firstCall = senators.get(0);
        Senator secondCall = senators.get(1);
        String sayMessage = String.format("Connecting you to %s. " +
                        "After the senator's office ends the call, you will " +
                        "be re-directed to %s.",
                firstCall.getName(),
                secondCall.getName());
        builder.say(new Say.Builder(sayMessage).build());
        builder.dial(new Dial.Builder()
                .number(new Number.Builder(firstCall.getPhone()).build())
                .action("/callcongress/call-second-senator/" + secondCall.getId())
                .build());

        response.type(APPLICATION_XML);
        return builder.build().toXml();
    };

    // Forward the caller to their second senator.
    public Route callSecondSenatorRoute = (request, response) -> {
        Senator senator = senatorRepository.find(Long.valueOf(request.params("senatorId")));
        VoiceResponse.Builder builder = new VoiceResponse.Builder();

        String sayMessage = String.format("Connecting you to %s.", senator.getName());
        builder.say(new Say.Builder(sayMessage).build());

        builder.dial(new Dial.Builder()
                .number(new Number.Builder(senator.getPhone()).build())
                .action("/callcongress/goodbye")
                .build());

        response.type(APPLICATION_XML);
        return builder.build().toXml();
    };

    // Thank user & hang up.
    public Route goodbyeRoute = (request, response) -> {
        VoiceResponse.Builder builder = new VoiceResponse.Builder();
        builder.say(new Say.Builder("Thank you for using Call Congress! " +
                "Your voice makes a difference. Goodbye.").build());
        builder.hangup(new Hangup());

        response.type(APPLICATION_XML);
        return builder.build().toXml();
    };
}
Retrieve the caller's state senators, dial the first senator and instruct Twilio to hit another resource when the call is finished.
Begin call forwarding

Retrieve the caller's state senators, dial the first senator and instruct Twilio to hit another resource when the call is finished.

The action attribute is great for redirecting a call in progress and is the backbone of our call forwarding use case.

However, it’s important to note that the action will only execute after the dialed party (in our case, the caller’s senator) ends the call. Twilio will continue to forward the original caller but they must stay on the line throughout the entire process.

Forward to the Next Senator and End the Call

Once the first senator ends the call with our user, the caller is forwarded to their second state senator. Just as we did in the previous route, we’ll include an action attribute that redirects the caller to a final bit of TwiML.

Loading Code Samples...
Language
package com.twilio.callforwarding.controllers;

import com.twilio.callforwarding.models.Senator;
import com.twilio.callforwarding.models.Zipcode;
import com.twilio.callforwarding.repositories.SenatorRepository;
import com.twilio.callforwarding.repositories.StateRepository;
import com.twilio.callforwarding.repositories.ZipcodeRepository;
import com.twilio.twiml.*;
import com.twilio.twiml.Number;
import spark.Route;
import spark.utils.StringUtils;

import java.util.List;

public class CallCongressController {

    private static final String APPLICATION_XML = "application/xml";
    private SenatorRepository senatorRepository;
    private StateRepository stateRepository;
    private ZipcodeRepository zipcodeRepository;

    public CallCongressController(SenatorRepository senatorRepository, StateRepository stateRepository, ZipcodeRepository zipcodeRepository) {
        this.senatorRepository = senatorRepository;
        this.stateRepository = stateRepository;
        this.zipcodeRepository = zipcodeRepository;
    }

    public CallCongressController() {
        this.senatorRepository = new SenatorRepository();
        this.stateRepository = new StateRepository();
        this.zipcodeRepository = new ZipcodeRepository();
    }

    // Verify or collect State information.
    public Route welcomeRoute = (request, response) -> {
        String fromState = request.params("FromState");

        VoiceResponse.Builder builder = new VoiceResponse.Builder();
        if (StringUtils.isNotBlank(fromState)) {
            builder.say(new Say.Builder(
                    String.format("Thank you for calling congress! It looks like" +
                            "you\'re calling from %s." +
                            "If this is correct, please press 1. Press 2 if" +
                            "this is not your current state of residence.", fromState)).build());
            builder.gather(new Gather.Builder()
                    .numDigits(1)
                    .action("/callcongress/set-state")
                    .method(Method.POST)
                    .build());
        } else {
            builder.say(new Say.Builder(
                    "Thank you for calling Call Congress! If you wish to" +
                            "call your senators, please enter your 5 - digit zip code.").build());
            builder.gather(new Gather.Builder()
                    .numDigits(5)
                    .action("/callcongress/state-lookup")
                    .method(Method.POST)
                    .build());
        }
        response.type(APPLICATION_XML);
        return builder.build().toXml();
    };

    // If our state guess is wrong, prompt user for zip code.
    public Route collectZipRoute = (request, response) -> {
        VoiceResponse.Builder builder = new VoiceResponse.Builder();
        builder.say(new Say.Builder("If you wish to call your senators, please " +
                "enter your 5-digit zip code.").build());
        builder.gather(new Gather.Builder()
                .numDigits(5)
                .action("/callcongress/state-lookup")
                .method(Method.POST)
                .build());

        response.type(APPLICATION_XML);
        return builder.build().toXml();
    };

    // Look up state from given zipcode.
    // Once state is found, redirect to call_senators for forwarding.
    public Route stateLookupRoute = (request, response) -> {
        String zipcode = request.queryParams("Digits");

        // NB: We don't do any error handling for a missing/erroneous zip code
        // in this sample application. You, gentle reader, should to handle that
        // edge case before deploying this code.
        response.type("application/xml");
        Zipcode zipcodeObject = zipcodeRepository.getFirstResultFilteredByZipcode(zipcode);

        response.redirect("/callcongress/call-senators/" + zipcodeObject.getState());
        return "";
    };

    // Set state for senator call list.
    // Set user's state from confirmation or user-provided Zip.
    // Redirect to call_senators route.
    public Route setStateRoute = (request, response) -> {
        // Get the digit pressed by the user
        String digits = request.params("Digits");

        if(digits.equals("1")) {
            String callerState = request.params("CallerState");
            response.redirect("/callcongress/call-senators/" + callerState);
        } else {
            response.redirect("/callcongress/collect-zip");
        }
        return "";
    };

    // Route for connecting caller to both of their senators.
    public Route callSenatorsRoute = (request, response) -> {
        String state = request.params("state");

        List<Senator> senators = stateRepository
                .findByStateName(state)
                .getSenators();

        VoiceResponse.Builder builder = new VoiceResponse.Builder();
        Senator firstCall = senators.get(0);
        Senator secondCall = senators.get(1);
        String sayMessage = String.format("Connecting you to %s. " +
                        "After the senator's office ends the call, you will " +
                        "be re-directed to %s.",
                firstCall.getName(),
                secondCall.getName());
        builder.say(new Say.Builder(sayMessage).build());
        builder.dial(new Dial.Builder()
                .number(new Number.Builder(firstCall.getPhone()).build())
                .action("/callcongress/call-second-senator/" + secondCall.getId())
                .build());

        response.type(APPLICATION_XML);
        return builder.build().toXml();
    };

    // Forward the caller to their second senator.
    public Route callSecondSenatorRoute = (request, response) -> {
        Senator senator = senatorRepository.find(Long.valueOf(request.params("senatorId")));
        VoiceResponse.Builder builder = new VoiceResponse.Builder();

        String sayMessage = String.format("Connecting you to %s.", senator.getName());
        builder.say(new Say.Builder(sayMessage).build());

        builder.dial(new Dial.Builder()
                .number(new Number.Builder(senator.getPhone()).build())
                .action("/callcongress/goodbye")
                .build());

        response.type(APPLICATION_XML);
        return builder.build().toXml();
    };

    // Thank user & hang up.
    public Route goodbyeRoute = (request, response) -> {
        VoiceResponse.Builder builder = new VoiceResponse.Builder();
        builder.say(new Say.Builder("Thank you for using Call Congress! " +
                "Your voice makes a difference. Goodbye.").build());
        builder.hangup(new Hangup());

        response.type(APPLICATION_XML);
        return builder.build().toXml();
    };
}
TwiML to dial th second senator phone and hangup the call.
Call second senator and end call

TwiML to dial th second senator phone and hangup the call.

Once the call with the second senator ends our user will hear a short message thanking them for their call. We then end the call with <Hangup>.

Where to Next?

That’s it! You should be able to start your development server with ngrok, dial your Twilio number, and be routed to your senators!

But wait… this isn’t actually your senator’s phone number, remember? We seeded our sample application’s database with some placeholder phone numbers with lightweight TwiML endpoints, so there’s still some work to be done to flesh out this application before it’s production-ready.

If your production case matches our demo's, ProPublica’s API grants access to a wealth of government data, including senators’ states and phone numbers. You may find yourself inspired to build out even more functionality for your civically engaged users. 

Interested in building something even bigger? See how twilio.org is helping people use messaging, voice, and video to advance their causes by connecting people and resources around the world.

Whatever your use case, we hope you feel empowered to use what you've learned here to seamlessly forward your users' calls.

You might also enjoy these other tutorials:

Click to Call with Java and Spring

Convert web traffic into phone calls with the click of a button.

Warm Transfers with Java and Servlets

Use Twilio powered warm transfers to help your agents dial in others in real time.

Did this help?

Thanks for checking out this tutorial. If you have any feedback to share with us, please reach out to us on Twitter and let us know what you’re building!

Hector Ortega
Kat King
Paul Kamp
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...
package com.twilio.callforwarding.db;

import com.twilio.callforwarding.models.Senator;
import com.twilio.callforwarding.models.State;
import com.twilio.callforwarding.models.Zipcode;
import com.twilio.callforwarding.repositories.SenatorRepository;
import com.twilio.callforwarding.repositories.StateRepository;
import com.twilio.callforwarding.repositories.ZipcodeRepository;

import javax.json.Json;
import javax.json.JsonObject;
import javax.json.JsonString;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class DbSeedHelper {

    private SenatorRepository senatorRepository;
    private StateRepository stateRepository;
    private ZipcodeRepository zipcodeRepository;

    public DbSeedHelper() {
        this.senatorRepository = new SenatorRepository();
        this.stateRepository = new StateRepository();
        this.zipcodeRepository = new ZipcodeRepository();
    }

    public void seedZipcodes() {
        URL zipcodesResource = this.getClass().getResource("/seed/free-zipcode-database.csv");
        InsertBatch<Zipcode> batch = new InsertBatch<>(b -> zipcodeRepository.bulkCreate(b));
        try(Stream<String> stream = Files.lines(Paths.get(zipcodesResource.toURI()))) {
            stream.map(line -> Arrays.stream(line.split(","))
                                     .map(p -> p.replace("\"", ""))
                                     .collect(Collectors.toList()))
                .filter(lineParts -> lineParts.get(0).matches("^[0-9]+$"))
                .forEach(lineParts -> {
                    Integer zipcodeNumber = Integer.valueOf(lineParts.get(0));
                    Zipcode zipcode = new Zipcode(zipcodeNumber, lineParts.get(3));
                    batch.add(zipcode);
                });
            batch.flush();
        } catch (IOException|URISyntaxException e) {
            e.printStackTrace();
        }
    }

    public void seedStatesAndSenators() {
        URL senatorsResource = this.getClass().getResource("/seed/senators.json");
        try {
            InputStream senatorsInputStream = Files.newInputStream(Paths.get(senatorsResource.toURI()));
            JsonObject root = Json.createReader(senatorsInputStream).readObject();

            List<JsonString> states = root.getJsonArray("states")
                                          .getValuesAs(JsonString.class);
            states.stream()
                .filter(state -> root.containsKey(state.getString()))
                .forEach(stateJsonObject -> {
                    String stateName = stateJsonObject.getString();
                    State persistedState = stateRepository.create(new State(stateName));
                    root.getJsonArray(stateName)
                            .getValuesAs(JsonObject.class)
                            .stream()
                            .forEach(senatorJson -> {
                                persistSenator(persistedState, senatorJson);
                            });
                });
        } catch (IOException|URISyntaxException|NullPointerException e) {
            e.printStackTrace();
        }
    }

    private void persistSenator(State persistedState, JsonObject senatorJson) {
        Senator senator = new Senator(
                senatorJson.getString("name"),
                senatorJson.getString("phone"),
                persistedState);
        senatorRepository.create(senator);
    }

    public void seedDb() {
        long countZipcode = zipcodeRepository.count();
        if(countZipcode == 0) {
            seedZipcodes();
        }
        long countSenator = senatorRepository.count();
        if(countSenator == 0) {
            seedStatesAndSenators();
        }
    }
}

class InsertBatch<T> {
    private List<T> batch;
    private Consumer<List<T>> action;

    public InsertBatch(Consumer<List<T>> action) {
        this.action = action;
        this.batch = new ArrayList<>();
    }

    public void add(T element) {
        batch.add(element);
        if(batch.size() > 1000) {
            flush();
        }
    }

    public void flush() {
        action.accept(batch);
        batch = new ArrayList<>();
    }
}
package com.twilio.callforwarding.controllers;

import com.twilio.callforwarding.models.Senator;
import com.twilio.callforwarding.models.Zipcode;
import com.twilio.callforwarding.repositories.SenatorRepository;
import com.twilio.callforwarding.repositories.StateRepository;
import com.twilio.callforwarding.repositories.ZipcodeRepository;
import com.twilio.twiml.*;
import com.twilio.twiml.Number;
import spark.Route;
import spark.utils.StringUtils;

import java.util.List;

public class CallCongressController {

    private static final String APPLICATION_XML = "application/xml";
    private SenatorRepository senatorRepository;
    private StateRepository stateRepository;
    private ZipcodeRepository zipcodeRepository;

    public CallCongressController(SenatorRepository senatorRepository, StateRepository stateRepository, ZipcodeRepository zipcodeRepository) {
        this.senatorRepository = senatorRepository;
        this.stateRepository = stateRepository;
        this.zipcodeRepository = zipcodeRepository;
    }

    public CallCongressController() {
        this.senatorRepository = new SenatorRepository();
        this.stateRepository = new StateRepository();
        this.zipcodeRepository = new ZipcodeRepository();
    }

    // Verify or collect State information.
    public Route welcomeRoute = (request, response) -> {
        String fromState = request.params("FromState");

        VoiceResponse.Builder builder = new VoiceResponse.Builder();
        if (StringUtils.isNotBlank(fromState)) {
            builder.say(new Say.Builder(
                    String.format("Thank you for calling congress! It looks like" +
                            "you\'re calling from %s." +
                            "If this is correct, please press 1. Press 2 if" +
                            "this is not your current state of residence.", fromState)).build());
            builder.gather(new Gather.Builder()
                    .numDigits(1)
                    .action("/callcongress/set-state")
                    .method(Method.POST)
                    .build());
        } else {
            builder.say(new Say.Builder(
                    "Thank you for calling Call Congress! If you wish to" +
                            "call your senators, please enter your 5 - digit zip code.").build());
            builder.gather(new Gather.Builder()
                    .numDigits(5)
                    .action("/callcongress/state-lookup")
                    .method(Method.POST)
                    .build());
        }
        response.type(APPLICATION_XML);
        return builder.build().toXml();
    };

    // If our state guess is wrong, prompt user for zip code.
    public Route collectZipRoute = (request, response) -> {
        VoiceResponse.Builder builder = new VoiceResponse.Builder();
        builder.say(new Say.Builder("If you wish to call your senators, please " +
                "enter your 5-digit zip code.").build());
        builder.gather(new Gather.Builder()
                .numDigits(5)
                .action("/callcongress/state-lookup")
                .method(Method.POST)
                .build());

        response.type(APPLICATION_XML);
        return builder.build().toXml();
    };

    // Look up state from given zipcode.
    // Once state is found, redirect to call_senators for forwarding.
    public Route stateLookupRoute = (request, response) -> {
        String zipcode = request.queryParams("Digits");

        // NB: We don't do any error handling for a missing/erroneous zip code
        // in this sample application. You, gentle reader, should to handle that
        // edge case before deploying this code.
        response.type("application/xml");
        Zipcode zipcodeObject = zipcodeRepository.getFirstResultFilteredByZipcode(zipcode);

        response.redirect("/callcongress/call-senators/" + zipcodeObject.getState());
        return "";
    };

    // Set state for senator call list.
    // Set user's state from confirmation or user-provided Zip.
    // Redirect to call_senators route.
    public Route setStateRoute = (request, response) -> {
        // Get the digit pressed by the user
        String digits = request.params("Digits");

        if(digits.equals("1")) {
            String callerState = request.params("CallerState");
            response.redirect("/callcongress/call-senators/" + callerState);
        } else {
            response.redirect("/callcongress/collect-zip");
        }
        return "";
    };

    // Route for connecting caller to both of their senators.
    public Route callSenatorsRoute = (request, response) -> {
        String state = request.params("state");

        List<Senator> senators = stateRepository
                .findByStateName(state)
                .getSenators();

        VoiceResponse.Builder builder = new VoiceResponse.Builder();
        Senator firstCall = senators.get(0);
        Senator secondCall = senators.get(1);
        String sayMessage = String.format("Connecting you to %s. " +
                        "After the senator's office ends the call, you will " +
                        "be re-directed to %s.",
                firstCall.getName(),
                secondCall.getName());
        builder.say(new Say.Builder(sayMessage).build());
        builder.dial(new Dial.Builder()
                .number(new Number.Builder(firstCall.getPhone()).build())
                .action("/callcongress/call-second-senator/" + secondCall.getId())
                .build());

        response.type(APPLICATION_XML);
        return builder.build().toXml();
    };

    // Forward the caller to their second senator.
    public Route callSecondSenatorRoute = (request, response) -> {
        Senator senator = senatorRepository.find(Long.valueOf(request.params("senatorId")));
        VoiceResponse.Builder builder = new VoiceResponse.Builder();

        String sayMessage = String.format("Connecting you to %s.", senator.getName());
        builder.say(new Say.Builder(sayMessage).build());

        builder.dial(new Dial.Builder()
                .number(new Number.Builder(senator.getPhone()).build())
                .action("/callcongress/goodbye")
                .build());

        response.type(APPLICATION_XML);
        return builder.build().toXml();
    };

    // Thank user & hang up.
    public Route goodbyeRoute = (request, response) -> {
        VoiceResponse.Builder builder = new VoiceResponse.Builder();
        builder.say(new Say.Builder("Thank you for using Call Congress! " +
                "Your voice makes a difference. Goodbye.").build());
        builder.hangup(new Hangup());

        response.type(APPLICATION_XML);
        return builder.build().toXml();
    };
}
package com.twilio.callforwarding.controllers;

import com.twilio.callforwarding.models.Senator;
import com.twilio.callforwarding.models.Zipcode;
import com.twilio.callforwarding.repositories.SenatorRepository;
import com.twilio.callforwarding.repositories.StateRepository;
import com.twilio.callforwarding.repositories.ZipcodeRepository;
import com.twilio.twiml.*;
import com.twilio.twiml.Number;
import spark.Route;
import spark.utils.StringUtils;

import java.util.List;

public class CallCongressController {

    private static final String APPLICATION_XML = "application/xml";
    private SenatorRepository senatorRepository;
    private StateRepository stateRepository;
    private ZipcodeRepository zipcodeRepository;

    public CallCongressController(SenatorRepository senatorRepository, StateRepository stateRepository, ZipcodeRepository zipcodeRepository) {
        this.senatorRepository = senatorRepository;
        this.stateRepository = stateRepository;
        this.zipcodeRepository = zipcodeRepository;
    }

    public CallCongressController() {
        this.senatorRepository = new SenatorRepository();
        this.stateRepository = new StateRepository();
        this.zipcodeRepository = new ZipcodeRepository();
    }

    // Verify or collect State information.
    public Route welcomeRoute = (request, response) -> {
        String fromState = request.params("FromState");

        VoiceResponse.Builder builder = new VoiceResponse.Builder();
        if (StringUtils.isNotBlank(fromState)) {
            builder.say(new Say.Builder(
                    String.format("Thank you for calling congress! It looks like" +
                            "you\'re calling from %s." +
                            "If this is correct, please press 1. Press 2 if" +
                            "this is not your current state of residence.", fromState)).build());
            builder.gather(new Gather.Builder()
                    .numDigits(1)
                    .action("/callcongress/set-state")
                    .method(Method.POST)
                    .build());
        } else {
            builder.say(new Say.Builder(
                    "Thank you for calling Call Congress! If you wish to" +
                            "call your senators, please enter your 5 - digit zip code.").build());
            builder.gather(new Gather.Builder()
                    .numDigits(5)
                    .action("/callcongress/state-lookup")
                    .method(Method.POST)
                    .build());
        }
        response.type(APPLICATION_XML);
        return builder.build().toXml();
    };

    // If our state guess is wrong, prompt user for zip code.
    public Route collectZipRoute = (request, response) -> {
        VoiceResponse.Builder builder = new VoiceResponse.Builder();
        builder.say(new Say.Builder("If you wish to call your senators, please " +
                "enter your 5-digit zip code.").build());
        builder.gather(new Gather.Builder()
                .numDigits(5)
                .action("/callcongress/state-lookup")
                .method(Method.POST)
                .build());

        response.type(APPLICATION_XML);
        return builder.build().toXml();
    };

    // Look up state from given zipcode.
    // Once state is found, redirect to call_senators for forwarding.
    public Route stateLookupRoute = (request, response) -> {
        String zipcode = request.queryParams("Digits");

        // NB: We don't do any error handling for a missing/erroneous zip code
        // in this sample application. You, gentle reader, should to handle that
        // edge case before deploying this code.
        response.type("application/xml");
        Zipcode zipcodeObject = zipcodeRepository.getFirstResultFilteredByZipcode(zipcode);

        response.redirect("/callcongress/call-senators/" + zipcodeObject.getState());
        return "";
    };

    // Set state for senator call list.
    // Set user's state from confirmation or user-provided Zip.
    // Redirect to call_senators route.
    public Route setStateRoute = (request, response) -> {
        // Get the digit pressed by the user
        String digits = request.params("Digits");

        if(digits.equals("1")) {
            String callerState = request.params("CallerState");
            response.redirect("/callcongress/call-senators/" + callerState);
        } else {
            response.redirect("/callcongress/collect-zip");
        }
        return "";
    };

    // Route for connecting caller to both of their senators.
    public Route callSenatorsRoute = (request, response) -> {
        String state = request.params("state");

        List<Senator> senators = stateRepository
                .findByStateName(state)
                .getSenators();

        VoiceResponse.Builder builder = new VoiceResponse.Builder();
        Senator firstCall = senators.get(0);
        Senator secondCall = senators.get(1);
        String sayMessage = String.format("Connecting you to %s. " +
                        "After the senator's office ends the call, you will " +
                        "be re-directed to %s.",
                firstCall.getName(),
                secondCall.getName());
        builder.say(new Say.Builder(sayMessage).build());
        builder.dial(new Dial.Builder()
                .number(new Number.Builder(firstCall.getPhone()).build())
                .action("/callcongress/call-second-senator/" + secondCall.getId())
                .build());

        response.type(APPLICATION_XML);
        return builder.build().toXml();
    };

    // Forward the caller to their second senator.
    public Route callSecondSenatorRoute = (request, response) -> {
        Senator senator = senatorRepository.find(Long.valueOf(request.params("senatorId")));
        VoiceResponse.Builder builder = new VoiceResponse.Builder();

        String sayMessage = String.format("Connecting you to %s.", senator.getName());
        builder.say(new Say.Builder(sayMessage).build());

        builder.dial(new Dial.Builder()
                .number(new Number.Builder(senator.getPhone()).build())
                .action("/callcongress/goodbye")
                .build());

        response.type(APPLICATION_XML);
        return builder.build().toXml();
    };

    // Thank user & hang up.
    public Route goodbyeRoute = (request, response) -> {
        VoiceResponse.Builder builder = new VoiceResponse.Builder();
        builder.say(new Say.Builder("Thank you for using Call Congress! " +
                "Your voice makes a difference. Goodbye.").build());
        builder.hangup(new Hangup());

        response.type(APPLICATION_XML);
        return builder.build().toXml();
    };
}
package com.twilio.callforwarding.controllers;

import com.twilio.callforwarding.models.Senator;
import com.twilio.callforwarding.models.Zipcode;
import com.twilio.callforwarding.repositories.SenatorRepository;
import com.twilio.callforwarding.repositories.StateRepository;
import com.twilio.callforwarding.repositories.ZipcodeRepository;
import com.twilio.twiml.*;
import com.twilio.twiml.Number;
import spark.Route;
import spark.utils.StringUtils;

import java.util.List;

public class CallCongressController {

    private static final String APPLICATION_XML = "application/xml";
    private SenatorRepository senatorRepository;
    private StateRepository stateRepository;
    private ZipcodeRepository zipcodeRepository;

    public CallCongressController(SenatorRepository senatorRepository, StateRepository stateRepository, ZipcodeRepository zipcodeRepository) {
        this.senatorRepository = senatorRepository;
        this.stateRepository = stateRepository;
        this.zipcodeRepository = zipcodeRepository;
    }

    public CallCongressController() {
        this.senatorRepository = new SenatorRepository();
        this.stateRepository = new StateRepository();
        this.zipcodeRepository = new ZipcodeRepository();
    }

    // Verify or collect State information.
    public Route welcomeRoute = (request, response) -> {
        String fromState = request.params("FromState");

        VoiceResponse.Builder builder = new VoiceResponse.Builder();
        if (StringUtils.isNotBlank(fromState)) {
            builder.say(new Say.Builder(
                    String.format("Thank you for calling congress! It looks like" +
                            "you\'re calling from %s." +
                            "If this is correct, please press 1. Press 2 if" +
                            "this is not your current state of residence.", fromState)).build());
            builder.gather(new Gather.Builder()
                    .numDigits(1)
                    .action("/callcongress/set-state")
                    .method(Method.POST)
                    .build());
        } else {
            builder.say(new Say.Builder(
                    "Thank you for calling Call Congress! If you wish to" +
                            "call your senators, please enter your 5 - digit zip code.").build());
            builder.gather(new Gather.Builder()
                    .numDigits(5)
                    .action("/callcongress/state-lookup")
                    .method(Method.POST)
                    .build());
        }
        response.type(APPLICATION_XML);
        return builder.build().toXml();
    };

    // If our state guess is wrong, prompt user for zip code.
    public Route collectZipRoute = (request, response) -> {
        VoiceResponse.Builder builder = new VoiceResponse.Builder();
        builder.say(new Say.Builder("If you wish to call your senators, please " +
                "enter your 5-digit zip code.").build());
        builder.gather(new Gather.Builder()
                .numDigits(5)
                .action("/callcongress/state-lookup")
                .method(Method.POST)
                .build());

        response.type(APPLICATION_XML);
        return builder.build().toXml();
    };

    // Look up state from given zipcode.
    // Once state is found, redirect to call_senators for forwarding.
    public Route stateLookupRoute = (request, response) -> {
        String zipcode = request.queryParams("Digits");

        // NB: We don't do any error handling for a missing/erroneous zip code
        // in this sample application. You, gentle reader, should to handle that
        // edge case before deploying this code.
        response.type("application/xml");
        Zipcode zipcodeObject = zipcodeRepository.getFirstResultFilteredByZipcode(zipcode);

        response.redirect("/callcongress/call-senators/" + zipcodeObject.getState());
        return "";
    };

    // Set state for senator call list.
    // Set user's state from confirmation or user-provided Zip.
    // Redirect to call_senators route.
    public Route setStateRoute = (request, response) -> {
        // Get the digit pressed by the user
        String digits = request.params("Digits");

        if(digits.equals("1")) {
            String callerState = request.params("CallerState");
            response.redirect("/callcongress/call-senators/" + callerState);
        } else {
            response.redirect("/callcongress/collect-zip");
        }
        return "";
    };

    // Route for connecting caller to both of their senators.
    public Route callSenatorsRoute = (request, response) -> {
        String state = request.params("state");

        List<Senator> senators = stateRepository
                .findByStateName(state)
                .getSenators();

        VoiceResponse.Builder builder = new VoiceResponse.Builder();
        Senator firstCall = senators.get(0);
        Senator secondCall = senators.get(1);
        String sayMessage = String.format("Connecting you to %s. " +
                        "After the senator's office ends the call, you will " +
                        "be re-directed to %s.",
                firstCall.getName(),
                secondCall.getName());
        builder.say(new Say.Builder(sayMessage).build());
        builder.dial(new Dial.Builder()
                .number(new Number.Builder(firstCall.getPhone()).build())
                .action("/callcongress/call-second-senator/" + secondCall.getId())
                .build());

        response.type(APPLICATION_XML);
        return builder.build().toXml();
    };

    // Forward the caller to their second senator.
    public Route callSecondSenatorRoute = (request, response) -> {
        Senator senator = senatorRepository.find(Long.valueOf(request.params("senatorId")));
        VoiceResponse.Builder builder = new VoiceResponse.Builder();

        String sayMessage = String.format("Connecting you to %s.", senator.getName());
        builder.say(new Say.Builder(sayMessage).build());

        builder.dial(new Dial.Builder()
                .number(new Number.Builder(senator.getPhone()).build())
                .action("/callcongress/goodbye")
                .build());

        response.type(APPLICATION_XML);
        return builder.build().toXml();
    };

    // Thank user & hang up.
    public Route goodbyeRoute = (request, response) -> {
        VoiceResponse.Builder builder = new VoiceResponse.Builder();
        builder.say(new Say.Builder("Thank you for using Call Congress! " +
                "Your voice makes a difference. Goodbye.").build());
        builder.hangup(new Hangup());

        response.type(APPLICATION_XML);
        return builder.build().toXml();
    };
}
package com.twilio.callforwarding.controllers;

import com.twilio.callforwarding.models.Senator;
import com.twilio.callforwarding.models.Zipcode;
import com.twilio.callforwarding.repositories.SenatorRepository;
import com.twilio.callforwarding.repositories.StateRepository;
import com.twilio.callforwarding.repositories.ZipcodeRepository;
import com.twilio.twiml.*;
import com.twilio.twiml.Number;
import spark.Route;
import spark.utils.StringUtils;

import java.util.List;

public class CallCongressController {

    private static final String APPLICATION_XML = "application/xml";
    private SenatorRepository senatorRepository;
    private StateRepository stateRepository;
    private ZipcodeRepository zipcodeRepository;

    public CallCongressController(SenatorRepository senatorRepository, StateRepository stateRepository, ZipcodeRepository zipcodeRepository) {
        this.senatorRepository = senatorRepository;
        this.stateRepository = stateRepository;
        this.zipcodeRepository = zipcodeRepository;
    }

    public CallCongressController() {
        this.senatorRepository = new SenatorRepository();
        this.stateRepository = new StateRepository();
        this.zipcodeRepository = new ZipcodeRepository();
    }

    // Verify or collect State information.
    public Route welcomeRoute = (request, response) -> {
        String fromState = request.params("FromState");

        VoiceResponse.Builder builder = new VoiceResponse.Builder();
        if (StringUtils.isNotBlank(fromState)) {
            builder.say(new Say.Builder(
                    String.format("Thank you for calling congress! It looks like" +
                            "you\'re calling from %s." +
                            "If this is correct, please press 1. Press 2 if" +
                            "this is not your current state of residence.", fromState)).build());
            builder.gather(new Gather.Builder()
                    .numDigits(1)
                    .action("/callcongress/set-state")
                    .method(Method.POST)
                    .build());
        } else {
            builder.say(new Say.Builder(
                    "Thank you for calling Call Congress! If you wish to" +
                            "call your senators, please enter your 5 - digit zip code.").build());
            builder.gather(new Gather.Builder()
                    .numDigits(5)
                    .action("/callcongress/state-lookup")
                    .method(Method.POST)
                    .build());
        }
        response.type(APPLICATION_XML);
        return builder.build().toXml();
    };

    // If our state guess is wrong, prompt user for zip code.
    public Route collectZipRoute = (request, response) -> {
        VoiceResponse.Builder builder = new VoiceResponse.Builder();
        builder.say(new Say.Builder("If you wish to call your senators, please " +
                "enter your 5-digit zip code.").build());
        builder.gather(new Gather.Builder()
                .numDigits(5)
                .action("/callcongress/state-lookup")
                .method(Method.POST)
                .build());

        response.type(APPLICATION_XML);
        return builder.build().toXml();
    };

    // Look up state from given zipcode.
    // Once state is found, redirect to call_senators for forwarding.
    public Route stateLookupRoute = (request, response) -> {
        String zipcode = request.queryParams("Digits");

        // NB: We don't do any error handling for a missing/erroneous zip code
        // in this sample application. You, gentle reader, should to handle that
        // edge case before deploying this code.
        response.type("application/xml");
        Zipcode zipcodeObject = zipcodeRepository.getFirstResultFilteredByZipcode(zipcode);

        response.redirect("/callcongress/call-senators/" + zipcodeObject.getState());
        return "";
    };

    // Set state for senator call list.
    // Set user's state from confirmation or user-provided Zip.
    // Redirect to call_senators route.
    public Route setStateRoute = (request, response) -> {
        // Get the digit pressed by the user
        String digits = request.params("Digits");

        if(digits.equals("1")) {
            String callerState = request.params("CallerState");
            response.redirect("/callcongress/call-senators/" + callerState);
        } else {
            response.redirect("/callcongress/collect-zip");
        }
        return "";
    };

    // Route for connecting caller to both of their senators.
    public Route callSenatorsRoute = (request, response) -> {
        String state = request.params("state");

        List<Senator> senators = stateRepository
                .findByStateName(state)
                .getSenators();

        VoiceResponse.Builder builder = new VoiceResponse.Builder();
        Senator firstCall = senators.get(0);
        Senator secondCall = senators.get(1);
        String sayMessage = String.format("Connecting you to %s. " +
                        "After the senator's office ends the call, you will " +
                        "be re-directed to %s.",
                firstCall.getName(),
                secondCall.getName());
        builder.say(new Say.Builder(sayMessage).build());
        builder.dial(new Dial.Builder()
                .number(new Number.Builder(firstCall.getPhone()).build())
                .action("/callcongress/call-second-senator/" + secondCall.getId())
                .build());

        response.type(APPLICATION_XML);
        return builder.build().toXml();
    };

    // Forward the caller to their second senator.
    public Route callSecondSenatorRoute = (request, response) -> {
        Senator senator = senatorRepository.find(Long.valueOf(request.params("senatorId")));
        VoiceResponse.Builder builder = new VoiceResponse.Builder();

        String sayMessage = String.format("Connecting you to %s.", senator.getName());
        builder.say(new Say.Builder(sayMessage).build());

        builder.dial(new Dial.Builder()
                .number(new Number.Builder(senator.getPhone()).build())
                .action("/callcongress/goodbye")
                .build());

        response.type(APPLICATION_XML);
        return builder.build().toXml();
    };

    // Thank user & hang up.
    public Route goodbyeRoute = (request, response) -> {
        VoiceResponse.Builder builder = new VoiceResponse.Builder();
        builder.say(new Say.Builder("Thank you for using Call Congress! " +
                "Your voice makes a difference. Goodbye.").build());
        builder.hangup(new Hangup());

        response.type(APPLICATION_XML);
        return builder.build().toXml();
    };
}
package com.twilio.callforwarding.controllers;

import com.twilio.callforwarding.models.Senator;
import com.twilio.callforwarding.models.Zipcode;
import com.twilio.callforwarding.repositories.SenatorRepository;
import com.twilio.callforwarding.repositories.StateRepository;
import com.twilio.callforwarding.repositories.ZipcodeRepository;
import com.twilio.twiml.*;
import com.twilio.twiml.Number;
import spark.Route;
import spark.utils.StringUtils;

import java.util.List;

public class CallCongressController {

    private static final String APPLICATION_XML = "application/xml";
    private SenatorRepository senatorRepository;
    private StateRepository stateRepository;
    private ZipcodeRepository zipcodeRepository;

    public CallCongressController(SenatorRepository senatorRepository, StateRepository stateRepository, ZipcodeRepository zipcodeRepository) {
        this.senatorRepository = senatorRepository;
        this.stateRepository = stateRepository;
        this.zipcodeRepository = zipcodeRepository;
    }

    public CallCongressController() {
        this.senatorRepository = new SenatorRepository();
        this.stateRepository = new StateRepository();
        this.zipcodeRepository = new ZipcodeRepository();
    }

    // Verify or collect State information.
    public Route welcomeRoute = (request, response) -> {
        String fromState = request.params("FromState");

        VoiceResponse.Builder builder = new VoiceResponse.Builder();
        if (StringUtils.isNotBlank(fromState)) {
            builder.say(new Say.Builder(
                    String.format("Thank you for calling congress! It looks like" +
                            "you\'re calling from %s." +
                            "If this is correct, please press 1. Press 2 if" +
                            "this is not your current state of residence.", fromState)).build());
            builder.gather(new Gather.Builder()
                    .numDigits(1)
                    .action("/callcongress/set-state")
                    .method(Method.POST)
                    .build());
        } else {
            builder.say(new Say.Builder(
                    "Thank you for calling Call Congress! If you wish to" +
                            "call your senators, please enter your 5 - digit zip code.").build());
            builder.gather(new Gather.Builder()
                    .numDigits(5)
                    .action("/callcongress/state-lookup")
                    .method(Method.POST)
                    .build());
        }
        response.type(APPLICATION_XML);
        return builder.build().toXml();
    };

    // If our state guess is wrong, prompt user for zip code.
    public Route collectZipRoute = (request, response) -> {
        VoiceResponse.Builder builder = new VoiceResponse.Builder();
        builder.say(new Say.Builder("If you wish to call your senators, please " +
                "enter your 5-digit zip code.").build());
        builder.gather(new Gather.Builder()
                .numDigits(5)
                .action("/callcongress/state-lookup")
                .method(Method.POST)
                .build());

        response.type(APPLICATION_XML);
        return builder.build().toXml();
    };

    // Look up state from given zipcode.
    // Once state is found, redirect to call_senators for forwarding.
    public Route stateLookupRoute = (request, response) -> {
        String zipcode = request.queryParams("Digits");

        // NB: We don't do any error handling for a missing/erroneous zip code
        // in this sample application. You, gentle reader, should to handle that
        // edge case before deploying this code.
        response.type("application/xml");
        Zipcode zipcodeObject = zipcodeRepository.getFirstResultFilteredByZipcode(zipcode);

        response.redirect("/callcongress/call-senators/" + zipcodeObject.getState());
        return "";
    };

    // Set state for senator call list.
    // Set user's state from confirmation or user-provided Zip.
    // Redirect to call_senators route.
    public Route setStateRoute = (request, response) -> {
        // Get the digit pressed by the user
        String digits = request.params("Digits");

        if(digits.equals("1")) {
            String callerState = request.params("CallerState");
            response.redirect("/callcongress/call-senators/" + callerState);
        } else {
            response.redirect("/callcongress/collect-zip");
        }
        return "";
    };

    // Route for connecting caller to both of their senators.
    public Route callSenatorsRoute = (request, response) -> {
        String state = request.params("state");

        List<Senator> senators = stateRepository
                .findByStateName(state)
                .getSenators();

        VoiceResponse.Builder builder = new VoiceResponse.Builder();
        Senator firstCall = senators.get(0);
        Senator secondCall = senators.get(1);
        String sayMessage = String.format("Connecting you to %s. " +
                        "After the senator's office ends the call, you will " +
                        "be re-directed to %s.",
                firstCall.getName(),
                secondCall.getName());
        builder.say(new Say.Builder(sayMessage).build());
        builder.dial(new Dial.Builder()
                .number(new Number.Builder(firstCall.getPhone()).build())
                .action("/callcongress/call-second-senator/" + secondCall.getId())
                .build());

        response.type(APPLICATION_XML);
        return builder.build().toXml();
    };

    // Forward the caller to their second senator.
    public Route callSecondSenatorRoute = (request, response) -> {
        Senator senator = senatorRepository.find(Long.valueOf(request.params("senatorId")));
        VoiceResponse.Builder builder = new VoiceResponse.Builder();

        String sayMessage = String.format("Connecting you to %s.", senator.getName());
        builder.say(new Say.Builder(sayMessage).build());

        builder.dial(new Dial.Builder()
                .number(new Number.Builder(senator.getPhone()).build())
                .action("/callcongress/goodbye")
                .build());

        response.type(APPLICATION_XML);
        return builder.build().toXml();
    };

    // Thank user & hang up.
    public Route goodbyeRoute = (request, response) -> {
        VoiceResponse.Builder builder = new VoiceResponse.Builder();
        builder.say(new Say.Builder("Thank you for using Call Congress! " +
                "Your voice makes a difference. Goodbye.").build());
        builder.hangup(new Hangup());

        response.type(APPLICATION_XML);
        return builder.build().toXml();
    };
}