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
_81package org.twilio.airtng.servlets;_81_81import org.twilio.airtng.lib.notifications.SmsNotifier;_81import org.twilio.airtng.lib.servlets.WebAppServlet;_81import org.twilio.airtng.lib.web.request.validators.RequestParametersValidator;_81import org.twilio.airtng.models.Reservation;_81import org.twilio.airtng.models.User;_81import org.twilio.airtng.models.VacationProperty;_81import org.twilio.airtng.repositories.ReservationRepository;_81import org.twilio.airtng.repositories.UserRepository;_81import org.twilio.airtng.repositories.VacationPropertiesRepository;_81_81import javax.servlet.ServletException;_81import javax.servlet.http.HttpServletRequest;_81import javax.servlet.http.HttpServletResponse;_81import java.io.IOException;_81_81public class ReservationServlet extends WebAppServlet {_81_81 private final VacationPropertiesRepository vacationPropertiesRepository;_81 private final ReservationRepository reservationRepository;_81 private UserRepository userRepository;_81 private SmsNotifier smsNotifier;_81_81 public ReservationServlet() {_81 this(new VacationPropertiesRepository(), new ReservationRepository(), new UserRepository(), new SmsNotifier());_81 }_81_81 public ReservationServlet(VacationPropertiesRepository vacationPropertiesRepository, ReservationRepository reservationRepository, UserRepository userRepository, SmsNotifier smsNotifier) {_81 super();_81 this.vacationPropertiesRepository = vacationPropertiesRepository;_81 this.reservationRepository = reservationRepository;_81 this.userRepository = userRepository;_81 this.smsNotifier = smsNotifier;_81 }_81_81 @Override_81 public void doGet(HttpServletRequest request, HttpServletResponse response)_81 throws ServletException, IOException {_81_81 VacationProperty vacationProperty = vacationPropertiesRepository.find(Long.parseLong(request.getParameter("id")));_81 request.setAttribute("vacationProperty", vacationProperty);_81 request.getRequestDispatcher("/reservation.jsp").forward(request, response);_81 }_81_81 @Override_81 public void doPost(HttpServletRequest request, HttpServletResponse response)_81 throws ServletException, IOException {_81_81 super.doPost(request, response);_81_81 String message = null;_81 VacationProperty vacationProperty = null;_81_81 if (isValidRequest()) {_81 message = request.getParameter("message");_81 String propertyId = request.getParameter("propertyId");_81 vacationProperty = vacationPropertiesRepository.find(Long.parseLong(propertyId));_81_81 User currentUser = userRepository.find(sessionManager.get().getLoggedUserId(request));_81 Reservation reservation = reservationRepository.create(new Reservation(message, vacationProperty, currentUser));_81 smsNotifier.notifyHost(reservation);_81 response.sendRedirect("/properties");_81 }_81 preserveStatusRequest(request, message, vacationProperty);_81 request.getRequestDispatcher("/reservation.jsp").forward(request, response);_81 }_81_81 @Override_81 protected boolean isValidRequest(RequestParametersValidator validator) {_81_81 return validator.validatePresence("message");_81 }_81_81 private void preserveStatusRequest(_81 HttpServletRequest request,_81 String message, Object vacationProperty) {_81 request.setAttribute("message", message);_81 request.setAttribute("vacationProperty", vacationProperty);_81 }_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
_70package org.twilio.airtng.servlets;_70_70import com.twilio.twiml.MessagingResponse;_70import com.twilio.twiml.TwiMLException;_70import org.twilio.airtng.lib.helpers.TwiMLHelper;_70import org.twilio.airtng.lib.notifications.SmsNotifier;_70import org.twilio.airtng.lib.servlets.WebAppServlet;_70import org.twilio.airtng.models.Reservation;_70import org.twilio.airtng.models.User;_70import org.twilio.airtng.repositories.ReservationRepository;_70import org.twilio.airtng.repositories.UserRepository;_70_70import javax.servlet.ServletException;_70import javax.servlet.http.HttpServletRequest;_70import javax.servlet.http.HttpServletResponse;_70import java.io.IOException;_70_70public class ReservationConfirmationServlet extends WebAppServlet {_70_70 private UserRepository userRepository;_70 private ReservationRepository reservationRepository;_70 private SmsNotifier smsNotifier;_70_70 public ReservationConfirmationServlet() {_70 this(new UserRepository(), new ReservationRepository(), new SmsNotifier());_70 }_70_70 public ReservationConfirmationServlet(UserRepository userRepository, ReservationRepository reservationRepository, SmsNotifier smsNotifier) {_70 super();_70 this.userRepository = userRepository;_70 this.reservationRepository = reservationRepository;_70 this.smsNotifier = smsNotifier;_70 }_70_70 @Override_70 public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {_70_70 String phone = request.getParameter("From");_70 String smsContent = request.getParameter("Body");_70_70 String smsResponseText = "Sorry, it looks like you don't have any reservations to respond to.";_70_70 try {_70 User user = userRepository.findByPhoneNumber(phone);_70 Reservation reservation = reservationRepository.findFirstPendantReservationsByUser(user.getId());_70 if (reservation != null) {_70 if (smsContent.contains("yes") || smsContent.contains("accept"))_70 reservation.confirm();_70 else_70 reservation.reject();_70 reservationRepository.update(reservation);_70_70 smsResponseText = String.format("You have successfully %s the reservation", reservation.getStatus().toString());_70 smsNotifier.notifyGuest(reservation);_70 }_70_70 respondSms(response, smsResponseText);_70_70 } catch (Exception e) {_70 throw new RuntimeException(e);_70 }_70 }_70_70 private void respondSms(HttpServletResponse response, String message)_70 throws IOException, TwiMLException {_70 MessagingResponse twiMLResponse = TwiMLHelper.buildSmsRespond(message);_70 response.setContentType("text/xml");_70 response.getWriter().write(twiMLResponse.toXml());_70 }_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
_57package org.twilio.airtng.lib.phonenumber;_57_57import com.twilio.base.ResourceSet;_57import com.twilio.http.TwilioRestClient;_57import com.twilio.rest.api.v2010.account.IncomingPhoneNumberCreator;_57import com.twilio.rest.api.v2010.account.availablephonenumbercountry.Local;_57import com.twilio.type.PhoneNumber;_57import org.twilio.airtng.lib.Config;_57_57public class Purchaser {_57_57 private final TwilioRestClient client;_57_57 public Purchaser() {_57 client = new TwilioRestClient.Builder(Config.getAccountSid(), Config.getAuthToken()).build();_57 }_57_57 public Purchaser(TwilioRestClient client) {_57 this.client = client;_57 }_57_57 public String buyNumber(Integer areaCode) {_57 ResourceSet<Local> availableNumbersForGivenArea = Local.reader("US")_57 .setAreaCode(areaCode)_57 .setSmsEnabled(true)_57 .setVoiceEnabled(true)_57 .read();_57_57 if (availableNumbersForGivenArea.iterator().hasNext()) {_57 PhoneNumber availableNumber = createBuyNumber(_57 availableNumbersForGivenArea.iterator().next().getPhoneNumber()_57 );_57_57 return availableNumber.toString();_57 } else {_57 ResourceSet<Local> generalAvailableNumbers = Local.reader("US")_57 .setSmsEnabled(true)_57 .setVoiceEnabled(true)_57 .read();_57 if (generalAvailableNumbers.iterator().hasNext()) {_57 PhoneNumber availableNumber = createBuyNumber(_57 generalAvailableNumbers.iterator().next().getPhoneNumber()_57 );_57 return availableNumber.toString();_57 } else {_57 return null;_57 }_57 }_57 }_57_57 private PhoneNumber createBuyNumber(PhoneNumber phoneNumber) {_57 return new IncomingPhoneNumberCreator(phoneNumber)_57 .setSmsApplicationSid(Config.getApplicationSid())_57 .setVoiceApplicationSid(Config.getApplicationSid())_57 .create(client).getPhoneNumber();_57 }_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
_43package org.twilio.airtng.servlets;_43_43import com.twilio.twiml.TwiML;_43import com.twilio.twiml.TwiMLException;_43import org.twilio.airtng.lib.servlets.WebAppServlet;_43import org.twilio.airtng.models.Reservation;_43import org.twilio.airtng.repositories.ReservationRepository;_43_43import javax.servlet.http.HttpServletResponse;_43import java.io.IOException;_43import java.util.Objects;_43_43public class BaseExchangeServlet extends WebAppServlet {_43 protected ReservationRepository reservationRepository;_43_43 public BaseExchangeServlet(ReservationRepository reservationRepository) {_43 this.reservationRepository = reservationRepository;_43 }_43_43 protected String gatherOutgoingPhoneNumber(String incomingPhoneNumber, String anonymousPhoneNumber) {_43 String outgoingPhoneNumber = null;_43_43 Reservation reservation = reservationRepository.findByAnonymousPhoneNumber(anonymousPhoneNumber);_43_43 if (Objects.equals(reservation.getUser().getPhoneNumber(), incomingPhoneNumber)) {_43 outgoingPhoneNumber = reservation.getVacationProperty().getUser().getPhoneNumber();_43 } else {_43 outgoingPhoneNumber = reservation.getUser().getPhoneNumber();_43 }_43_43 return outgoingPhoneNumber;_43 }_43_43 protected void respondTwiML(HttpServletResponse response, TwiML twiMLResponse)_43 throws IOException {_43 response.setContentType("text/xml");_43 try {_43 response.getWriter().write(twiMLResponse.toXml());_43 } catch (TwiMLException e) {_43 e.printStackTrace();_43 }_43 }_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
_40package org.twilio.airtng.servlets;_40_40import com.twilio.twiml.Body;_40import com.twilio.twiml.Message;_40import com.twilio.twiml.MessagingResponse;_40import org.twilio.airtng.repositories.ReservationRepository;_40_40import javax.servlet.ServletException;_40import javax.servlet.http.HttpServletRequest;_40import javax.servlet.http.HttpServletResponse;_40import java.io.IOException;_40_40public class ExchangeSmsServlet extends BaseExchangeServlet {_40_40 @SuppressWarnings("unused")_40 public ExchangeSmsServlet() {_40 this(new ReservationRepository());_40 }_40_40 public ExchangeSmsServlet(ReservationRepository reservationRepository) {_40 super(reservationRepository);_40 }_40_40 @Override_40 public void doPost(HttpServletRequest request, HttpServletResponse response)_40 throws ServletException, IOException {_40_40 String from = request.getParameter("From");_40 String to = request.getParameter("To");_40 String body = request.getParameter("Body");_40_40 String outgoingNumber = gatherOutgoingPhoneNumber(from, to);_40_40 MessagingResponse messagingResponse = new MessagingResponse.Builder()_40 .message(new Message.Builder().body(new Body(body)).to(outgoingNumber).build())_40 .build();_40_40 respondTwiML(response, messagingResponse);_40 }_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
_42package org.twilio.airtng.servlets;_42_42import com.twilio.twiml.Dial;_42import com.twilio.twiml.Number;_42import com.twilio.twiml.Play;_42import com.twilio.twiml.VoiceResponse;_42import org.twilio.airtng.repositories.ReservationRepository;_42_42import javax.servlet.ServletException;_42import javax.servlet.http.HttpServletRequest;_42import javax.servlet.http.HttpServletResponse;_42import java.io.IOException;_42_42public class ExchangeVoiceServlet extends BaseExchangeServlet {_42_42 @SuppressWarnings("unused")_42 public ExchangeVoiceServlet() {_42 this(new ReservationRepository());_42 }_42_42 public ExchangeVoiceServlet(ReservationRepository reservationRepository) {_42 super(reservationRepository);_42 }_42_42 @Override_42 public void doPost(HttpServletRequest request, HttpServletResponse response)_42 throws ServletException, IOException {_42_42 String from = request.getParameter("From");_42 String to = request.getParameter("To");_42_42 String outgoingNumber = gatherOutgoingPhoneNumber(from, to);_42_42 VoiceResponse voiceResponse = new VoiceResponse.Builder()_42 .play(new Play.Builder("http://howtodocs.s3.amazonaws.com/howdy-tng.mp3").build())_42 .dial(new Dial.Builder().number(new Number.Builder(outgoingNumber).build()).build())_42 .build();_42_42 respondTwiML(response, voiceResponse);_42 }_42_42}
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.