package org.opentripplanner.updater.bike_rental; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.prefs.Preferences; import org.opentripplanner.updater.JsonConfigurable; import org.opentripplanner.routing.bike_rental.BikeRentalStation; import org.opentripplanner.routing.graph.Graph; import org.opentripplanner.util.HttpUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; /** * Fetch Bike Rental JSON feeds and pass each record on to the specific rental subclass * * @see BikeRentalDataSource */ public abstract class GenericJsonBikeRentalDataSource implements BikeRentalDataSource, JsonConfigurable { private static final Logger log = LoggerFactory.getLogger(GenericJsonBikeRentalDataSource.class); private String url; private String apiKey; private String jsonParsePath; ArrayList<BikeRentalStation> stations = new ArrayList<BikeRentalStation>(); /** * Construct superclass * * @param JSON path to get from enclosing elements to nested rental list. * Separate path levels with '/' For example "d/list" * */ public GenericJsonBikeRentalDataSource(String jsonPath) { jsonParsePath = jsonPath; apiKey= null; } /** * Construct superclass * * @param JSON path to get from enclosing elements to nested rental list. * Separate path levels with '/' For example "d/list" * @param Api key, when used by bike rental type * */ public GenericJsonBikeRentalDataSource(String jsonPath, String apiKeyValue) { jsonParsePath = jsonPath; apiKey = apiKeyValue; } /** * Construct superclass where rental list is on the top level of JSON code * */ public GenericJsonBikeRentalDataSource() { jsonParsePath = ""; } @Override public boolean update() { try { InputStream data = null; URL url2 = new URL(url); String proto = url2.getProtocol(); if (proto.equals("http") || proto.equals("https")) { data = HttpUtils.getData(url, "ApiKey", apiKey); } else { // Local file probably, try standard java data = url2.openStream(); } if (data == null) { log.warn("Failed to get data from url " + url); return false; } parseJSON(data); data.close(); } catch (IllegalArgumentException e) { log.warn("Error parsing bike rental feed from " + url, e); return false; } catch (JsonProcessingException e) { log.warn("Error parsing bike rental feed from " + url + "(bad JSON of some sort)", e); return false; } catch (IOException e) { log.warn("Error reading bike rental feed from " + url, e); return false; } return true; } private void parseJSON(InputStream dataStream) throws JsonProcessingException, IllegalArgumentException, IOException { ArrayList<BikeRentalStation> out = new ArrayList<BikeRentalStation>(); String rentalString = convertStreamToString(dataStream); ObjectMapper mapper = new ObjectMapper(); JsonNode rootNode = mapper.readTree(rentalString); if (!jsonParsePath.equals("")) { String delimiter = "/"; String[] parseElement = jsonParsePath.split(delimiter); for(int i =0; i < parseElement.length ; i++) { rootNode = rootNode.path(parseElement[i]); } if (rootNode.isMissingNode()) { throw new IllegalArgumentException("Could not find jSON elements " + jsonParsePath); } } for (int i = 0; i < rootNode.size(); i++) { JsonNode node = rootNode.get(i); if (node == null) { continue; } BikeRentalStation brstation = makeStation(node); if (brstation != null) out.add(brstation); } synchronized(this) { stations = out; } } private String convertStreamToString(java.io.InputStream is) { java.util.Scanner scanner = null; String result=""; try { scanner = new java.util.Scanner(is).useDelimiter("\\A"); result = scanner.hasNext() ? scanner.next() : ""; scanner.close(); } finally { if(scanner!=null) scanner.close(); } return result; } @Override public synchronized List<BikeRentalStation> getStations() { return stations; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public abstract BikeRentalStation makeStation(JsonNode rentalStationNode); @Override public String toString() { return getClass().getName() + "(" + url + ")"; } /** * Note that the JSON being passed in here is for configuration of the OTP component, it's completely separate * from the JSON coming in from the update source. */ @Override public void configure (Graph graph, JsonNode jsonNode) { String url = jsonNode.path("url").asText(); // path() returns MissingNode not null. if (url == null) { throw new IllegalArgumentException("Missing mandatory 'url' configuration."); } this.url = url; } }