Skip to contentSkip to navigationSkip to topbar
Rate this page:
On this page

Workflow Automation with Java and Servlets


One of the more abstract concepts you'll handle when building your business is what the workflow will look like.

At its core, setting up a standardized workflow is about enabling your service providers (agents, hosts, customer service reps, administrators, and the rest of the gang) to better serve your customers.

To illustrate a very real-world example, today we'll build a Java and Servlets webapp for finding and booking vacation properties — tentatively called Airtng.

Here's how it'll work:

  1. A host creates a vacation property listing
  2. A guest requests a reservation for a property
  3. The host receives an SMS notifying them of the reservation request. The host can either Accept or Reject the reservation
  4. The guest is notified whether a request was rejected or accepted

Learn how Airbnb used Twilio SMS to streamline the rental experience for 60M+ travelers around the world.(link takes you to an external page)


Workflow Building Blocks

workflow-building-blocks page anchor

We'll be using the Twilio REST API to send our users messages at important junctures. Here's a bit more on our API:

  • Sending Messages with Twilio API

User and Session Management

user-and-session-management page anchor

For this workflow to work, we need to have a user model and allow logins.

User and Session Management

user-and-session-management-1 page anchor

src/main/java/org/twilio/airtng/models/User.java


_116
package org.twilio.airtng.models;
_116
_116
import javax.persistence.*;
_116
import java.util.ArrayList;
_116
import java.util.List;
_116
_116
@Entity
_116
@Table(name = "users")
_116
public class User {
_116
@Id
_116
@GeneratedValue(strategy = GenerationType.IDENTITY)
_116
@Column(name = "id")
_116
private long id;
_116
_116
@Column(name = "name")
_116
private String name;
_116
_116
@Column(name = "email")
_116
private String email;
_116
_116
@Column(name = "password")
_116
private String password;
_116
_116
@Column(name = "phone_number")
_116
private String phoneNumber;
_116
_116
@OneToMany(mappedBy="user")
_116
private List<Reservation> reservations;
_116
_116
@OneToMany(mappedBy="user")
_116
private List<VacationProperty> vacationProperties;
_116
_116
public User() {
_116
this.reservations = new ArrayList<>();
_116
this.vacationProperties = new ArrayList<>();
_116
}
_116
_116
public User(
_116
String name,
_116
String email,
_116
String password,
_116
String phoneNumber) {
_116
this();
_116
this.name = name;
_116
this.email = email;
_116
this.password = password;
_116
this.phoneNumber = phoneNumber;
_116
}
_116
_116
public long getId() {
_116
return id;
_116
}
_116
_116
public void setId(long id) {
_116
this.id = id;
_116
}
_116
_116
public String getName() {
_116
return name;
_116
}
_116
_116
public void setName(String name) {
_116
this.name = name;
_116
}
_116
_116
public String getEmail() {
_116
return email;
_116
}
_116
_116
public void setEmail(String email) {
_116
this.email = email;
_116
}
_116
_116
public String getPassword() {
_116
return password;
_116
}
_116
_116
public void setPassword(String password) {
_116
this.password = password;
_116
}
_116
_116
public String getPhoneNumber() {
_116
return phoneNumber;
_116
}
_116
_116
public void setPhoneNumber(String phoneNumber) {
_116
this.phoneNumber = phoneNumber;
_116
}
_116
_116
public void addReservation(Reservation reservation) {
_116
_116
if (this.reservations.add(reservation) && reservation.getUser() != this) {
_116
reservation.setUser(this);
_116
}
_116
}
_116
_116
public void removeReservation(Reservation reservation) {
_116
if (this.reservations.remove(reservation) && reservation.getUser() == this) {
_116
reservation.setUser(null);
_116
}
_116
}
_116
_116
public void addVacationProperty(VacationProperty vacationProperty) {
_116
_116
if (this.vacationProperties.add(vacationProperty) && vacationProperty.getUser() != this) {
_116
vacationProperty.setUser(this);
_116
}
_116
}
_116
_116
public void removeVacationProperty(VacationProperty vacationProperty) {
_116
if (this.reservations.remove(vacationProperty) && vacationProperty.getUser() == this) {
_116
vacationProperty.setUser(null);
_116
}
_116
}
_116
_116
}

Next, let's look at the Vacation Property model.


In order to build a true vacation rental company we'll need a way to create a property rental listing.

The VacationProperty model belongs to the User who created it (we'll call this user the host moving forward) and contains only two properties: a description and an imageUrl.

The model has two associations implemented: there are many reservations in it and many users can make those reservations.

src/main/java/org/twilio/airtng/models/VacationProperty.java


_83
package org.twilio.airtng.models;
_83
_83
import javax.persistence.*;
_83
import java.util.ArrayList;
_83
import java.util.List;
_83
_83
@Entity
_83
@Table(name = "vacation_properties")
_83
public class VacationProperty {
_83
_83
@javax.persistence.Id
_83
@GeneratedValue(strategy = GenerationType.IDENTITY)
_83
@Column(name = "id")
_83
private long id;
_83
_83
@Column(name = "description")
_83
private String description;
_83
_83
@Column(name = "image_url")
_83
private String imageUrl;
_83
_83
@OneToMany(mappedBy = "vacationProperty")
_83
private List<Reservation> reservations;
_83
_83
@ManyToOne(fetch = FetchType.LAZY)
_83
@JoinColumn(name = "user_id")
_83
private User user;
_83
_83
public VacationProperty() {
_83
this.reservations = new ArrayList<Reservation>();
_83
}
_83
_83
public VacationProperty(String description, String imageUrl, User user) {
_83
this.description = description;
_83
this.imageUrl = imageUrl;
_83
this.user = user;
_83
}
_83
_83
public long getId() {
_83
return id;
_83
}
_83
_83
public void setId(long id) {
_83
this.id = id;
_83
}
_83
_83
public String getDescription() {
_83
return description;
_83
}
_83
_83
public void setDescription(String description) {
_83
this.description = description;
_83
}
_83
_83
public String getImageUrl() {
_83
return imageUrl;
_83
}
_83
_83
public void setImageUrl(String imageUrl) {
_83
this.imageUrl = imageUrl;
_83
}
_83
_83
public User getUser() {
_83
return this.user;
_83
}
_83
_83
public void setUser(User user) {
_83
this.user = user;
_83
}
_83
_83
public void addReservation(Reservation reservation) {
_83
_83
if (this.reservations.add(reservation) && reservation.getVacationProperty() != this) {
_83
reservation.setVacationProperty(this);
_83
}
_83
}
_83
_83
public void removeReservation(Reservation reservation) {
_83
if (this.reservations.remove(reservation) && reservation.getVacationProperty() == this) {
_83
reservation.setVacationProperty(null);
_83
}
_83
}
_83
}

Next, let's look at the all-important Reservation model.


The Reservation model is at the center of the workflow for this application. It is responsible for keeping track of:

  • The guest who performed the reservation
  • The vacation property the guest is requesting (and associated host )
  • The status of the reservation: pending , confirmed , or rejected

src/main/java/org/twilio/airtng/models/Reservation.java


_133
package org.twilio.airtng.models;
_133
_133
import javax.persistence.*;
_133
_133
@Entity
_133
@Table(name = "reservations")
_133
public class Reservation {
_133
_133
@javax.persistence.Id
_133
@GeneratedValue(strategy = GenerationType.IDENTITY)
_133
@Column(name = "id")
_133
private long id;
_133
_133
@Column(name = "message")
_133
private String message;
_133
_133
@Column(name = "status")
_133
private Integer status;
_133
_133
@ManyToOne(fetch = FetchType.LAZY)
_133
@JoinColumn(name = "user_id")
_133
private User user;
_133
_133
@ManyToOne(fetch = FetchType.LAZY)
_133
@JoinColumn(name = "vacation_property_id")
_133
private VacationProperty vacationProperty;
_133
_133
public Reservation() {
_133
}
_133
_133
public Reservation(String message, VacationProperty vacationProperty, User user) {
_133
this();
_133
this.message = message;
_133
this.vacationProperty = vacationProperty;
_133
this.user = user;
_133
this.setStatus(Status.Pending);
_133
}
_133
_133
public long getId() {
_133
return id;
_133
}
_133
_133
public void setId(long id) {
_133
this.id = id;
_133
}
_133
_133
public String getMessage() {
_133
return message;
_133
}
_133
_133
public void setMessage(String message) {
_133
this.message = message;
_133
}
_133
_133
public Status getStatus() {
_133
return Status.getType(this.status);
_133
}
_133
_133
public void setStatus(Status status) {
_133
if (status == null) {
_133
this.status = null;
_133
} else {
_133
this.status = status.getValue();
_133
}
_133
}
_133
_133
public VacationProperty getVacationProperty() {
_133
return vacationProperty;
_133
}
_133
_133
public void setVacationProperty(VacationProperty vacationProperty) {
_133
this.vacationProperty = vacationProperty;
_133
}
_133
_133
public User getUser() {
_133
return this.user;
_133
}
_133
_133
public void setUser(User user) {
_133
this.user = user;
_133
}
_133
_133
public void reject() {
_133
this.setStatus(Status.Rejected);
_133
}
_133
_133
public void confirm() {
_133
this.setStatus(Status.Confirmed);
_133
}
_133
_133
public enum Status {
_133
_133
Pending(0), Confirmed(1), Rejected(2);
_133
_133
private int value;
_133
_133
Status(int value) {
_133
this.value = value;
_133
}
_133
_133
public int getValue() {
_133
return value;
_133
}
_133
_133
@Override
_133
public String toString() {
_133
switch (value) {
_133
case 0:
_133
return "pending";
_133
case 1:
_133
return "confirmed";
_133
case 2:
_133
return "rejected";
_133
}
_133
return "Value out of range";
_133
}
_133
_133
_133
public static Status getType(Integer id) {
_133
_133
if (id == null) {
_133
return null;
_133
}
_133
_133
for (Status status : Status.values()) {
_133
if (id.equals(status.getValue())) {
_133
return status;
_133
}
_133
}
_133
throw new IllegalArgumentException("No matching type for value " + id);
_133
}
_133
}
_133
}

Next up, we'll show how our new application will create a new reservation.


The reservation creation form holds only one field: the message that will be sent to the host when reserving one of her properties.

The rest of the information necessary to create a reservation is taken from the logged in user and the relationship between a property and its owner. Our base generic Repository(link takes you to an external page) is in charge of handling entity insertion.

A reservation is created with a default status pending. This lets our application easily react to a host rejecting or accepting a reservation request.

Create a new reservation

create-a-new-reservation page anchor

src/main/java/org/twilio/airtng/servlets/ReservationServlet.java


_81
package org.twilio.airtng.servlets;
_81
_81
import org.twilio.airtng.lib.notifications.SmsNotifier;
_81
import org.twilio.airtng.lib.servlets.WebAppServlet;
_81
import org.twilio.airtng.lib.web.request.validators.RequestParametersValidator;
_81
import org.twilio.airtng.models.Reservation;
_81
import org.twilio.airtng.models.User;
_81
import org.twilio.airtng.models.VacationProperty;
_81
import org.twilio.airtng.repositories.ReservationRepository;
_81
import org.twilio.airtng.repositories.UserRepository;
_81
import org.twilio.airtng.repositories.VacationPropertiesRepository;
_81
_81
import javax.servlet.ServletException;
_81
import javax.servlet.http.HttpServletRequest;
_81
import javax.servlet.http.HttpServletResponse;
_81
import java.io.IOException;
_81
_81
public 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
}

Now let's look at how the system will notify a host when a new reservation request is submitted.


When a reservation is created for a property we want to notify the owner that someone has made a reservation.

We use an abstraction called SmsNotifier which under the hood uses another abstraction called Sender. Here is where we use Twilio's Rest API to send an SMS message to the host using your Twilio phone number(link takes you to an external page). That's right - it's as simple as that to send a SMS with Twilio.

src/main/java/org/twilio/airtng/lib/sms/Sender.java


_28
package org.twilio.airtng.lib.sms;
_28
_28
import com.twilio.http.TwilioRestClient;
_28
import com.twilio.rest.api.v2010.account.MessageCreator;
_28
import com.twilio.type.PhoneNumber;
_28
import org.twilio.airtng.lib.Config;
_28
_28
public class Sender {
_28
_28
private final TwilioRestClient client;
_28
_28
public Sender() {
_28
client = new TwilioRestClient.Builder(Config.getAccountSid(), Config.getAuthToken()).build();
_28
}
_28
_28
public Sender(TwilioRestClient client) {
_28
this.client = client;
_28
}
_28
_28
public void send(String to, String message) {
_28
new MessageCreator(
_28
new PhoneNumber(to),
_28
new PhoneNumber(Config.getPhoneNumber()),
_28
message
_28
).create(client);
_28
}
_28
_28
}

The next step shows how to handle a host accepting or rejecting a request - let's continue.


Handle Incoming Messages

handle-incoming-messages page anchor

Let's take a closer look at the ReservationConfirmation servlet. This servlet handles our incoming Twilio request and does three things:

  1. Check for a pending reservation from the incoming user.
  2. Update the status of the reservation.
  3. Respond to the host and send notification to the guest.

Incoming Twilio Requests

incoming-twilio-requests page anchor

In the Twilio console(link takes you to an external page), you should change the 'A Message Comes In' webhook to call your application's endpoint in the route /reservation-confirmation:

SMS Webhook.

One way to expose your machine to the world during development is to use ngrok(link takes you to an external page). Your URL for the SMS web hook on your phone number should look something like this:


_10
http://<subdomain>.ngrok.io/reservation-confirmation

An incoming request from Twilio comes with some helpful parameters, including the From phone number and the message Body.

We'll use the From parameter to lookup the host and check if he/she has any pending reservations. If he/she does, we'll use the message body to check for an 'accept' or 'reject'.

Finally, we update the reservation status and use the SmsNotifier abstraction to send an SMS to the guest with the news.

Webhook for handling Host's decision


_70
package org.twilio.airtng.servlets;
_70
_70
import com.twilio.twiml.MessagingResponse;
_70
import com.twilio.twiml.TwiMLException;
_70
import org.twilio.airtng.lib.helpers.TwiMLHelper;
_70
import org.twilio.airtng.lib.notifications.SmsNotifier;
_70
import org.twilio.airtng.lib.servlets.WebAppServlet;
_70
import org.twilio.airtng.models.Reservation;
_70
import org.twilio.airtng.models.User;
_70
import org.twilio.airtng.repositories.ReservationRepository;
_70
import org.twilio.airtng.repositories.UserRepository;
_70
_70
import javax.servlet.ServletException;
_70
import javax.servlet.http.HttpServletRequest;
_70
import javax.servlet.http.HttpServletResponse;
_70
import java.io.IOException;
_70
_70
public 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
}

Next up, let's see how we will respond to Twilio.


Finally, we'll use Twilio's TwiML as a response to Twilio's server instructing it to send SMS notification message to the host.

We'll be using the Message(link takes you to an external page) verb to instruct Twilio's server that it should send the message.

src/main/java/org/twilio/airtng/servlets/ReservationConfirmationServlet.java


_70
package org.twilio.airtng.servlets;
_70
_70
import com.twilio.twiml.MessagingResponse;
_70
import com.twilio.twiml.TwiMLException;
_70
import org.twilio.airtng.lib.helpers.TwiMLHelper;
_70
import org.twilio.airtng.lib.notifications.SmsNotifier;
_70
import org.twilio.airtng.lib.servlets.WebAppServlet;
_70
import org.twilio.airtng.models.Reservation;
_70
import org.twilio.airtng.models.User;
_70
import org.twilio.airtng.repositories.ReservationRepository;
_70
import org.twilio.airtng.repositories.UserRepository;
_70
_70
import javax.servlet.ServletException;
_70
import javax.servlet.http.HttpServletRequest;
_70
import javax.servlet.http.HttpServletResponse;
_70
import java.io.IOException;
_70
_70
public 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
}

Congrats! You've just learned how to automate your workflow with Twilio SMS.

Next, let's look at some other interesting features you might want to add to your application.


Like Twilio? Like Java? You're in the right place. Here are a couple other excellent tutorials:

SMS and MMS notifications

Build a server notification system that will alert all administrators via SMS when a server outage occurs.

Click To Call

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

Did this help?

did-this-help page anchor

Thanks for checking this tutorial out! Tweet to us @twilio(link takes you to an external page) with what you're building!


Rate this page: