I am trying to return a CompletableFuture that will return a response from Amazon. My code first checks to see if a response is cached locally. If so, it returns the response, otherwise it calls Amazon. [Note: The real version will also cache the response received from Amazon, but I haven't included that as the code is already pretty complicated.]
Is there a way to change the implementation of my callAmazon method (or otherwise reorganize the code) so that I don't have to "manually" copy the response state from amazonApi to the finalResponse?
I don't want to return cacheCheck directly because I don't want the caller to be able to complete() it.
public CompletableFuture<Response> fetchProductList() {
CompletableFuture<Response> finalResponse = new CompletableFuture<>();
CompletableFuture<Response> cacheCheck = //...
// First, see if we have a cached copy
cacheCheck.whenComplete((response, throwable) -> {
if (throwable == null) {
// Cache hit. Return the cached response
finalResponse.complete(response);
} else {
// Cache miss. Call Amazon
callAmazon(finalResponse);
}
});
return finalResponse;
}
private void callAmazon(CompletableFuture<Response> finalResponse) {
CompletableFuture<Response> amazonApi = //...
amazonApi.whenComplete((response, throwable) -> {
// Copy the state to the `finalResponse`
if (throwable == null) {
finalResponse.complete(response);
} else {
finalResponse.completeExceptionally(throwable);
}
});
}
What makes your requirement so complex is the fact that cacheCheck can throw an exception.
What I would do in your case is to refactor the cache to deliver either null if the value was not found in the cache, or the actual Response, if the value was in the cache.
Furthermore, I would modify callAmazon to return directly the CompletableFuture:
private CompletableFuture<Response> callAmazon() {
CompletableFuture<Response> amazonApi = //...
return amazonApi;
}
This way you can use thenCompose:
final CompletableFuture<Response> cacheCheck = //...
final CompletableFuture<Response> amazonResponse = callAmazon();
final CompletableFuture<Response> finalResult =
cachedResponse.thenCompose(cacheResult -> {
return cacheResult == null ? amazonResponse : CompletableFuture.completedFuture(cacheResult);
});
If you really need to throw an exception from the cache, you can use exceptionally to convert the exception to a null value, and then use thenCompose to decide if you use the cache value, or call Amazon:
final CompletableFuture<Response> finalResult = cachedResponse.exceptionally(e -> {
return null;
}).thenCompose(cacheResult -> {
return cacheResult == null ? amazonResponse : CompletableFuture.completedFuture(cacheResult);
});
Related
I am using Vert.x in my project, I used future() to get the results from a MongoDB query. However when I do future().result it returns "null". I want the result to be saved in the future and I will use it for other APIs. Is there any guide for me, I will be very grateful and appreciate if someone give me a solution. Thanks
router.class
rivate void getClazzById(RoutingContext rc) {
Future<JsonObject> future = Future.future();
String clazzId = rc.request().getParam("clazzId");
classService.getClazzById(clazzId, res -> {
System.out.println(res.result());
if (res.succeeded()) {
JsonObject result = res.result();
if (result != null) {
future.complete(res.result());
rc.response().setStatusCode(200).putHeader("Content-Type", "application/json; charset=utf-8")
.end(result.encodePrettily());
} else {
rc.response().setStatusCode(400).putHeader("Content-Type", "application/json; charset=utf-8")
.end(new JsonObject().put("error", "Class not found!").encodePrettily());
}
} else
rc.fail(res.cause());
});
future.setHandler(s -> {
if (s.succeeded()) {
System.out.println("sss: " +s.result()); // print: {"_id":"123", "name":"abc"}
}
else {
System.out.println("fail");
}
});
System.out.println("hhhhhh: " + future.result()); // I want to print {"_id":"123", "name":"abc"}
}
service.class
public void getClazzById(String clazzId, Handler<AsyncResult<JsonObject>> resultHandler) {
JsonObject query = new JsonObject().put("_id", clazzId);
client.findOne(Collection.CLAZZ, query, null, ar -> {
if (ar.succeeded()) {
if (ar.succeeded()) {
JsonObject result = ar.result();
if (result == null) {
resultHandler.handle(Future.failedFuture("Error"));
} else {
resultHandler.handle(Future.succeededFuture(result));
}
} else {
ar.cause();
}
}
});
}
When writing asynchronous code, you are carried to use the framework / runtime semantics and tools for communication.
You are already leveraging one of Vert.x's way of async communication, the Future - but in the wrong manner trying to retrieve its result inline.
Instead of having the Future result accessed within your method, you need to return it as your mean of communication to a caller, which would be able to set a completion handler (Handler) to it:
private Future<JsonObject> getClazzById(RoutingContext rc) {
Future<JsonObject> future = Future.future();
String clazzId = rc.request().getParam("clazzId");
classService.getClazzById(clazzId, res -> {
if (res.succeeded()) {
JsonObject result = res.result();
if (result != null) {
future.complete(res.result()); // set the retrieved result
rc.response().setStatusCode(200).putHeader("Content-Type", "application/json; charset=utf-8")
.end(result.encodePrettily());
} else {
future.complete(null); // you need to provide 'null' result to avoid caller blocking
rc.response().setStatusCode(400).putHeader("Content-Type", "application/json; charset=utf-8")
.end(new JsonObject().put("error", "Class not found!").encodePrettily());
}
} else
rc.fail(res.cause());
});
return future; // return the future to caller
}
An interested caller would be able to set a handler for Future completion as needed:
getClassById(rc).setHandler(s -> {
if (s.succeeded()) {
System.out.println("sss: " +s.result()); // print: {"_id":"123", "name":"abc"}
}
else {
System.out.println("fail");
}
});
As a side note: you are not setting the API boundaries properly in your business logic as you are trying to resolve the HTTP Response result which generally denotes the request processing end while still returning the query result to be handled in some other manner.
I am using executeBlocking from vertx in a for loop to parallelise the processing of a result, and collating the results using a CompositeFuture. Based on all the results, I want to return some value from the method, but lambda function inside CompositeFuture's handler is not letting me do so. How to work with this usecase?
Code for reference:
public Response call() throws Exception {
List<Future> futureList = new ArrayList<>();
//services is a global variable, an arraylist of services
for(String service: services) {
Future serviceFuture = Future.future();
futureList.add(serviceFuture);
vertx.executeBlocking(implementTask(service, serviceFuture), false, asyncResult -> {
if (asyncResult.failed()) {
LOGGER.error("Task FAILED for service {}", service, asyncResult.cause());
}
});
}
CompositeFuture.all(futureList).setHandler(asyncResult -> {
if(asyncResult.succeeded()) {
LOGGER.debug("Task completed successfully");
return new Response(ResponseStatus.SUCCESS);
} else {
LOGGER.error("Task FAILED", asyncResult.cause());
return new Response(ResponseStatus.FAILED);
}
});
}
You can't do this.
Your call() method should return Future<Result> and not the Result. Then you would need to attach the callback handler on your original caller. This is the way that async methods propagate the result in Vert.x.
public Future<Response> call() throws Exception {
Promise<Response> promise = Promise.promise();
List<Future> futureList = new ArrayList<>();
//services is a global variable, an arraylist of services
for(String service: services) {
Future serviceFuture = Future.future();
futureList.add(serviceFuture);
vertx.executeBlocking(implementTask(service, serviceFuture), false, asyncResult -> {
if (asyncResult.failed()) {
LOGGER.error("Task FAILED for service {}", service, asyncResult.cause());
}
});
}
CompositeFuture.all(futureList).setHandler(asyncResult -> {
if(asyncResult.succeeded()) {
LOGGER.debug("Task completed successfully");
promise.complete(new Response(ResponseStatus.SUCCESS));
} else {
LOGGER.error("Task FAILED", asyncResult.cause());
promise.fail("Failed");
}
});
return promise.future();
}
Then the call would look like this:
object.call().onSuccess(resultHandler - > {
//Code when call succeeded
}).onFailure(failureHandler - > {
//Code when call failed
});
Note: This example is using Vert.x 4. If you are using a version of Vert.x less than 4, then the syntax is a bit different, but the point stays the same.
I am trying to use source.queue with cachedHostConnectionPool in java but not able to get the correct output. Below is my attempt at it. I am always getting success response, which i understand why but not sure what is the right way to do it.
Reason why i am getting success response is because i have defined Try as HttpResponse.create() , which is always giving me success response but my doubt is why it is not getting overwritten when i am doing queue.offer(Pair.create(httprequest,promise)).
Can someone please help me to understand what am i doing wrong here.
// Method to return queue
public SourceQueueWithComplete<Object> httpGetRequestWithPool
(Materializer materializer)
{
final Http http = Http.get(system);
final Flow<Pair<HttpRequest, Object>, Pair<Try<HttpResponse>, Object>, HostConnectionPool> flow;
flow = http.cachedHostConnectionPool(ConnectHttp.toHost("host:port"));
final SourceQueueWithComplete<Object> queue= Source.queue(3,OverflowStrategy.dropNew())
.map(d -> Pair.create(HttpRequest.create("contextpath"),d))
.via(flow)
.toMat(Sink.foreach(p -> p.first()), Keep.left())
.run(materializer);
return queue;
}
// Here i am trying to use.
SourceQueueWithComplete<Object> queue = util.httpGetRequestWithPool(materializer);
Source<Object, NotUsed> source = Source.from(ListOfObject);
source.mapAsyncUnordered(3,x-> {
String url = util.getServiceUrl("servicename").append(x.getId).toString();
HttpRequest httpRequest = HttpRequest.create(url);
Try<HttpResponse> promise = Try.apply(()->HttpResponse.create());
queue.offer(Pair.create(httpRequest,promise))
.thenCompose(result -> {
if(result instanceof QueueOfferResult.Enqueued$){
return CompletableFuture.completedFuture(promise)
.thenApply(res ->{
if(res.get().status().intValue()==200){
System.out.println("success");
}
return res;
});
}
else{
return CompletableFuture.completedFuture(HttpResponse.create().withStatus(StatusCodes.SERVICE_UNAVAILABLE));
}
});
return null;
}).run(materializer);
```
I have two observable executions.
I want to execute the second only if first one is empty/null and when finish to execute the final code block.
However - the second observable always executed even if first observable is not empty.
handleLocation(msg).filter(result -> result != null).switchIfEmpty(addLocation(msg)).subscribe(
response -> {
handleResponse(routingContext, transactionId, msg, response);
});
private Observable<LocationDTO> handleLocation(JsonObject msg) {
Location locationDTO=new locationDTO();
...
return Observable.just(locationDTO);
}
as you see handleLocation will never return null/empty object.
why addLocation(msg) getting triggered?
addLocation signature:
private Observable<MyDTO> addLocation(JsonObject msg) {
return redisRepo.getLocationByIp(ip).switchIfEmpty(getLocationByHost(host);
}
private Observable<LocationDTO> getLocationByHost(Strin host) {
...
return Observable.just(new LocationDTO());
I managed to fix this by adding return Observable.fromCallable(() to addLocation. any idea why it resolved this way?
Using filter will emit all the values that pass the filter. If I understand right, you are looking for something like:
Observable.concat(cache, remote)
.first(new Func1<Result, Boolean>() {
#Override
public Boolean call(Result result) {
return result != null;
}
});
This will emit the first non-null "Result".
In my GWT Application I'm often refering several times to the same server results. I also don't know which code is executed first. I therefore want to use caching of my asynchronous (client-side) results.
I want to use an existing caching library; I'm considering guava-gwt.
I found this example of a Guava synchronous cache (in guava's documentation):
LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
.build(
new CacheLoader<Key, Graph>() {
public Graph load(Key key) throws AnyException {
return createExpensiveGraph(key);
}
});
This is how I'm trying to use a Guava cache asynchronously (I have no clue about how to make this work):
LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
.build(
new CacheLoader<Key, Graph>() {
public Graph load(Key key) throws AnyException {
// I want to do something asynchronous here, I cannot use Thread.sleep in the browser/JavaScript environment.
service.createExpensiveGraph(key, new AsyncCallback<Graph>() {
public void onFailure(Throwable caught) {
// how to tell the cache about the failure???
}
public void onSuccess(Graph result) {
// how to fill the cache with that result???
}
});
return // I cannot provide any result yet. What can I return???
}
});
GWT is missing many classes from the default JRE (especially concerning threads and concurrancy).
How can I use guava-gwt to cache asynchronous results?
As I understood what you want to achieve is not just a asynchronous cache but also a lazy cache and to create one the GWT is not a best place as there is a big problem when implementing a GWT app with client side Asynchronous executions, as GWT lacks the client side implementations of Futures and/or Rx components (still there are some implementations of RxJava for GWT). So in usual java what you want to create can be achieved by :
LoadingCache<String, Future<String>> graphs = CacheBuilder.newBuilder().build(new CacheLoader<String, Future<String>>() {
public Future<String> load(String key) {
ExecutorService service = Executors.newSingleThreadExecutor();
return service.submit(()->service.createExpensiveGraph(key));
}
});
Future<String> value = graphs.get("Some Key");
if(value.isDone()){
// This will block the execution until data is loaded
String success = value.get();
}
But as GWT has no implementations for Futures you need to create one just like
public class FutureResult<T> implements AsyncCallback<T> {
private enum State {
SUCCEEDED, FAILED, INCOMPLETE;
}
private State state = State.INCOMPLETE;
private LinkedHashSet<AsyncCallback<T>> listeners = new LinkedHashSet<AsyncCallback<T>>();
private T value;
private Throwable error;
public T get() {
switch (state) {
case INCOMPLETE:
// Do not block browser so just throw ex
throw new IllegalStateException("The server response did not yet recieved.");
case FAILED: {
throw new IllegalStateException(error);
}
case SUCCEEDED:
return value;
}
throw new IllegalStateException("Something very unclear");
}
public void addCallback(AsyncCallback<T> callback) {
if (callback == null) return;
listeners.add(callback);
}
public boolean isDone() {
return state == State.SUCCEEDED;
}
public void onFailure(Throwable caught) {
state = State.FAILED;
error = caught;
for (AsyncCallback<T> callback : listeners) {
callback.onFailure(caught);
}
}
public void onSuccess(T result) {
this.value = result;
state = State.SUCCEEDED;
for (AsyncCallback<T> callback : listeners) {
callback.onSuccess(value);
}
}
}
And your implementation will become :
LoadingCache<String, FutureResult<String>> graphs = CacheBuilder.newBuilder().build(new CacheLoader<String, FutureResult<String>>() {
public FutureResult<String> load(String key) {
FutureResult<String> result = new FutureResult<String>();
return service.createExpensiveGraph(key, result);
}
});
FutureResult<String> value = graphs.get("Some Key");
// add a custom handler
value.addCallback(new AsyncCallback<String>() {
public void onSuccess(String result) {
// do something
}
public void onFailure(Throwable caught) {
// do something
}
});
// or see if it is already loaded / do not wait
if (value.isDone()) {
String success = value.get();
}
When using the FutureResult you will not just cache the execution but also get some kind of laziness so you can show some loading screen while the data is loaded into cache.
If you just need to cache the asynchronous call results, you can go for a
Non-Loading Cache, instead of a Loading Cache
In this case you need to use put, getIfPresent methods to store and retrieve records from cache.
String v = cache.getIfPresent("one");
// returns null
cache.put("one", "1");
v = cache.getIfPresent("one");
// returns "1"
Alternatively a new value can be loaded from a Callable on cache misses
String v = cache.get(key,
new Callable<String>() {
public String call() {
return key.toLowerCase();
}
});
For further reference: https://guava-libraries.googlecode.com/files/JavaCachingwithGuava.pdf