diff --git a/enabler/src/de/schildbach/pte/AbstractEfaProvider.java b/enabler/src/de/schildbach/pte/AbstractEfaProvider.java index 98c7315b..e57043d5 100644 --- a/enabler/src/de/schildbach/pte/AbstractEfaProvider.java +++ b/enabler/src/de/schildbach/pte/AbstractEfaProvider.java @@ -22,7 +22,6 @@ import static com.google.common.base.Preconditions.checkState; import java.io.IOException; import java.io.InputStream; -import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; @@ -52,7 +51,6 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserFactory; -import com.google.common.base.Charsets; import com.google.common.base.MoreObjects; import com.google.common.base.Strings; @@ -97,17 +95,15 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider { protected static final String SERVER_PRODUCT = "efa"; - private final String departureMonitorEndpoint; - private final String tripEndpoint; - private final String stopFinderEndpoint; - private final String coordEndpoint; + private final HttpUrl departureMonitorEndpoint; + private final HttpUrl tripEndpoint; + private final HttpUrl stopFinderEndpoint; + private final HttpUrl coordEndpoint; private String language = "de"; - private @Nullable String additionalQueryParameter = null; private boolean needsSpEncId = false; private boolean includeRegionId = true; private boolean useProxFootSearch = true; - private Charset requestUrlEncoding = Charsets.ISO_8859_1; private @Nullable String httpReferer = null; private @Nullable String httpRefererTrip = null; private boolean httpPost = false; @@ -144,22 +140,28 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider { } } - public AbstractEfaProvider(final NetworkId network, final String apiBase) { + public AbstractEfaProvider(final NetworkId network, final HttpUrl apiBase) { this(network, apiBase, null, null, null, null); } - public AbstractEfaProvider(final NetworkId network, final String apiBase, final String departureMonitorEndpoint, + public AbstractEfaProvider(final NetworkId network, final HttpUrl apiBase, final String departureMonitorEndpoint, final String tripEndpoint, final String stopFinderEndpoint, final String coordEndpoint) { this(network, - apiBase + (departureMonitorEndpoint != null ? departureMonitorEndpoint - : DEFAULT_DEPARTURE_MONITOR_ENDPOINT), // - apiBase + (tripEndpoint != null ? tripEndpoint : DEFAULT_TRIP_ENDPOINT), // - apiBase + (stopFinderEndpoint != null ? stopFinderEndpoint : DEFAULT_STOPFINDER_ENDPOINT), // - apiBase + (coordEndpoint != null ? coordEndpoint : DEFAULT_COORD_ENDPOINT)); + apiBase.newBuilder() + .addPathSegment(departureMonitorEndpoint != null ? departureMonitorEndpoint + : DEFAULT_DEPARTURE_MONITOR_ENDPOINT) + .build(), + apiBase.newBuilder().addPathSegment(tripEndpoint != null ? tripEndpoint : DEFAULT_TRIP_ENDPOINT) + .build(), + apiBase.newBuilder() + .addPathSegment(stopFinderEndpoint != null ? stopFinderEndpoint : DEFAULT_STOPFINDER_ENDPOINT) + .build(), + apiBase.newBuilder().addPathSegment(coordEndpoint != null ? coordEndpoint : DEFAULT_COORD_ENDPOINT) + .build()); } - public AbstractEfaProvider(final NetworkId network, final String departureMonitorEndpoint, - final String tripEndpoint, final String stopFinderEndpoint, final String coordEndpoint) { + public AbstractEfaProvider(final NetworkId network, final HttpUrl departureMonitorEndpoint, + final HttpUrl tripEndpoint, final HttpUrl stopFinderEndpoint, final HttpUrl coordEndpoint) { super(network); try { @@ -180,16 +182,6 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider { return this; } - protected AbstractEfaProvider setAdditionalQueryParameter(final String additionalQueryParameter) { - this.additionalQueryParameter = additionalQueryParameter; - return this; - } - - protected AbstractEfaProvider setRequestUrlEncoding(final Charset requestUrlEncoding) { - this.requestUrlEncoding = requestUrlEncoding; - return this; - } - protected AbstractEfaProvider setHttpReferer(final String httpReferer) { this.httpReferer = httpReferer; this.httpRefererTrip = httpReferer; @@ -246,24 +238,21 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider { return true; } - private final void appendCommonRequestParams(final StringBuilder uri, final String outputFormat) { - uri.append("?outputFormat=").append(outputFormat); - uri.append("&language=").append(language); - uri.append("&stateless=1"); - uri.append("&coordOutputFormat=WGS84"); - if (additionalQueryParameter != null) - uri.append('&').append(additionalQueryParameter); + private final void appendCommonRequestParams(final HttpUrl.Builder url, final String outputFormat) { + url.addEncodedQueryParameter("outputFormat", outputFormat); + url.addEncodedQueryParameter("language", language); + url.addEncodedQueryParameter("stateless", "1"); + url.addEncodedQueryParameter("coordOutputFormat", "WGS84"); } protected SuggestLocationsResult jsonStopfinderRequest(final Location constraint) throws IOException { - final StringBuilder uri = new StringBuilder(stopFinderEndpoint); - final StringBuilder parameters = stopfinderRequestParameters(constraint, "JSON"); + final HttpUrl.Builder url = stopFinderEndpoint.newBuilder(); + appendStopfinderRequestParameters(url, constraint, "JSON"); final CharSequence page; if (httpPost) - page = httpClient.get(HttpUrl.parse(uri.toString()), parameters.substring(1), - "application/x-www-form-urlencoded"); + page = httpClient.get(url.build(), url.build().encodedQuery(), "application/x-www-form-urlencoded"); else - page = httpClient.get(HttpUrl.parse(uri.append(parameters).toString())); + page = httpClient.get(url.build()); final ResultHeader header = new ResultHeader(network, SERVER_PRODUCT); try { @@ -310,7 +299,7 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider { return new SuggestLocationsResult(header, locations); } catch (final JSONException x) { - throw new RuntimeException("cannot parse: '" + page + "' on " + uri, x); + throw new RuntimeException("cannot parse: '" + page + "' on " + url, x); } } @@ -347,29 +336,28 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider { return new SuggestedLocation(location, quality); } - private StringBuilder stopfinderRequestParameters(final Location constraint, final String outputFormat) { - final StringBuilder parameters = new StringBuilder(); - appendCommonRequestParams(parameters, outputFormat); - parameters.append("&locationServerActive=1"); + private void appendStopfinderRequestParameters(final HttpUrl.Builder url, final Location constraint, + final String outputFormat) { + appendCommonRequestParams(url, outputFormat); + url.addEncodedQueryParameter("locationServerActive", "1"); if (includeRegionId) - parameters.append("®ionID_sf=1"); // prefer own region - appendLocation(parameters, constraint, "sf"); + url.addEncodedQueryParameter("regionID_sf", "1"); // prefer own region + appendLocationParams(url, constraint, "sf"); if (constraint.type == LocationType.ANY) { if (needsSpEncId) - parameters.append("&SpEncId=0"); + url.addEncodedQueryParameter("SpEncId", "0"); // 1=place 2=stop 4=street 8=address 16=crossing 32=poi 64=postcode - parameters.append("&anyObjFilter_sf=").append(2 + 4 + 8 + 16 + 32 + 64); - parameters.append("&reducedAnyPostcodeObjFilter_sf=64&reducedAnyTooManyObjFilter_sf=2"); - parameters.append("&useHouseNumberList=true"); - parameters.append("&anyMaxSizeHitList=500"); + url.addEncodedQueryParameter("anyObjFilter_sf", Integer.toString(2 + 4 + 8 + 16 + 32 + 64)); + url.addEncodedQueryParameter("reducedAnyPostcodeObjFilter_sf", "64"); + url.addEncodedQueryParameter("reducedAnyTooManyObjFilter_sf", "2"); + url.addEncodedQueryParameter("useHouseNumberList", "true"); + url.addEncodedQueryParameter("anyMaxSizeHitList", "500"); } - - return parameters; } protected SuggestLocationsResult xmlStopfinderRequest(final Location constraint) throws IOException { - final StringBuilder uri = new StringBuilder(stopFinderEndpoint); - final StringBuilder parameters = stopfinderRequestParameters(constraint, "XML"); + final HttpUrl.Builder url = stopFinderEndpoint.newBuilder(); + appendStopfinderRequestParameters(url, constraint, "XML"); final AtomicReference result = new AtomicReference(); final HttpClient.Callback callback = new HttpClient.Callback() { @@ -401,17 +389,17 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider { }; if (httpPost) - httpClient.getInputStream(callback, HttpUrl.parse(uri.toString()), parameters.substring(1), + httpClient.getInputStream(callback, url.build(), url.build().encodedQuery(), "application/x-www-form-urlencoded", httpReferer); else - httpClient.getInputStream(callback, HttpUrl.parse(uri.append(parameters).toString()), httpReferer); + httpClient.getInputStream(callback, url.build(), httpReferer); return result.get(); } protected SuggestLocationsResult mobileStopfinderRequest(final Location constraint) throws IOException { - final StringBuilder uri = new StringBuilder(stopFinderEndpoint); - final StringBuilder parameters = stopfinderRequestParameters(constraint, "XML"); + final HttpUrl.Builder url = stopFinderEndpoint.newBuilder(); + appendStopfinderRequestParameters(url, constraint, "XML"); final AtomicReference result = new AtomicReference(); final HttpClient.Callback callback = new HttpClient.Callback() { @@ -486,45 +474,42 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider { }; if (httpPost) - httpClient.getInputStream(callback, HttpUrl.parse(uri.toString()), parameters.substring(1), + httpClient.getInputStream(callback, url.build(), url.build().encodedQuery(), "application/x-www-form-urlencoded", httpReferer); else - httpClient.getInputStream(callback, HttpUrl.parse(uri.append(parameters).toString()), httpReferer); + httpClient.getInputStream(callback, url.build(), httpReferer); return result.get(); } - private StringBuilder xmlCoordRequestParameters(final EnumSet types, final int lat, final int lon, - final int maxDistance, final int maxLocations) { - final StringBuilder parameters = new StringBuilder(); - appendCommonRequestParams(parameters, "XML"); - parameters.append("&coord=") - .append(String.format(Locale.ENGLISH, "%2.6f:%2.6f:WGS84", latLonToDouble(lon), latLonToDouble(lat))); + private void appendXmlCoordRequestParameters(final HttpUrl.Builder url, final EnumSet types, + final int lat, final int lon, final int maxDistance, final int maxLocations) { + appendCommonRequestParams(url, "XML"); + url.addEncodedQueryParameter("coord", + ParserUtils.urlEncode( + String.format(Locale.ENGLISH, "%2.6f:%2.6f:WGS84", latLonToDouble(lon), latLonToDouble(lat)), + requestUrlEncoding)); if (useStringCoordListOutputFormat) - parameters.append("&coordListOutputFormat=STRING"); - parameters.append("&max=").append(maxLocations != 0 ? maxLocations : 50); - parameters.append("&inclFilter=1"); + url.addEncodedQueryParameter("coordListOutputFormat", "STRING"); + url.addEncodedQueryParameter("max", Integer.toString(maxLocations != 0 ? maxLocations : 50)); + url.addEncodedQueryParameter("inclFilter", "1"); int i = 1; for (final LocationType type : types) { - parameters.append("&radius_").append(i).append('=').append(maxDistance != 0 ? maxDistance : 1320); - parameters.append("&type_").append(i).append('='); + url.addEncodedQueryParameter("radius_" + i, Integer.toString(maxDistance != 0 ? maxDistance : 1320)); if (type == LocationType.STATION) - parameters.append("STOP"); + url.addEncodedQueryParameter("type_" + i, "STOP"); else if (type == LocationType.POI) - parameters.append("POI_POINT"); + url.addEncodedQueryParameter("type_" + i, "POI_POINT"); else - throw new IllegalArgumentException("cannot handle location type: " + type); // ENTRANCE, - // BUS_POINT + throw new IllegalArgumentException("cannot handle location type: " + type); i++; } - - return parameters; } protected NearbyLocationsResult xmlCoordRequest(final EnumSet types, final int lat, final int lon, final int maxDistance, final int maxStations) throws IOException { - final StringBuilder uri = new StringBuilder(coordEndpoint); - final StringBuilder parameters = xmlCoordRequestParameters(types, lat, lon, maxDistance, maxStations); + final HttpUrl.Builder url = coordEndpoint.newBuilder(); + appendXmlCoordRequestParameters(url, types, lat, lon, maxDistance, maxStations); final AtomicReference result = new AtomicReference(); final HttpClient.Callback callback = new HttpClient.Callback() { @@ -587,18 +572,18 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider { }; if (httpPost) - httpClient.getInputStream(callback, HttpUrl.parse(uri.toString()), parameters.substring(1), + httpClient.getInputStream(callback, url.build(), url.build().encodedQuery(), "application/x-www-form-urlencoded", httpReferer); else - httpClient.getInputStream(callback, HttpUrl.parse(uri.append(parameters).toString()), httpReferer); + httpClient.getInputStream(callback, url.build(), httpReferer); return result.get(); } protected NearbyLocationsResult mobileCoordRequest(final EnumSet types, final int lat, final int lon, final int maxDistance, final int maxStations) throws IOException { - final StringBuilder uri = new StringBuilder(coordEndpoint); - final StringBuilder parameters = xmlCoordRequestParameters(types, lat, lon, maxDistance, maxStations); + final HttpUrl.Builder url = coordEndpoint.newBuilder(); + appendXmlCoordRequestParameters(url, types, lat, lon, maxDistance, maxStations); final AtomicReference result = new AtomicReference(); final HttpClient.Callback callback = new HttpClient.Callback() { @@ -665,10 +650,10 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider { }; if (httpPost) - httpClient.getInputStream(callback, HttpUrl.parse(uri.toString()), parameters.substring(1), + httpClient.getInputStream(callback, url.build(), url.build().encodedQuery(), "application/x-www-form-urlencoded", httpReferer); else - httpClient.getInputStream(callback, HttpUrl.parse(uri.append(parameters).toString()), httpReferer); + httpClient.getInputStream(callback, url.build(), httpReferer); return result.get(); } @@ -864,17 +849,18 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider { private NearbyLocationsResult nearbyStationsRequest(final String stationId, final int maxLocations) throws IOException { - final StringBuilder uri = new StringBuilder(departureMonitorEndpoint); - final StringBuilder parameters = new StringBuilder(); - appendCommonRequestParams(parameters, "XML"); - parameters.append("&type_dm=stop&name_dm=").append(normalizeStationId(stationId)); - parameters.append("&itOptionsActive=1"); - parameters.append("&ptOptionsActive=1"); + final HttpUrl.Builder url = departureMonitorEndpoint.newBuilder(); + appendCommonRequestParams(url, "XML"); + url.addEncodedQueryParameter("type_dm", "stop"); + url.addEncodedQueryParameter("name_dm", + ParserUtils.urlEncode(normalizeStationId(stationId), requestUrlEncoding)); + url.addEncodedQueryParameter("itOptionsActive", "1"); + url.addEncodedQueryParameter("ptOptionsActive", "1"); if (useProxFootSearch) - parameters.append("&useProxFootSearch=1"); - parameters.append("&mergeDep=1"); - parameters.append("&useAllStops=1"); - parameters.append("&mode=direct"); + url.addEncodedQueryParameter("useProxFootSearch", "1"); + url.addEncodedQueryParameter("mergeDep", "1"); + url.addEncodedQueryParameter("useAllStops", "1"); + url.addEncodedQueryParameter("mode", "direct"); final AtomicReference result = new AtomicReference(); final HttpClient.Callback callback = new HttpClient.Callback() { @@ -923,10 +909,10 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider { }; if (httpPost) - httpClient.getInputStream(callback, HttpUrl.parse(uri.toString()), parameters.substring(1), + httpClient.getInputStream(callback, url.build(), url.build().encodedQuery(), "application/x-www-form-urlencoded", httpReferer); else - httpClient.getInputStream(callback, HttpUrl.parse(uri.append(parameters).toString()), httpReferer); + httpClient.getInputStream(callback, url.build(), httpReferer); return result.get(); } @@ -1433,28 +1419,26 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider { return xsltDepartureMonitorRequest(stationId, time, maxDepartures, equivs); } - protected StringBuilder xsltDepartureMonitorRequestParameters(final String stationId, final @Nullable Date time, - final int maxDepartures, final boolean equivs) { - final StringBuilder parameters = new StringBuilder(); - appendCommonRequestParams(parameters, "XML"); - parameters.append("&type_dm=stop"); - parameters.append("&name_dm=").append(normalizeStationId(stationId)); + protected void appendXsltDepartureMonitorRequestParameters(final HttpUrl.Builder url, final String stationId, + final @Nullable Date time, final int maxDepartures, final boolean equivs) { + appendCommonRequestParams(url, "XML"); + url.addEncodedQueryParameter("type_dm", "stop"); + url.addEncodedQueryParameter("name_dm", + ParserUtils.urlEncode(normalizeStationId(stationId), requestUrlEncoding)); if (time != null) - appendItdDateTimeParameters(parameters, time); - parameters.append("&useRealtime=1"); - parameters.append("&mode=direct"); - parameters.append("&ptOptionsActive=1"); - parameters.append("&deleteAssignedStops_dm=").append(equivs ? '0' : '1'); + appendItdDateTimeParameters(url, time); + url.addEncodedQueryParameter("useRealtime", "1"); + url.addEncodedQueryParameter("mode", "direct"); + url.addEncodedQueryParameter("ptOptionsActive", "1"); + url.addEncodedQueryParameter("deleteAssignedStops_dm", equivs ? "0" : "1"); if (useProxFootSearch) - parameters.append("&useProxFootSearch=").append(equivs ? '1' : '0'); - parameters.append("&mergeDep=1"); // merge departures + url.addEncodedQueryParameter("useProxFootSearch", equivs ? "1" : "0"); + url.addEncodedQueryParameter("mergeDep", "1"); // merge departures if (maxDepartures > 0) - parameters.append("&limit=").append(maxDepartures); - - return parameters; + url.addEncodedQueryParameter("limit", Integer.toString(maxDepartures)); } - private final void appendItdDateTimeParameters(final StringBuilder uri, final Date time) { + private final void appendItdDateTimeParameters(final HttpUrl.Builder url, final Date time) { final Calendar c = new GregorianCalendar(timeZone); c.setTime(time); final int year = c.get(Calendar.YEAR); @@ -1462,14 +1446,14 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider { final int day = c.get(Calendar.DAY_OF_MONTH); final int hour = c.get(Calendar.HOUR_OF_DAY); final int minute = c.get(Calendar.MINUTE); - uri.append("&itdDate=").append(String.format(Locale.ENGLISH, "%04d%02d%02d", year, month, day)); - uri.append("&itdTime=").append(String.format(Locale.ENGLISH, "%02d%02d", hour, minute)); + url.addEncodedQueryParameter("itdDate", String.format(Locale.ENGLISH, "%04d%02d%02d", year, month, day)); + url.addEncodedQueryParameter("itdTime", String.format(Locale.ENGLISH, "%02d%02d", hour, minute)); } private QueryDeparturesResult xsltDepartureMonitorRequest(final String stationId, final @Nullable Date time, final int maxDepartures, final boolean equivs) throws IOException { - final StringBuilder uri = new StringBuilder(departureMonitorEndpoint); - final StringBuilder parameters = xsltDepartureMonitorRequestParameters(stationId, time, maxDepartures, equivs); + final HttpUrl.Builder url = departureMonitorEndpoint.newBuilder(); + appendXsltDepartureMonitorRequestParameters(url, stationId, time, maxDepartures, equivs); final AtomicReference result = new AtomicReference(); final HttpClient.Callback callback = new HttpClient.Callback() { @@ -1632,18 +1616,18 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider { }; if (httpPost) - httpClient.getInputStream(callback, HttpUrl.parse(uri.toString()), parameters.substring(1), + httpClient.getInputStream(callback, url.build(), url.build().encodedQuery(), "application/x-www-form-urlencoded", httpReferer); else - httpClient.getInputStream(callback, HttpUrl.parse(uri.append(parameters).toString()), httpReferer); + httpClient.getInputStream(callback, url.build(), httpReferer); return result.get(); } protected QueryDeparturesResult queryDeparturesMobile(final String stationId, final @Nullable Date time, final int maxDepartures, final boolean equivs) throws IOException { - final StringBuilder uri = new StringBuilder(departureMonitorEndpoint); - final StringBuilder parameters = xsltDepartureMonitorRequestParameters(stationId, time, maxDepartures, equivs); + final HttpUrl.Builder url = departureMonitorEndpoint.newBuilder(); + appendXsltDepartureMonitorRequestParameters(url, stationId, time, maxDepartures, equivs); final AtomicReference result = new AtomicReference(); final HttpClient.Callback callback = new HttpClient.Callback() { @@ -1716,10 +1700,10 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider { }; if (httpPost) - httpClient.getInputStream(callback, HttpUrl.parse(uri.toString()), parameters.substring(1), + httpClient.getInputStream(callback, url.build(), url.build().encodedQuery(), "application/x-www-form-urlencoded", httpReferer); else - httpClient.getInputStream(callback, HttpUrl.parse(uri.append(parameters).toString()), httpReferer); + httpClient.getInputStream(callback, url.build(), httpReferer); return result.get(); } @@ -1956,115 +1940,114 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider { return (double) value / 1000000; } - protected String xsltTripRequestParameters(final Location from, final @Nullable Location via, final Location to, - final Date time, final boolean dep, final @Nullable Collection products, - final @Nullable Optimize optimize, final @Nullable WalkSpeed walkSpeed, - final @Nullable Accessibility accessibility, final @Nullable Set