This Java Servlets sample application is modeled after the amazing rental experience created by AirBnB, but with more Klingons.
Host users can offer rental properties which other guest users can reserve. The guest and the host can then anonymously communicate via a disposable Twilio phone number created just for a reservation. In this tutorial, we'll show you the key bits of code to make this work.
To run this sample app yourself, download the code and follow the instructions on GitHub.
If you choose to manage communications between your users, including voice calls, text-based messages (e.g., SMS), and chat, you may need to comply with certain laws and regulations, including those regarding obtaining consent. Additional information regarding legal compliance considerations and best practices for using Twilio to manage and record communications between your users, such as when using Twilio Proxy, can be found here.
Notice: Twilio recommends that you consult with your legal counsel to make sure that you are complying with all applicable laws in connection with communications you record or store using Twilio.
Read how Lyft uses masked phone numbers to let customers securely contact drivers.
The first step in connecting a guest and host is creating a reservation. Here, we handle a form submission for a new reservation which contains the message. The guest's information is pulled out from the logged user.
src/main/java/org/twilio/airtng/servlets/ReservationServlet.java
1package org.twilio.airtng.servlets;23import org.twilio.airtng.lib.notifications.SmsNotifier;4import org.twilio.airtng.lib.servlets.WebAppServlet;5import org.twilio.airtng.lib.web.request.validators.RequestParametersValidator;6import org.twilio.airtng.models.Reservation;7import org.twilio.airtng.models.User;8import org.twilio.airtng.models.VacationProperty;9import org.twilio.airtng.repositories.ReservationRepository;10import org.twilio.airtng.repositories.UserRepository;11import org.twilio.airtng.repositories.VacationPropertiesRepository;1213import javax.servlet.ServletException;14import javax.servlet.http.HttpServletRequest;15import javax.servlet.http.HttpServletResponse;16import java.io.IOException;1718public class ReservationServlet extends WebAppServlet {1920private final VacationPropertiesRepository vacationPropertiesRepository;21private final ReservationRepository reservationRepository;22private UserRepository userRepository;23private SmsNotifier smsNotifier;2425public ReservationServlet() {26this(new VacationPropertiesRepository(), new ReservationRepository(), new UserRepository(), new SmsNotifier());27}2829public ReservationServlet(VacationPropertiesRepository vacationPropertiesRepository, ReservationRepository reservationRepository, UserRepository userRepository, SmsNotifier smsNotifier) {30super();31this.vacationPropertiesRepository = vacationPropertiesRepository;32this.reservationRepository = reservationRepository;33this.userRepository = userRepository;34this.smsNotifier = smsNotifier;35}3637@Override38public void doGet(HttpServletRequest request, HttpServletResponse response)39throws ServletException, IOException {4041VacationProperty vacationProperty = vacationPropertiesRepository.find(Long.parseLong(request.getParameter("id")));42request.setAttribute("vacationProperty", vacationProperty);43request.getRequestDispatcher("/reservation.jsp").forward(request, response);44}4546@Override47public void doPost(HttpServletRequest request, HttpServletResponse response)48throws ServletException, IOException {4950super.doPost(request, response);5152String message = null;53VacationProperty vacationProperty = null;5455if (isValidRequest()) {56message = request.getParameter("message");57String propertyId = request.getParameter("propertyId");58vacationProperty = vacationPropertiesRepository.find(Long.parseLong(propertyId));5960User currentUser = userRepository.find(sessionManager.get().getLoggedUserId(request));61Reservation reservation = reservationRepository.create(new Reservation(message, vacationProperty, currentUser));62smsNotifier.notifyHost(reservation);63response.sendRedirect("/properties");64}65preserveStatusRequest(request, message, vacationProperty);66request.getRequestDispatcher("/reservation.jsp").forward(request, response);67}6869@Override70protected boolean isValidRequest(RequestParametersValidator validator) {7172return validator.validatePresence("message");73}7475private void preserveStatusRequest(76HttpServletRequest request,77String message, Object vacationProperty) {78request.setAttribute("message", message);79request.setAttribute("vacationProperty", vacationProperty);80}81}
Part of our reservation system is receiving reservation requests from potential renters. However, these reservations need to be confirmed. Let's see how we would handle this step.
Before the reservation is finalized, the host needs to confirm that the property was reserved. Learn how to automate this process in our first AirTNG tutorial, Workflow Automation.
src/main/java/org/twilio/airtng/servlets/ReservationConfirmationServlet.java
1package org.twilio.airtng.servlets;23import com.twilio.twiml.MessagingResponse;4import com.twilio.twiml.TwiMLException;5import org.twilio.airtng.lib.helpers.TwiMLHelper;6import org.twilio.airtng.lib.notifications.SmsNotifier;7import org.twilio.airtng.lib.servlets.WebAppServlet;8import org.twilio.airtng.models.Reservation;9import org.twilio.airtng.models.User;10import org.twilio.airtng.repositories.ReservationRepository;11import org.twilio.airtng.repositories.UserRepository;1213import javax.servlet.ServletException;14import javax.servlet.http.HttpServletRequest;15import javax.servlet.http.HttpServletResponse;16import java.io.IOException;1718public class ReservationConfirmationServlet extends WebAppServlet {1920private UserRepository userRepository;21private ReservationRepository reservationRepository;22private SmsNotifier smsNotifier;2324public ReservationConfirmationServlet() {25this(new UserRepository(), new ReservationRepository(), new SmsNotifier());26}2728public ReservationConfirmationServlet(UserRepository userRepository, ReservationRepository reservationRepository, SmsNotifier smsNotifier) {29super();30this.userRepository = userRepository;31this.reservationRepository = reservationRepository;32this.smsNotifier = smsNotifier;33}3435@Override36public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {3738String phone = request.getParameter("From");39String smsContent = request.getParameter("Body");4041String smsResponseText = "Sorry, it looks like you don't have any reservations to respond to.";4243try {44User user = userRepository.findByPhoneNumber(phone);45Reservation reservation = reservationRepository.findFirstPendantReservationsByUser(user.getId());46if (reservation != null) {47if (smsContent.contains("yes") || smsContent.contains("accept"))48reservation.confirm();49else50reservation.reject();51reservationRepository.update(reservation);5253smsResponseText = String.format("You have successfully %s the reservation", reservation.getStatus().toString());54smsNotifier.notifyGuest(reservation);55}5657respondSms(response, smsResponseText);5859} catch (Exception e) {60throw new RuntimeException(e);61}62}6364private void respondSms(HttpServletResponse response, String message)65throws IOException, TwiMLException {66MessagingResponse twiMLResponse = TwiMLHelper.buildSmsRespond(message);67response.setContentType("text/xml");68response.getWriter().write(twiMLResponse.toXml());69}70}
Once the reservation is confirmed, we need to purchase a Twilio number that the guest and host can use to communicate.
Here we use a Twilio Java helper library to search for and buy a new phone number to associate with the reservation. When we buy the number, we designate a Twilio Application that will handle webhook requests when the new number receives an incoming call or text.
We then save the new phone number on our Reservation
model, so when our app receives calls or texts to this number, we'll know which reservation the call or text belongs to.
src/main/java/org/twilio/airtng/lib/phonenumber/Purchaser.java
1package org.twilio.airtng.lib.phonenumber;23import com.twilio.base.ResourceSet;4import com.twilio.http.TwilioRestClient;5import com.twilio.rest.api.v2010.account.IncomingPhoneNumberCreator;6import com.twilio.rest.api.v2010.account.availablephonenumbercountry.Local;7import com.twilio.type.PhoneNumber;8import org.twilio.airtng.lib.Config;910public class Purchaser {1112private final TwilioRestClient client;1314public Purchaser() {15client = new TwilioRestClient.Builder(Config.getAccountSid(), Config.getAuthToken()).build();16}1718public Purchaser(TwilioRestClient client) {19this.client = client;20}2122public String buyNumber(Integer areaCode) {23ResourceSet<Local> availableNumbersForGivenArea = Local.reader("US")24.setAreaCode(areaCode)25.setSmsEnabled(true)26.setVoiceEnabled(true)27.read();2829if (availableNumbersForGivenArea.iterator().hasNext()) {30PhoneNumber availableNumber = createBuyNumber(31availableNumbersForGivenArea.iterator().next().getPhoneNumber()32);3334return availableNumber.toString();35} else {36ResourceSet<Local> generalAvailableNumbers = Local.reader("US")37.setSmsEnabled(true)38.setVoiceEnabled(true)39.read();40if (generalAvailableNumbers.iterator().hasNext()) {41PhoneNumber availableNumber = createBuyNumber(42generalAvailableNumbers.iterator().next().getPhoneNumber()43);44return availableNumber.toString();45} else {46return null;47}48}49}5051private PhoneNumber createBuyNumber(PhoneNumber phoneNumber) {52return new IncomingPhoneNumberCreator(phoneNumber)53.setSmsApplicationSid(Config.getApplicationSid())54.setVoiceApplicationSid(Config.getApplicationSid())55.create(client).getPhoneNumber();56}57}
Now that each reservation has a Twilio Phone Number, we can see how the application will look up reservations as guest or host calls come in.
When someone sends an SMS or calls one of the Twilio numbers you have configured, Twilio makes a request to the URL you set in the TwiML app. In this request, Twilio includes some useful information including:
incomingPhoneNumber
number that originally called or sent an SMS.anonymousPhoneNumber
Twilio number that triggered this request.Take a look at Twilio's SMS Documentation and Twilio's Voice Documentation for a full list of the parameters you can use.
In our servlet we use the to
parameter sent by Twilio to find a reservation that has the number we bought stored in it, as this is the number both hosts and guests will call and send SMS to.
src/main/java/org/twilio/airtng/servlets/BaseExchangeServlet.java
1package org.twilio.airtng.servlets;23import com.twilio.twiml.TwiML;4import com.twilio.twiml.TwiMLException;5import org.twilio.airtng.lib.servlets.WebAppServlet;6import org.twilio.airtng.models.Reservation;7import org.twilio.airtng.repositories.ReservationRepository;89import javax.servlet.http.HttpServletResponse;10import java.io.IOException;11import java.util.Objects;1213public class BaseExchangeServlet extends WebAppServlet {14protected ReservationRepository reservationRepository;1516public BaseExchangeServlet(ReservationRepository reservationRepository) {17this.reservationRepository = reservationRepository;18}1920protected String gatherOutgoingPhoneNumber(String incomingPhoneNumber, String anonymousPhoneNumber) {21String outgoingPhoneNumber = null;2223Reservation reservation = reservationRepository.findByAnonymousPhoneNumber(anonymousPhoneNumber);2425if (Objects.equals(reservation.getUser().getPhoneNumber(), incomingPhoneNumber)) {26outgoingPhoneNumber = reservation.getVacationProperty().getUser().getPhoneNumber();27} else {28outgoingPhoneNumber = reservation.getUser().getPhoneNumber();29}3031return outgoingPhoneNumber;32}3334protected void respondTwiML(HttpServletResponse response, TwiML twiMLResponse)35throws IOException {36response.setContentType("text/xml");37try {38response.getWriter().write(twiMLResponse.toXml());39} catch (TwiMLException e) {40e.printStackTrace();41}42}43}
Next, let's see how to connect the guest and the host via SMS.
Our Twilio application should be configured to send HTTP requests to this controller method on any incoming text message. Our app responds with TwiML to tell Twilio what to do in response to the message.
If the initial message sent to the anonymous number was sent by the host, we forward it on to the guest. Conversely, if the original message was sent by the guest, we forward it to the host.
To find the outgoing number we'll use the gatherOutgoingPhoneNumberAsync
helper method.
src/main/java/org/twilio/airtng/servlets/ExchangeSmsServlet.java
1package org.twilio.airtng.servlets;23import com.twilio.twiml.Body;4import com.twilio.twiml.Message;5import com.twilio.twiml.MessagingResponse;6import org.twilio.airtng.repositories.ReservationRepository;78import javax.servlet.ServletException;9import javax.servlet.http.HttpServletRequest;10import javax.servlet.http.HttpServletResponse;11import java.io.IOException;1213public class ExchangeSmsServlet extends BaseExchangeServlet {1415@SuppressWarnings("unused")16public ExchangeSmsServlet() {17this(new ReservationRepository());18}1920public ExchangeSmsServlet(ReservationRepository reservationRepository) {21super(reservationRepository);22}2324@Override25public void doPost(HttpServletRequest request, HttpServletResponse response)26throws ServletException, IOException {2728String from = request.getParameter("From");29String to = request.getParameter("To");30String body = request.getParameter("Body");3132String outgoingNumber = gatherOutgoingPhoneNumber(from, to);3334MessagingResponse messagingResponse = new MessagingResponse.Builder()35.message(new Message.Builder().body(new Body(body)).to(outgoingNumber).build())36.build();3738respondTwiML(response, messagingResponse);39}40}
Let's see how to connect the guest and the host via phone call next.
Our Twilio application will send HTTP requests to this method on any incoming voice call. Our app responds with TwiML instructions that tell Twilio to Play
an introductory MP3 audio file and then Dial
either the guest or host, depending on who initiated the call.
src/main/java/org/twilio/airtng/servlets/ExchangeVoiceServlet.java
1package org.twilio.airtng.servlets;23import com.twilio.twiml.Dial;4import com.twilio.twiml.Number;5import com.twilio.twiml.Play;6import com.twilio.twiml.VoiceResponse;7import org.twilio.airtng.repositories.ReservationRepository;89import javax.servlet.ServletException;10import javax.servlet.http.HttpServletRequest;11import javax.servlet.http.HttpServletResponse;12import java.io.IOException;1314public class ExchangeVoiceServlet extends BaseExchangeServlet {1516@SuppressWarnings("unused")17public ExchangeVoiceServlet() {18this(new ReservationRepository());19}2021public ExchangeVoiceServlet(ReservationRepository reservationRepository) {22super(reservationRepository);23}2425@Override26public void doPost(HttpServletRequest request, HttpServletResponse response)27throws ServletException, IOException {2829String from = request.getParameter("From");30String to = request.getParameter("To");3132String outgoingNumber = gatherOutgoingPhoneNumber(from, to);3334VoiceResponse voiceResponse = new VoiceResponse.Builder()35.play(new Play.Builder("http://howtodocs.s3.amazonaws.com/howdy-tng.mp3").build())36.dial(new Dial.Builder().number(new Number.Builder(outgoingNumber).build()).build())37.build();3839respondTwiML(response, voiceResponse);40}4142}
That's it! We've just implemented anonymous communications that allow your customers to connect while protecting their privacy.
If you're a Java developer working with Twilio, you might want to check out these other tutorials:
Create a seamless customer service experience by building an IVR Phone Tree for your company.
Allow your company to convert web traffic into phone calls with the click of a button.
Thanks for checking out this tutorial! If you have any feedback to share with us, we'd love to hear it. Tweet @twilio to let us know what you think.