diff --git a/src/it/reyboz/bustorino/backend/FiveTAPIFetcher.java b/src/it/reyboz/bustorino/backend/FiveTAPIFetcher.java index 1f1717d..586df23 100644 --- a/src/it/reyboz/bustorino/backend/FiveTAPIFetcher.java +++ b/src/it/reyboz/bustorino/backend/FiveTAPIFetcher.java @@ -1,422 +1,426 @@ /* BusTO - Backend components Copyright (C) 2018 Fabio Mazza This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program 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 this program. If not, see . */ package it.reyboz.bustorino.backend; import android.support.annotation.Nullable; import android.util.Log; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; import java.net.URL; import java.net.URLEncoder; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.*; import java.util.concurrent.atomic.AtomicReference; public class FiveTAPIFetcher implements ArrivalsFetcher{ private static final String SECRET_KEY="759C97DC7D115966C30FD9169BB200D9"; private static final String DEBUG_NAME = "FiveTAPIFetcher"; final static LinkedList apiDays = new LinkedList<>(Arrays.asList("dom","lun","mar","mer","gio","ven","sab")); @Override public Palina ReadArrivalTimesAll(String stopID, AtomicReference res) { //set the date for the request as now Palina p = new Palina(stopID); //request parameters String response = performAPIRequest(QueryType.ARRIVALS,stopID,res); if(response==null) { if(res.get()==result.SERVER_ERROR_404) { Log.w(DEBUG_NAME,"Got 404, either the server failed, or the stop was not found, or the hack is not working anymore"); res.set(result.EMPTY_RESULT_SET); }; return p; } - - List routes = parseArrivalsServerResponse(response,res); - for(Route r: routes){ - p.addRoute(r); + try { + List routes = parseArrivalsServerResponse(response, res); + for(Route r: routes){ + p.addRoute(r); + } + } catch (JSONException ex){ + res.set(result.PARSER_ERROR); + return null; } res.set(result.OK); p.sortRoutes(); return p; } - List parseArrivalsServerResponse(String JSONresponse, AtomicReference res){ + List parseArrivalsServerResponse(String JSONresponse, AtomicReference res) throws JSONException{ ArrayList routes = new ArrayList<>(3); /* Slight problem: "longName": ==> DESCRIPTION "name": "13N", "departures": [ { "arrivalTimeInt": 1272, "time": "21:12", "rt": false }] "lineType": "URBANO" ==> URBANO can be either bus or tram or METRO */ JSONArray arr; try{ arr = new JSONArray(JSONresponse); String type; Route.Type routetype; for(int i =0; i parseDirectionsFromResponse(String response) throws IllegalArgumentException,JSONException{ if(response == null || response.length()==0) throw new IllegalArgumentException("Response string is null or void"); ArrayList routes = new ArrayList<>(10); JSONArray lines =new JSONArray(response); for(int i=0; i 1) { String secondo = exploded[exploded.length-2]; if (secondo.contains("festivo")) { festivo = Route.FestiveInfo.FESTIVO; } else if (secondo.contains("feriale")) { festivo = Route.FestiveInfo.FERIALE; } else if(secondo.contains("lun. - ven")) { serviceDays = Route.reduced_week; } else if(secondo.contains("sab - fest.")){ serviceDays = Route.weekend; festivo = Route.FestiveInfo.FESTIVO; } else { /* Log.d(DEBUG_NAME,"Parsing details of line "+lineName+" branchid "+branchid+":\n\t"+ "Couldn't find a the service days\n"+ "Description: "+secondo+","+description ); */ } if(exploded.length>2){ switch (exploded[exploded.length-3].trim()) { case "bus": t = Route.Type.BUS; break; case "tram": //never happened, but if it could happen you can get it t = Route.Type.TRAM; break; default: //nothing } } } else //only one piece if(description.contains("festivo")){ festivo = Route.FestiveInfo.FESTIVO; } else if(description.contains("feriale")){ festivo = Route.FestiveInfo.FERIALE; } if(t == null &&(lineName.trim().equals("10")|| lineName.trim().equals("15"))) t= Route.Type.TRAM; if(direction.contains("-")){ //Sometimes the actual filtered direction still remains the full line (including both extremes) direction = direction.split("-")[1]; } Route r = new Route(lineName.trim(),direction.trim(),t,new ArrayList<>()); if(serviceDays.length>0) r.serviceDays = serviceDays; r.festivo = festivo; r.branchid = branchid; r.description = description.trim(); r.setStopsList(Arrays.asList(stops.split(","))); routes.add(r); } return routes; } public List getDirectionsForStop(String stopID, AtomicReference res) { String response = performAPIRequest(QueryType.DETAILS,stopID,res); List routes; try{ routes = parseDirectionsFromResponse(response); res.set(result.OK); } catch (JSONException | IllegalArgumentException e) { e.printStackTrace(); res.set(result.PARSER_ERROR); routes = null; } return routes; } public ArrayList getAllStopsFromGTT(AtomicReference res){ String response = performAPIRequest(QueryType.STOPS_ALL,null,res); if(response==null) return null; ArrayList stopslist = null; try{ JSONObject responseJSON = new JSONObject(response); JSONArray stops = responseJSON.getJSONArray("stops"); stopslist = new ArrayList<>(stops.length()); for (int i=0;i getAllLinesFromGTT(AtomicReference res){ String resp = performAPIRequest(QueryType.LINES,null,res); if(resp==null) { return null; } ArrayList routes = null; try { JSONArray lines = new JSONArray(resp); routes = new ArrayList<>(lines.length()); for(int i = 0; i getHeadersForRequest(String url) throws UnsupportedEncodingException, NoSuchAlgorithmException { final Date d = new Date(); HashMap param = new HashMap<>(); param.put("TOKEN",getAccessToken(url,d)); param.put("TIMESTAMP",String.valueOf(d.getTime())); param.put("Accept-Encoding","gzip"); param.put("Connection","Keep-Alive"); return param; } /** * Create and perform the network request. This method adds parameters and returns the result * @param t type of request to be performed * @param stopID optional parameter, stop ID which you need for passages and branches * @param res result container * @return a String which contains the result of the query, to be parsed */ @Nullable public static String performAPIRequest(QueryType t,@Nullable String stopID, AtomicReference res){ URL u; Map param; try { String address = getURLForOperation(t,stopID); //Log.d(DEBUG_NAME,"The address to query is: "+address); param = getHeadersForRequest(address); u = new URL(address); } catch (UnsupportedEncodingException | NoSuchAlgorithmException |MalformedURLException e) { e.printStackTrace(); res.set(result.PARSER_ERROR); return null; } String response = networkTools.queryURL(u,res,param); return response; } /** * Get the Token needed to access the API * @param URL the URL of the request * @return token * @throws NoSuchAlgorithmException if the system doesn't support MD5 * @throws UnsupportedEncodingException if we made mistakes in writing utf-8 */ private static String getAccessToken(String URL,Date d) throws NoSuchAlgorithmException,UnsupportedEncodingException{ MessageDigest md = MessageDigest.getInstance("MD5"); String strippedQuery = URL.replace("http://www.5t.torino.it/proxyws",""); //return the time in milliseconds long timeMilli = d.getTime(); StringBuilder sb = new StringBuilder(); sb.append(strippedQuery); sb.append(timeMilli); sb.append(SECRET_KEY); String stringToBeHashed = sb.toString(); //Log.d(DEBUG_NAME,"Hashing string: "+stringToBeHashed); md.reset(); byte[] data = md.digest(stringToBeHashed.getBytes("UTF-8")); sb = new StringBuilder(); for (byte b : data){ sb.append(String.format("%02x",b)); } String result = sb.toString(); //Log.d(DEBUG_NAME,"getting token:\n\treduced URL: "+strippedQuery+"\n\ttimestamp: "+timeMilli+"\nTOKEN:"+result.toLowerCase()); return result.toLowerCase(); } /** * Get the right url for the operation you are doing, to be fed into the queryURL method * @param t type of operation * @param stopID stop on which you are working on * @return the Url to go to * @throws UnsupportedEncodingException if it cannot be converted to utf-8 */ public static String getURLForOperation(QueryType t,@Nullable String stopID) throws UnsupportedEncodingException { final StringBuilder sb = new StringBuilder(); sb.append("http://www.5t.torino.it/proxyws/ws2.1/rest/"); if(t!=QueryType.LINES) sb.append("stops/"); switch (t){ case ARRIVALS: sb.append(URLEncoder.encode(stopID,"utf-8")); sb.append("/departures"); break; case DETAILS: sb.append(URLEncoder.encode(stopID,"utf-8")); sb.append("/branches/details"); break; case STOPS_ALL: sb.append("all"); break; case STOPS_VERSION: sb.append("version"); break; case LINES: sb.append("lines/all"); break; } return sb.toString(); } public enum QueryType { ARRIVALS, DETAILS,STOPS_ALL, STOPS_VERSION,LINES } } diff --git a/src/it/reyboz/bustorino/backend/FiveTAPIVolleyRequest.java b/src/it/reyboz/bustorino/backend/FiveTAPIVolleyRequest.java index 186e7f1..4377bc3 100644 --- a/src/it/reyboz/bustorino/backend/FiveTAPIVolleyRequest.java +++ b/src/it/reyboz/bustorino/backend/FiveTAPIVolleyRequest.java @@ -1,136 +1,137 @@ /* BusTO - Backend components Copyright (C) 2019 Fabio Mazza This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program 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 this program. If not, see . */ package it.reyboz.bustorino.backend; import android.support.annotation.Nullable; import android.util.Log; import com.android.volley.*; import com.android.volley.toolbox.HttpHeaderParser; import org.json.JSONException; import java.io.UnsupportedEncodingException; import java.security.NoSuchAlgorithmException; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; /** * Class to handle request with the Volley Library */ public class FiveTAPIVolleyRequest extends Request { private static final String LOG_TAG = "BusTO-FiveTAPIVolleyReq"; private ResponseListener listener; final private String url,stopID; final private AtomicReference resultRef; final private FiveTAPIFetcher fetcher; final private FiveTAPIFetcher.QueryType type; private FiveTAPIVolleyRequest(int method, String url, String stopID, FiveTAPIFetcher.QueryType kind, ResponseListener listener, @Nullable Response.ErrorListener errorListener) { super(method, url, errorListener); this.url = url; this.resultRef = new AtomicReference<>(); this.fetcher = new FiveTAPIFetcher(); this.listener = listener; this.stopID = stopID; this.type = kind; } @Nullable public static FiveTAPIVolleyRequest getNewRequest(FiveTAPIFetcher.QueryType type, String stopID, ResponseListener listener, @Nullable Response.ErrorListener errorListener){ String url; try { url = FiveTAPIFetcher.getURLForOperation(type,stopID); } catch (UnsupportedEncodingException e) { e.printStackTrace(); Log.e(LOG_TAG,"Cannot get an URL for the operation"); return null; } return new FiveTAPIVolleyRequest(Method.POST,url,stopID,type,listener,errorListener); } @Override protected Response parseNetworkResponse(NetworkResponse response) { if(response.statusCode != 200) return Response.error(new VolleyError("Response Error Code "+response.statusCode)); final String stringResponse = new String(response.data); List routeList; - switch (type){ - case ARRIVALS: - routeList = fetcher.parseArrivalsServerResponse(stringResponse,resultRef); - break; - case DETAILS: - try { + try{ + switch (type){ + case ARRIVALS: + routeList = fetcher.parseArrivalsServerResponse(stringResponse,resultRef); + break; + case DETAILS: routeList = fetcher.parseDirectionsFromResponse(stringResponse); - } catch (JSONException e) { - resultRef.set(Fetcher.result.PARSER_ERROR); - e.printStackTrace(); - return Response.error(new ParseError()); - } - break; - default: - //empty - return Response.error(new VolleyError("Invalid query type")); + break; + default: + //empty + return Response.error(new VolleyError("Invalid query type")); + } + } catch (JSONException e) { + resultRef.set(Fetcher.result.PARSER_ERROR); + //e.printStackTrace(); + Log.w("FivetVolleyParser","JSON Exception in parsing response of: "+url); + return Response.error(new ParseError(response)); } if(resultRef.get()== Fetcher.result.PARSER_ERROR){ - return Response.error(new ParseError()); + return Response.error(new ParseError(response)); } final Palina p = new Palina(stopID); p.setRoutes(routeList); p.sortRoutes(); return Response.success(p, HttpHeaderParser.parseCacheHeaders(response)); } @Override protected void deliverResponse(Palina p) { listener.onResponse(p,type); } @Override public Map getHeaders() throws AuthFailureError { Map headers; try{ headers = FiveTAPIFetcher.getHeadersForRequest(url); } catch (UnsupportedEncodingException | NoSuchAlgorithmException e) { e.printStackTrace(); throw new AuthFailureError("Cannot get the token for the authorization"); } return headers; } //from https://stackoverflow.com/questions/21867929/android-how-handle-message-error-from-the-server-using-volley @Override protected VolleyError parseNetworkError(VolleyError volleyError){ if(volleyError.networkResponse != null && volleyError.networkResponse.data != null){ volleyError = new NetworkError(volleyError.networkResponse); } return volleyError; } public interface ResponseListener{ void onResponse(Palina result, FiveTAPIFetcher.QueryType type); } //public interface ErrorListener extends Response.ErrorListener{} }