Skip to content

Commit

Permalink
Merge pull request #8 from traveltime-dev/time-filter-support
Browse files Browse the repository at this point in the history
Time filter support
  • Loading branch information
mjanuszkiewicz-tt authored Dec 20, 2022
2 parents b686305 + 2e8f53e commit f0cf9be
Show file tree
Hide file tree
Showing 25 changed files with 664 additions and 122 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.traveltime.plugin.solr;

import com.traveltime.plugin.solr.cache.RequestCache;
import com.traveltime.plugin.solr.fetcher.JsonFetcherSingleton;
import com.traveltime.plugin.solr.query.timefilter.TimeFilterQueryParser;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.search.QParser;
import org.apache.solr.search.QParserPlugin;

import java.net.URI;
import java.util.Optional;

public class TimeFilterQParserPlugin extends QParserPlugin {
private String cacheName = RequestCache.NAME;

private static final Integer DEFAULT_LOCATION_SIZE_LIMIT = 2000;

@Override
public void init(NamedList args) {
Object cache = args.get("cache");
if(cache != null) cacheName = cache.toString();

Object uriVal = args.get("api_uri");
URI uri = null;
if(uriVal != null) uri = URI.create(uriVal.toString());

String appId = args.get("app_id").toString();
String apiKey = args.get("api_key").toString();
int locationLimit =
Optional.ofNullable(args.get("location_limit"))
.map(x -> Integer.parseInt(x.toString()))
.orElse(DEFAULT_LOCATION_SIZE_LIMIT);

JsonFetcherSingleton.INSTANCE.init(uri, appId, apiKey, locationLimit);
}

@Override
public QParser createParser(String qstr, SolrParams localParams, SolrParams params, SolrQueryRequest req) {
return new TimeFilterQueryParser(qstr, localParams, params, req, JsonFetcherSingleton.INSTANCE.getFetcher(), cacheName);
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.traveltime.plugin.solr;

import com.traveltime.plugin.solr.cache.RequestCache;
import com.traveltime.plugin.solr.fetcher.ProtoFetcherSingleton;
import com.traveltime.plugin.solr.query.TraveltimeQueryParser;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.NamedList;
Expand All @@ -11,7 +12,6 @@
import java.net.URI;

public class TraveltimeQParserPlugin extends QParserPlugin {
public static String PARAM_PREFIX = "traveltime_";
private String cacheName = RequestCache.NAME;

@Override
Expand All @@ -25,12 +25,12 @@ public void init(NamedList args) {

String appId = args.get("app_id").toString();
String apiKey = args.get("api_key").toString();
FetcherSingleton.INSTANCE.init(uri, appId, apiKey);
ProtoFetcherSingleton.INSTANCE.init(uri, appId, apiKey);
}

@Override
public QParser createParser(String qstr, SolrParams localParams, SolrParams params, SolrQueryRequest req) {
return new TraveltimeQueryParser(qstr, localParams, params, req, FetcherSingleton.INSTANCE.getFetcher(), cacheName);
return new TraveltimeQueryParser(qstr, localParams, params, req, ProtoFetcherSingleton.INSTANCE.getFetcher(), cacheName);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import com.traveltime.plugin.solr.query.TraveltimeQueryParameters;

public class ExactRequestCache extends RequestCache {
public class ExactRequestCache extends RequestCache<TraveltimeQueryParameters> {
private final Object[] lock = new Object[0];

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.traveltime.plugin.solr.cache;

import com.traveltime.plugin.solr.query.timefilter.TimeFilterQueryParameters;

public class ExactTimeFilterRequestCache extends RequestCache<TimeFilterQueryParameters> {
private final Object[] lock = new Object[0];

@Override
public TravelTimes getOrFresh(TimeFilterQueryParameters key) {
TravelTimes result = get(key);
if (result == null) {
synchronized (lock) {
result = get(key);
if (result == null) {
result = new BasicTravelTimes();
put(key, result);
}
}
}
return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import java.util.Map;

public class FuzzyRequestCache extends RequestCache {
public class FuzzyRequestCache extends RequestCache<TraveltimeQueryParameters> {
private final Object[] lock = new Object[0];
private Map<String, String> args;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.traveltime.plugin.solr.cache;

import com.traveltime.plugin.solr.query.timefilter.TimeFilterQueryParameters;
import org.apache.solr.search.CacheRegenerator;

import java.util.Map;

public class FuzzyTimeFilterRequestCache extends RequestCache<TimeFilterQueryParameters> {
private final Object[] lock = new Object[0];
private Map<String, String> args;

@Override
public Object init(Map args, Object persistence, CacheRegenerator regenerator) {
this.args = args;
return super.init(args, persistence, regenerator);
}

@Override
public TravelTimes getOrFresh(TimeFilterQueryParameters key) {
key = key.withField(null).withTravelTime(0);
TravelTimes result = get(key);
if (result == null) {
synchronized (lock) {
result = get(key);
if (result == null) {
result = new LRUTimes(args);
put(key, result);
}
}
}
return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@

import java.util.Map;

public abstract class RequestCache extends FastLRUCache<TraveltimeQueryParameters, TravelTimes> {
public abstract class RequestCache<P> extends FastLRUCache<P, TravelTimes> {
public static String NAME = "traveltime";

public abstract TravelTimes getOrFresh(TraveltimeQueryParameters key);
public abstract TravelTimes getOrFresh(P key);

@Override
public void init(Map<String, String> args, CacheRegenerator ignored) {
Expand Down
10 changes: 10 additions & 0 deletions 8/src/main/java/com/traveltime/plugin/solr/fetcher/Fetcher.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.traveltime.plugin.solr.fetcher;

import com.traveltime.sdk.dto.common.Coordinates;
import java.util.ArrayList;

import java.util.List;

public interface Fetcher<Params> {
List<Integer> getTimes(Params parameters, ArrayList<Coordinates> points);
}
158 changes: 158 additions & 0 deletions 8/src/main/java/com/traveltime/plugin/solr/fetcher/JsonFetcher.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package com.traveltime.plugin.solr.fetcher;

import com.google.common.collect.Iterables;
import com.traveltime.plugin.solr.query.timefilter.TimeFilterQueryParameters;
import com.traveltime.plugin.solr.util.Util;
import com.traveltime.sdk.TravelTimeSDK;
import com.traveltime.sdk.auth.TravelTimeCredentials;
import com.traveltime.sdk.dto.common.Coordinates;
import com.traveltime.sdk.dto.common.Location;
import com.traveltime.sdk.dto.common.Property;
import com.traveltime.sdk.dto.requests.TimeFilterRequest;
import com.traveltime.sdk.dto.requests.timefilter.ArrivalSearch;
import com.traveltime.sdk.dto.requests.timefilter.DepartureSearch;
import com.traveltime.sdk.dto.responses.TimeFilterResponse;
import com.traveltime.sdk.dto.responses.errors.IOError;
import com.traveltime.sdk.dto.responses.errors.ResponseError;
import com.traveltime.sdk.dto.responses.errors.TravelTimeError;
import lombok.val;
import okhttp3.OkHttpClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.StreamSupport;

public class JsonFetcher implements Fetcher<TimeFilterQueryParameters> {
private final TravelTimeSDK api;

private final int locationSizeLimit;

private final Logger log = LoggerFactory.getLogger(JsonFetcher.class);

private void logError(TravelTimeError left) {
if (left instanceof IOError) {
val ioerr = (IOError) left;
log.warn(ioerr.getMessage());
log.warn(
Arrays.stream(ioerr.getCause().getStackTrace())
.map(StackTraceElement::toString)
.reduce("", (a, b) -> a + "\n\t" + b)
);
} else if (left instanceof ResponseError) {
val error = (ResponseError) left;
log.warn(error.getDescription());
}
}

public JsonFetcher(URI uri, String id, String key, int locationSizeLimit) {
val auth = TravelTimeCredentials.builder().appId(id).apiKey(key).build();
val client = new OkHttpClient.Builder()
.connectTimeout(5, TimeUnit.MINUTES)
.callTimeout(5, TimeUnit.MINUTES)
.readTimeout(5, TimeUnit.MINUTES)
.build();
val builder = TravelTimeSDK.builder().credentials(auth).client(client);
if(uri != null) {
builder.baseProtoUri(uri);
}
api = builder.build();
this.locationSizeLimit = locationSizeLimit;
}

private Integer[] extractTimes(TimeFilterResponse response, Integer[] travelTimes) {
val result = response.getResults().get(0);

result.getLocations()
.forEach(location ->
travelTimes[Integer.parseInt(location.getId())] = location.getProperties().get(0).getTravelTime()
);

result.getUnreachable()
.forEach(unreachableId ->
travelTimes[Integer.parseInt(unreachableId)] = -1
);

return travelTimes;
}

public List<Integer> getTimes(TimeFilterQueryParameters parameters, ArrayList<Coordinates> points) {

val locations = IntStream
.range(0, points.size())
.mapToObj(i -> new Location(String.valueOf(i), points.get(i)))
.collect(Collectors.toList());

val groupedLocations = Iterables.partition(locations, locationSizeLimit);

val requests = StreamSupport.stream(groupedLocations.spliterator(), true)
.map(locationGroup -> {
val requestBuilder = TimeFilterRequest.builder();

requestBuilder
.location(parameters.getLocation())
.locations(locations);

switch (parameters.getSearchType()) {
case ARRIVAL:
val arrivalSearchBuilder = ArrivalSearch
.builder()
.id("search")
.arrivalLocationId(parameters.getLocation().getId())
.departureLocationIds(locations.stream().map(Location::getId).collect(Collectors.toList()))
.arrivalTime(parameters.getTime())
.travelTime(parameters.getTravelTime())
.properties(Collections.singletonList(Property.TRAVEL_TIME))
.transportation(parameters.getTransportation());
val arrivalSearch = parameters
.getRange()
.map(arrivalSearchBuilder::range)
.orElse(arrivalSearchBuilder)
.build();
requestBuilder.arrivalSearch(arrivalSearch);
break;
case DEPARTURE:
val departureSearchBuilder = DepartureSearch
.builder()
.id("search")
.departureLocationId(parameters.getLocation().getId())
.arrivalLocationIds(locations.stream().map(Location::getId).collect(Collectors.toList()))
.departureTime(parameters.getTime())
.travelTime(parameters.getTravelTime())
.properties(Collections.singletonList(Property.TRAVEL_TIME))
.transportation(parameters.getTransportation());
val departureSearch = parameters
.getRange()
.map(departureSearchBuilder::range)
.orElse(departureSearchBuilder)
.build();
requestBuilder.departureSearch(departureSearch);
break;
}

return requestBuilder.build();
});

log.info(String.format("Fetching %d locations", points.size()));

Integer[] resultArray = new Integer[locations.size()];

requests.map(request -> Util.time(log, () -> api.send(request)))
.forEach(result -> result.fold(err -> {
logError(err);
throw new RuntimeException(err.toString());
},
succ -> extractTimes(succ, resultArray))
);

return Arrays.stream(resultArray).collect(Collectors.toList());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.traveltime.plugin.solr.fetcher;

import java.net.URI;

public enum JsonFetcherSingleton {
INSTANCE;

private JsonFetcher underlying = null;
private final Object[] lock = new Object[0];

public void init(URI uri, String id, String key, int locationSizeLimit) {
if(underlying != null) return;
synchronized (lock) {
if(underlying != null) return;
underlying = new JsonFetcher(uri, id, key, locationSizeLimit);
}
}

public JsonFetcher getFetcher() {
return underlying;
}

}
Loading

0 comments on commit f0cf9be

Please sign in to comment.