Java-based Event Ticket Recommendation


1. Setup Tomcat Server

1.0 Why we need HTTP protocal?

  • Set the same format patten for both local and server end to help communication.
  • (req/ res) Header & a blank line to seperate & (req/ res) message body.
  • Http methods support all CRUD operations. 为了区分idempotent(幂等操作) operations: 幂等 用put/ get; 非幂等 用post/delete;

1.1 Concept of RESTful API

  • In this project: 1) We plan to use the HTTP methods to indicate what kind of operation we want to take. The reason is that if the operation are directly based on HTTP methods, then the server could get it directly without parsing extra things. 2) Use HTTP URL to indicate which service we want to use/ what data we want to request. That will be easy for client/users to understand. 3) Each request will be independent, which ensures the server is running in the stateless mode and improve the scalability.

1.2 Concept of Tomcat/ RPC/ Java Servlet

  • Tomcat Server is an open-source Java Servlet Container that provides “pure Java” http wep server environment (including low level supports: making TCP connection/ revceiving request from client/ send response back to client…) in which Java code can run. 帮忙做http 请求的分发。
  • RPC (Remode Procedure Call) is a function call to the remote server.
  • Java Servlets are Java classes to handle RPC calls on the server side.

1.3 Setup Tomcat Server within Eclips

  • Go to Servers, find Apache -> Tomcat v9.0 Server and specify the path where the Tomcat installation package was downloaded. Then the Tomcat v9.0 Server will be at localhost.
  • Double Click ‘Tomcat v9.0 Server at localhost’ in Server window. In ‘Server Locations’, click ‘Use Tomcat installation’. Save Config.
  • Right click ‘Tomcat v9.0 Server at localhost’, choose ‘Properties’. Click ‘Switch Location’ to change the location to /Servers/Tomcat v9.0 Server at localhost.server.
  • Start Tomcat Server, check connection on Port: 8080.

2. Implement RPC Handlers

2.1 Implement the SearchItem servlet (class)

  • Create the first servlet/class SearchItem. Eclipse creates the Java class for us automatically. Then we need to change the endpoint to “/search”.

    1
    @WebServlet("/search")
  • Within this project, server and client need to communicate by JSON object format. To enable this, we need to download the JSON library .jar file and paste it into WEB-INFO -> lib folder.

2.2 Implement the RecommendItem servlet (class)

  • Create the servlet/class RecommendItem, then we change the endpoint to “/recommendation”.
    1
    @WebServlet("/recommendation")

3. Fetch data from TicketMaster API

3.1 Create a RpcHelper Class to help write Json Array/ Obj.

  • Create a new class in rpc, define the writeJsonArray() function and writeJsonObject() function inside RpcHelper class.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public class RpcHelper {
    ...

    // Writes a JSONArray to http response.
    public static void writeJsonArray(HttpServletResponse response, JSONArray array) throws IOException {
    response.setContentType("application/json");
    response.setHeader("Access-Control-Allow-Origin", "*");
    PrintWriter out = response.getWriter();
    out.print(array);
    out.close();
    }
    // Writes a JSONObject to http response.
    public static void writeJsonObject(HttpServletResponse response, JSONObject obj) throws IOException {
    response.setContentType("application/json");
    response.setHeader("Access-Control-Allow-Origin", "*");
    PrintWriter out = response.getWriter();
    out.print(obj);
    out.close();
    }
    }
  • Update the doGet() function in both SearchItem and RecommendItem Servlet to use these helper functions.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
    JSONArray array = new JSONArray();
    // test with some dummy data here
    try {
    array.put(new JSONObject().put("username", "abcd").put("address", "San Francisco").put("time", "01/01/2017"));
    array.put(new JSONObject().put("username", "1234").put("address", "San Jose").put("time", "01/02/2017"));
    } catch (JSONException e) {
    e.printStackTrace();
    }
    RpcHelper.writeJsonArray(response, array);
    }

3.2 Look into TicketMaster API

  • TicketMaster is a web-based API from which users could get real event informations. In this project, data will be fetched from TicketMaster API. For security reasons, TicketMaster requires users to register their own uniqe API key, so that they know who is fetching data from their API.
  • Firstly, we need to look through the Documents provided by TicketMaster to undetstand how datas are stored in thier object structure.
  • According to thier settings, the endpoint for event searching is /discovery/v2/events, Therefore, the request URL will be something like: https//app.ticketmaster.com/discovery/v2/events.json?apikey=<YOUR_UNIQUE_API_KEY>&latlong=<YOUR_LAT,YOUR_LNG>&keyword=<YOUR_SEARCH_KEY_WORD>&radius=<SEARCH_RADIUS>.

3.3 Get data from TicketMaster API.

  • Create a new Package named external, within this package create a class TicketMasterAPI for calling the TicketMaster API. Some constants are needed to be updated here.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    package external;
    import java.io.BufferedReader;
    import java.io.InputStreamReader;
    import java.net.HttpURLConnection;
    import java.net.URL;
    import java.net.URLEncoder;
    import org.json.JSONArray;
    import org.json.JSONObject;

    public class TicketMasterAPI {
    private static final String URL = "https://app.ticketmaster.com/discovery/v2/events.json";
    private static final String DEFAULT_KEYWORD = ""; // no restriction
    private static final String API_KEY = "USE_YOUR_OWN_KEY";
    }
  • Add search() method to calling the TicketMaster and getting datas, and add another test method queryAPI() to temporaryly print the search result for debugging. Create a main function main() to call queryAPI().

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    public class TicketMasterAPI {
    private static final String URL = "https://app.ticketmaster.com/discovery/v2/events.json";
    private static final String DEFAULT_KEYWORD = ""; // no restriction
    private static final String API_KEY = "USE_YOUR_OWN_KEY";

    public List<Item> search(double lat, double lon, String keyword) {
    if (keyword == null) {
    keyword = DEFAULT_KEYWORD;
    }
    try {
    keyword = URLEncoder.encode(keyword, "UTF-8");
    } catch (UnsupportedEncodingException e) {
    e.printStackTrace();
    }

    String query = String.format("apikey=%s&latlong=%s,%s&keyword=%s&radius=%s", API_KEY, lat, lon, keyword, 50);
    String url = URL + "?" + query;

    try {
    HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
    connection.setRequestMethod("GET");

    int responseCode = connection.getResponseCode();
    System.out.println("Sending request to url: " + url);
    System.out.println("Response code: " + responseCode);

    if (responseCode != 200) {
    return new ArrayList<>();
    }

    BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
    String line;
    StringBuilder response = new StringBuilder();

    while ((line = reader.readLine()) != null) {
    response.append(line);
    }
    reader.close();
    JSONObject obj = new JSONObject(response.toString());

    if (!obj.isNull("_embedded")) {
    JSONObject embedded = obj.getJSONObject("_embedded");
    return embedded.getItemList("events");
    }
    } catch (Exception e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    }
    return new ArrayList<>();
    }

    // Convert JSONArray to a list of item objects.
    private List<Item> getItemList(JSONArray events) throws JSONException {
    List<Item> itemList = new ArrayList<>();
    return itemList;
    }

    private void queryAPI(double lat, double lon) {
    List<Item> events = search(lat, lon, null);
    for (Item event : events) {
    System.out.println(event.toJSONObject());
    }
    }

    /**
    * Main entry for sample TicketMaster API requests.
    */
    public static void main(String[] args) {
    TicketMasterAPI tmApi = new TicketMasterAPI();
    // Give a dummy query location for testing: (Mountain View, CA)
    tmApi.queryAPI(37.38, -122.08);
    }
    }

3.4 Purify the data return from TicketMaster API.

  • In this project, we only need some fields within all fields return by TicketMaster, so that’s the reason why we need to purify it before send it back.

  • Create a new package entity to hold data for us. Within this package, new a class Item.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    package entity;
    import java.util.Set;

    public class Item {
    private String itemId;
    private String name;
    private double rating;
    private String address;
    private Set<String> categories;
    private String imageUrl;
    private String url;
    private double distance;
    }
  • Add Getters and Setters. In Source -> Generate Getters and Setters by clicking Select Getters, Eclips will automatically generate all getters for us.

  • Then, we need to add a toJSONObject() method, since we need to convert item datas into JSON format to send to the frontend.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public JSONObject toJSONObject() {
    JSONObject obj = new JSONObject();
    try {
    obj.put("item_id", itemId);
    obj.put("name", name);
    obj.put("rating", rating);
    obj.put("address", address);
    obj.put("categories", new JSONArray(categories));
    obj.put("image_url", imageUrl);
    obj.put("url", url);
    obj.put("distance", distance);
    } catch (JSONException e) {
    e.printStackTrace();
    }
    return obj;
    }

3.5 Build a Builder Pattern to easily handle thc construction of a complex object.

  • Within class Item, add a static new class ItemBuilder and move all fileds into this class. Generate setters for all data fields in this ItemBuilder.

  • To use this builder pattern, we create a private constructor.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    public class Item {
    private String itemId;
    private String name;
    private double rating;
    private String address;
    private Set<String> categories;
    private String imageUrl;
    private String url;
    private double distance;

    public static class ItemBuilder {
    private String itemId;
    private String name;
    private double rating;
    private String address;
    private Set<String> categories;
    private String imageUrl;
    private String url;
    private double distance;

    // Generate Setters for all fields
    public ItemBuilder setItemId(String itemId) {
    this.itemId = itemId;
    return this;
    }
    public ItemBuilder setName(String name) {
    this.name = name;
    return this;
    }
    public ItemBuilder setRating(double rating) {
    this.rating = rating;
    return this;
    }
    public ItemBuilder setAddress(String address) {
    this.address = address;
    return this;
    }
    public ItemBuilder setCategories(Set<String> categories) {
    this.categories = categories;
    return this;
    }
    public ItemBuilder setImageUrl(String imageUrl) {
    this.imageUrl = imageUrl;
    return this;
    }
    public ItemBuilder setUrl(String url) {
    this.url = url;
    return this;
    }
    public ItemBuilder setDistance(double distance) {
    this.distance = distance;
    return this;
    }

    // define a build function to create a ItemBuilder object
    public Item build() {
    return new Item(this);
    }
    }
    /**
    * This is a builder pattern in Java.
    */
    private Item(ItemBuilder builder) {
    this.itemId = builder.itemId;
    this.name = builder.name;
    this.rating = builder.rating;
    this.address = builder.address;
    this.categories = builder.categories;
    this.imageUrl = builder.imageUrl;
    this.url = builder.url;
    this.distance = builder.distance;
    }

    ...

    }
  • According to the body structure, we need to add multiple helper functions to fetch data which are included deeply in TicketMaster API response body.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    public class TicketMasterAPI {

    ...

    private String getAddress(JSONObject event) throws JSONException {
    if (!event.isNull("_embedded")) {
    JSONObject embedded = event.getJSONObject("_embedded");
    if (!embedded.isNull("venues")) {
    JSONArray venues = embedded.getJSONArray("venues");
    for (int i = 0; i < venues.length(); ++i) {
    JSONObject venue = venues.getJSONObject(i);
    StringBuilder builder = new StringBuilder();
    if (!venue.isNull("address")) {
    JSONObject address = venue.getJSONObject("address");
    if (!address.isNull("line1")) {
    builder.append(address.getString("line1"));
    }
    if (!address.isNull("line2")) {
    builder.append(",");
    builder.append(address.getString("line2"));
    }
    if (!address.isNull("line3")) {
    builder.append(",");
    builder.append(address.getString("line3"));
    }
    }
    if (!venue.isNull("city")) {
    JSONObject city = venue.getJSONObject("city");
    builder.append(",");
    builder.append(city.getString("name"));
    }
    String result = builder.toString();
    if (!result.isEmpty()) {
    return result;
    }
    }
    }
    }
    return "";
    }

    private String getImageUrl(JSONObject event) throws JSONException {
    if (!event.isNull("images")){
    JSONArray images = event.getJSONArray("images");
    for (int i = 0; i < images.length(); ++i) {
    JSONObject image = array.getJSONObject(i);
    if(!image.isNull("url")){
    return image.getString("url");
    }
    }
    }
    return "";
    }

    private Set<String> getCategories(JSONObject event) throws JSONException {
    Set<String> categories = new HashSet<>();
    if (!event.isNull("classifications")) {
    JSONArray classifications = event.getJSONArray("classifications");
    for (int i = 0; i < classifications.length(); ++i) {
    JSONObject classification = classifications.getJSONObject(i);
    if (!classification.isNull("segment")) {
    JSONObject segment = classification.getJSONObject("segment");
    if (!segment.isNull("name")) {
    categories.add(segment.getString("name"));
    }
    }
    }
    }
    return categories;
    }
    }

3.6 Connect the SearchItem servlet to TicketMasterAPI

  • Update thedoGet() method within the SearchItem class.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public class SearchItem extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
    double lat = Double.parseDouble(request.getParameter("lat"));
    double lon = Double.parseDouble(request.getParameter("lon"));

    TicketMasterAPI tmAPI = new TicketMasterAPI();
    List<Item> items = tmAPI.search(lat, lon, null);

    JSONArray array = new JSONArray();
    for (Item item : items) {
    array.put(item.toJSONObject());
    }
    RpcHelper.writeJsonArray(response, array);
    }
    ...
    }

4. Setup MySQL database

5. Setup MongoDB database

6. Implement Login/ Logout

7. Implement Recommendation function

8. Deploy on Amazon EC2


Author: Luchen
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint polocy. If reproduced, please indicate source Luchen !
  TOC