org.transitime.custom.missionBay.SfmtaApiCaller.java Source code

Java tutorial

Introduction

Here is the source code for org.transitime.custom.missionBay.SfmtaApiCaller.java

Source

/*
 * This file is part of Transitime.org
 * 
 * Transitime.org is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License (GPL) as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 *
 * Transitime.org is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Transitime.org .  If not, see <http://www.gnu.org/licenses/>.
 */

package org.transitime.custom.missionBay;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.net.ssl.HttpsURLConnection;

import org.apache.commons.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.transitime.config.IntegerConfigValue;
import org.transitime.config.StringConfigValue;
import org.transitime.db.structs.AvlReport;

/**
 *
 *
 * @author SkiBu Smith
 *
 */
public class SfmtaApiCaller {

    private static StringConfigValue telemetryUrl = new StringConfigValue("transitime.sfmta.telemetryUrl",
            "https://services.sfmta.com/shuttle/api/Telemetries/", "The telemetry URL for the SFMTA API");

    private static StringConfigValue stopsUrl = new StringConfigValue("transitime.sfmta.telemetryUrl",
            "https://services.sfmta.com/shuttle/api/StopEvents/", "The stops URL for the SFMTA API");

    private static StringConfigValue login = new StringConfigValue("transitime.sfmta.login", "TransitTimeApiUser",
            "Login for basic authentication.");

    private static StringConfigValue password = new StringConfigValue("transitime.sfmta.password",
            "SFMTA$hutt1e$3G", "Password for basic authentication.");

    private static StringConfigValue techProviderId = new StringConfigValue("transitime.sfmta.techProviderId",
            "248", "The TechProviderId for the SFMTA API");

    private static StringConfigValue shuttleCompanyId = new StringConfigValue("transitime.sfmta.shuttleCompanyId",
            "11", "The ShuttleCompanyId for the SFMTA API");

    private static IntegerConfigValue timeout = new IntegerConfigValue("transitime.sfmta.timeout", 10000,
            "Timeout in msec for API calls. A timeout of 0 is " + "interpreted as an infinit timeout.");

    private static IntegerConfigValue avlReportsBatchSize = new IntegerConfigValue(
            "transitime.sfmta.avlReportsBatchSize", 10,
            "Won't actually post AVL reports to SFMTA API until this " + "many have been received.");

    private static class VInfo {
        VInfo(String vehiclePlacardNum, String licensePlateNum) {
            this.vehiclePlacardNum = vehiclePlacardNum;
            this.licensePlateNum = licensePlateNum;
        }

        private String vehiclePlacardNum;
        private String licensePlateNum;
    }

    private static Map<String, VInfo> vehicleInfo = null;

    // For format: 2014-11-05T08:15:30-08:00
    private static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss-08:00");

    // For batching the AVL reports
    private static List<AvlReport> avlReports = new ArrayList<AvlReport>();

    private static final Logger logger = LoggerFactory.getLogger(SfmtaApiCaller.class);

    /********************** Member classes *******************************/

    /**
     * Contains the data for a SFMTA API stop report
     */
    private static class StopInfo {
        private String vehicleId;
        private int stopId;
        private double stopLat;
        private double stopLon;
        private Date arrivalTime;
        private Date departureTime;

        /**
         * Constructor
         * 
         * @param vehicleId
         * @param stopId Numeric SFMTA stop ID
         * @param stopLat
         * @param stopLon
         * @param arrivalTime
         * @param departureTime
         */
        public StopInfo(String vehicleId, int stopId, double stopLat, double stopLon, Date arrivalTime,
                Date departureTime) {
            this.vehicleId = vehicleId;
            this.stopId = stopId;
            this.stopLat = stopLat;
            this.stopLon = stopLon;
            this.arrivalTime = arrivalTime;
            this.departureTime = departureTime;
        }
    }

    /********************** Member Functions  **************************/

    private static void init() {
        if (vehicleInfo != null)
            return;

        vehicleInfo = new HashMap<String, VInfo>();

        vehicleInfo.put("1207", new VInfo("11-004", "8JO8212"));
        vehicleInfo.put("1209", new VInfo("11-006", "8JO8211"));
        vehicleInfo.put("N-1210", new VInfo("11-003", "8JO8206"));
        vehicleInfo.put("1369", new VInfo("11-002", "96458B1"));
        vehicleInfo.put("1370", new VInfo("11-001", "06459B1"));
        vehicleInfo.put("947", new VInfo("11-007", "07276B1"));
        vehicleInfo.put("1552", new VInfo("11-008", "83093S1"));
        vehicleInfo.put("1553", new VInfo("11-009", "83092S1"));
        vehicleInfo.put("1285", new VInfo("11-005", "8U13888"));

        // These buses seem to have existed at some point but are supposedly now not active 
        vehicleInfo.put("693", new VInfo("693", "693"));
        vehicleInfo.put("694", new VInfo("694", "694"));
    }

    /**
     * Returns date/time in format 2014-11-05T08:15:30-08:00
     * 
     * @param date
     * @return
     */
    private static String formattedTime(Date date) {
        return dateFormat.format(date);
    }

    /**
     * Returns JSON for telemetry API call
     * 
     * @param avlReports
     * @return
     */
    private static String getTelemetryJson(List<AvlReport> avlReports) {
        StringBuilder sb = new StringBuilder();

        sb.append("{ \n");
        sb.append("  \"TechProviderId\": " + techProviderId.getValue() + ", \n");
        sb.append("  \"ShuttleCompanyId\": \"" + shuttleCompanyId.getValue() + "\", \n");

        sb.append("  \"Telemetries\": { \n");
        sb.append("    \"Telemetry\": [ \n");

        boolean firstAvlReport = true;
        for (AvlReport avlReport : avlReports) {
            if (!firstAvlReport)
                sb.append("    ,\n");
            firstAvlReport = false;

            VInfo vinfo = vehicleInfo.get(avlReport.getVehicleId());
            if (vinfo == null) {
                logger.error("No info for vehicleId={}", avlReport.getVehicleId());
                continue;
            }

            sb.append("    { \n");
            sb.append("      \"VehiclePlacardNum\": \"" + vinfo.vehiclePlacardNum + "\",\n");
            sb.append("      \"LicensePlateNum\": \"" + vinfo.licensePlateNum + "\",\n");

            // Stop info is not know but desired by the API??!?
            sb.append("      \"VehicleStatus\": " + 1 + ",\n");
            //         sb.append("      \"StopId\": " + 0 + ",\n");
            //         sb.append("      \"StopLocationLatitude\": " + 0.0 + ",\n");
            //         sb.append("      \"StopLocationLongitude\": " + 0.0 + ",\n");

            sb.append("      \"LocationLatitude\": " + avlReport.getLat() + ",\n");
            sb.append("      \"LocationLongitude\": " + avlReport.getLon() + ",\n");
            sb.append("      \"TimeStampLocal\": \"" + formattedTime(avlReport.getDate()) + "\"\n");
            sb.append("    } \n");
        }
        sb.append("    ] \n");
        sb.append("  } \n");
        sb.append("} \n");

        return sb.toString();
    }

    /**
     * Returns JSON for stop API call
     * 
     * @param stopInfos
     * @return
     */
    private static String getStopJson(List<StopInfo> stopInfos) {
        StringBuilder sb = new StringBuilder();

        sb.append("{ \n");
        sb.append("  \"TechProviderId\": " + techProviderId.getValue() + ", \n");
        sb.append("  \"ShuttleCompanyId\": \"" + shuttleCompanyId.getValue() + "\", \n");

        sb.append("  \"StopEvents\": { \n");
        sb.append("    \"StopEvent\": [ \n");

        boolean firstStopReport = true;
        for (StopInfo stopInfo : stopInfos) {
            if (!firstStopReport)
                sb.append("    ,\n");
            firstStopReport = false;

            VInfo vinfo = vehicleInfo.get(stopInfo.vehicleId);
            if (vinfo == null) {
                logger.error("No info for vehicleId={}", stopInfo.vehicleId);
                continue;
            }
            sb.append("    { \n");
            sb.append("      \"VehiclePlacardNum\": \"" + vinfo.vehiclePlacardNum + "\",\n");
            sb.append("      \"LicensePlateNum\": \"" + vinfo.licensePlateNum + "\",\n");
            sb.append("      \"StopId\": " + stopInfo.stopId + ",\n");
            sb.append("      \"StopTimeStart\": \"" + formattedTime(stopInfo.arrivalTime) + "\",\n");
            sb.append("      \"StopTimeEnd\": \"" + formattedTime(stopInfo.departureTime) + "\"\n");
            sb.append("      \"StopLocationLatitude\": " + stopInfo.stopLat + ",\n");
            sb.append("      \"StopLocationLongitude\": " + stopInfo.stopLon + ",\n");
            sb.append("    } \n");
        }
        sb.append("    ] \n");
        sb.append("  } \n");
        sb.append("} \n");

        return sb.toString();
    }

    /**
     * Posts the JSON string to the URL. For either the telemetry or the stop
     * command.
     * 
     * @param baseUrl
     * @param jsonStr
     * @return True if successfully posted the data
     */
    private static boolean post(String baseUrl, String jsonStr) {
        try {
            // Create the connection
            URL url = new URL(baseUrl);
            HttpsURLConnection con = (HttpsURLConnection) url.openConnection();

            // Set parameters for the connection
            con.setRequestMethod("POST");
            con.setRequestProperty("content-type", "application/json");
            con.setDoOutput(true);
            con.setDoInput(true);
            con.setUseCaches(false);

            // API now uses basic authentication
            String authString = login.getValue() + ":" + password.getValue();
            byte[] authEncBytes = Base64.encodeBase64(authString.getBytes());
            String authStringEnc = new String(authEncBytes);
            con.setRequestProperty("Authorization", "Basic " + authStringEnc);

            // Set the timeout so don't wait forever (unless timeout is set to 0)
            int timeoutMsec = timeout.getValue();
            con.setConnectTimeout(timeoutMsec);
            con.setReadTimeout(timeoutMsec);

            // Write the json data to the connection
            DataOutputStream wr = new DataOutputStream(con.getOutputStream());
            wr.writeBytes(jsonStr);
            wr.flush();
            wr.close();

            // Get the response
            int responseCode = con.getResponseCode();

            // If wasn't successful then log the response so can debug
            if (responseCode != 200) {
                String responseStr = "";
                if (responseCode != 500) {
                    // Response code indicates there was a problem so get the
                    // reply in case API returned useful error message
                    InputStream inputStream = con.getErrorStream();
                    if (inputStream != null) {
                        BufferedReader in = new BufferedReader(new InputStreamReader(inputStream));
                        String inputLine;
                        StringBuffer response = new StringBuffer();
                        while ((inputLine = in.readLine()) != null) {
                            response.append(inputLine);
                        }
                        in.close();
                        responseStr = response.toString();
                    }
                }

                // Lot that response code indicates there was a problem
                logger.error(
                        "Bad HTTP response {} when writing data to SFMTA "
                                + "API. Response text=\"{}\" URL={} json=\n{}",
                        responseCode, responseStr, baseUrl, jsonStr);
            }

            // Done so disconnect just for the heck of it
            con.disconnect();

            // Return whether was successful
            return responseCode == 200;
        } catch (IOException e) {
            logger.error("Exception when writing data to SFMTA API: \"{}\" " + "URL={} json=\n{}", e.getMessage(),
                    baseUrl, jsonStr);

            // Return that was unsuccessful
            return false;
        }

    }

    /**
     * Posts list of telemetry data to SFMTA API
     * 
     * @param avlReports
     */
    private static void postAvlReports(List<AvlReport> avlReports) {
        init();

        // Convert AVL data to a JSON string
        String jsonStr = getTelemetryJson(avlReports);

        // Post data to the API
        if (post(telemetryUrl.getValue(), jsonStr)) {
            logger.info("Successfully pushed out {} telemetry data points to " + "SFMTA API", avlReports.size());
            logger.debug("Successfully pushed telemetry data json=\n{}", jsonStr);
        }
        ;
    }

    /**
     * Posts list of stop data to SFMTA API
     * 
     * @param avlReports
     */
    public static void postStopReports(List<StopInfo> stopInfos) {
        init();

        // Convert AVL data to a JSON string
        String jsonStr = getStopJson(stopInfos);

        // Post data to the API
        if (post(stopsUrl.getValue(), jsonStr)) {
            logger.info("Successfully pushed out stop data to SFMTA API");
            logger.debug("Successfully pushed stop data json=\n{}", jsonStr);
        }
        ;
    }

    /**
     * Posts a single stop report to the SFMTA API.
     * 
     * @param vehicleId
     * @param stopId
     * @param lat
     * @param lon
     * @param arrivalTime
     * @param departureTime
     */
    public static void postStopReport(String vehicleId, int stopId, double lat, double lon, Date arrivalTime,
            Date departureTime) {
        init();

        StopInfo stopInfo = new StopInfo(vehicleId, stopId, lat, lon, arrivalTime, departureTime);

        // Put the StopInfo into a list so can call postStopReports()
        List<StopInfo> stopInfos = new ArrayList<StopInfo>();
        stopInfos.add(stopInfo);

        // Actually post the data
        postStopReports(stopInfos);
    }

    /**
     * To be called when system has a new AVL report. Batches the reports
     * together. If have enough reports (as specified by avlReportsBatchSize)
     * then actually writes them to the API.
     * <p>
     * Synchronized in case multiple threads used when accessing the avlReports
     * cache
     * 
     * @param avlReport
     */
    public synchronized static void postAvlReportWhenAppropriate(AvlReport avlReport) {
        avlReports.add(avlReport);
        if (avlReports.size() >= avlReportsBatchSize.getValue()) {
            postAvlReports(avlReports);
            avlReports.clear();
        }
    }

    /**
     * For debugging.
     * 
     * @param args
     */
    public static void main(String args[]) {
        AvlReport avlReport1 = new AvlReport("693", System.currentTimeMillis(), 37.12345, -122.4567, null);
        AvlReport avlReport2 = new AvlReport("694", System.currentTimeMillis(), 37.12345, -122.4567, null);
        List<AvlReport> avlReports = new ArrayList<AvlReport>();
        avlReports.add(avlReport1);
        avlReports.add(avlReport2);

        postAvlReports(avlReports);

        StopInfo s1 = new StopInfo("693", 3124, 37.12345, -122.4567, new Date(), new Date());
        StopInfo s2 = new StopInfo("694", 4321, 37.12346, -122.4667, new Date(), new Date());

        List<StopInfo> stopInfos = new ArrayList<StopInfo>();
        stopInfos.add(s1);
        stopInfos.add(s2);

        postStopReports(stopInfos);
    }
}