Play2 calling multiple webservices with AsyncResult (Java) - java

I have a Play 2.1 controller in Java, and I need to call an external webservice to get some data. Then with this data result, I must call another web service with n calls, corresponding to the n results from the first webservice call.
For performance issues, I want to make the n calls in separated threads using promises.
So I would have a loop like this:
List<String> firstResults = WS.url("http://...") ///...blablabla
for(String keyword : firstResults){
Promise<ResultType> promise = play.libs.Akka.future(
new Callable<ResultType>() {
public Integer call() {
return //...
}
}
);}
How can I synchronize the n promises and then reduce the results in one response (a list of all results) using the Async API, and then return the http responses only when all calls are finished?
Not being able to know the number of calls make the problem more difficult ... (I can't declare promises as promise1, promise2 etc.)

Promise.waitAll is what you want:
List<String> firstResults = WS.url("http://...") ///...blablabla
List<Promise<? extends ResultType>> webServiceCalls = new ArrayList<>;
for(String keyword : firstResults){
Promise<ResultType> promise = WS.url("http://...?keyboard=" + keyword).get().map(
// function of Response to ResultType
);
webServiceCalls.add(promise);
}
// Don't be confused by the name here, it's not actually waiting
Promise<List<ResultType>> results = Promise.waitAll(webServiceCalls);
return async(results.map(new Function<List<ResultType, Result>>() {
public Result apply(List<ResultType> results) {
// Convert results to ResultType
}
});

Related

Mutiny - How to group items to send request by blocks

I'm using Mutiny extension (for Quarkus) and I don't know how to manage this problem.
I want to send many request in an async way so I've read about Mutiny extension. But the server closes the connection because it receives thousand of them.
So I need:
Send the request by blocks
After all request are sent, do things.
I've been using Uni object to combine all the responses as this:
Uni<Map<Integer, String>> uniAll = Uni.combine()
.all()
.unis(list)
.combinedWith(...);
And then:
uniAll.subscribe()
.with(...);
This code, send all the request in paralell so the server closes the connection.
I'm using group of Multi objects, but I don't know how to use it (in Mutiny docs I can't found any example).
This is the way I'm doing now:
//Launch 1000 request
for (int i=0;i<1000;i++) {
multi = client.getAbs("https://api.*********.io/jokes/random")
.as(BodyCodec.jsonObject())
.send()
.onItem().transformToMulti(
array -> Multi.createFrom()
.item(array.body().getString("value")))
.group()
.intoLists()
.of(100)
.subscribe()
.with(a->{
System.out.println("Value: "+a);
});
}
I think that the subscription doesn't execute until there are "100" groups of items, but I guess this is not the way because it doesn't work.
Does anybody know how to launch 1000 of async requests in blocks of 100?
Thanks in advance.
UPDATED 2021-04-19
I've tried with this approach:
List<Uni<String>> listOfUnis = new ArrayList<>();
for (int i=0;i<1000;i++) {
listOfUnis.add(client
.getAbs("https://api.*******.io/jokes/random")
.as(BodyCodec.jsonObject())
.send()
.onItem()
.transform(item -> item
.body()
.getString("value")));
}
Multi<Uni<String>> multiFormUnis = Multi.createFrom()
.iterable(listOfUnis);
List<String> listOfResponses = new ArrayList<>();
List<String> listOfValues = multiFormUnis.group()
.intoLists()
.of(100)
.onItem()
.transformToMultiAndConcatenate(listOfOneHundred ->
{
System.out.println("Size: "+listOfOneHundred.size());
for (int index=0;index<listOfOneHundred.size();index++) {
listOfResponses.add(listOfOneHundred.get(index)
.await()
.indefinitely());
}
return Multi.createFrom()
.iterable(listOfResponses);
})
.collectItems()
.asList()
.await()
.indefinitely();
for (String value : listOfValues) {
System.out.println(value);
}
When I put this line:
listOfResponses.add(listOfOneHundred.get(index)
.await()
.indefinitely());
The responses are printed one after each other, and when the first 100s group of items ends, it prints the next group. The problem? There are sequential requests and it takes so much time
I think I am close to the solution, but I need to know, how to send the parallel request only in group of 100s, because if I put:
subscribe().with()
All the request are sent in parallel (and not in group of 100s)
I think you create the multy wrong, it would be much easier to use this:
Multi<String> multiOfJokes = Multi.createFrom().emitter(multiEmitter -> {
for (int i=0;i<1000;i++) {
multiEmitter.emit(i);
}
multiEmitter.complete();
}).onItem().transformToUniAndMerge(index -> {
return Uni.createFrom().item("String" + index);
})
With this approach it should mace the call parallel.
Now is the question of how to make it to a list.
The grouping works fine
I run it with this code:
Random random = new Random();
Multi<Integer> multiOfInteger = Multi.createFrom().emitter(multiEmitter -> {
for (Integer i=0;i<1000;i++) {
multiEmitter.emit(i);
}
multiEmitter.complete();
});
Multi<String> multiOfJokes = multiOfInteger.onItem().transformToUniAndMerge(index -> {
if (index % 10 == 0 ) {
Duration delay = Duration.ofMillis(random.nextInt(100) + 1);
return Uni.createFrom().item("String " + index + " delayed").onItem()
.delayIt().by(delay);
}
return Uni.createFrom().item("String" + index);
}).onCompletion().invoke(() -> System.out.println("Completed"));
Multi<List<String>> multiListJokes = multiOfJokes
.group().intoLists().of(100)
.onCompletion().invoke(() -> System.out.println("Completed"))
.onItem().invoke(strings -> System.out.println(strings));
multiListJokes.collect().asList().await().indefinitely();
You will get a list of your string.
I don't know, how you intend to send the list to backend.
But you can either to it with:
call (executed asynchronously)
write own subscriber (implements Subscriber) the methods are straight forward.
As you need for your bulk request.
I hope you understand it better afterward.
PS: link to guide where I learned all of it:
https://smallrye.io/smallrye-mutiny/guides
So in short you want to batch parallel calls to the server, without hitting it with everything at once.
Could this work for you? It uses merge. In my example, it has a parallelism of 2.
Multi.createFrom().range(1, 10)
.onItem()
.transformToUni(integer -> {
return <<my long operation Uni>>
})
.merge(2) //this is the concurrency
.collect()
.asList();
I'm not sure if merge was added later this year, but this seems to do what you want. In my example, the "long operation producing Uni" is actually a call to the Microprofile Rest Client which produces a Uni, and returns a string. After the merge you can put another onItem to perform something with the response (it's a plain Multi after the merge), instead of collecting everything as list.

Making multiple get requests to a list of URLs in android

I basically have a list of strings (about 20 to 60 strings) and I want to send one post request (and get the result which will be in json format and append a specific value from response to a new list) with each item in the list. I want to use ThreadPoolExecutor with a specific number of workers.
I tried few things but unfortunately I couldn't do it. I can do one request at a time but that's not efficient and takes a long time.
I have this code in python which achieves exactly what I want to do but unfortunately I couldn't reproduce it in Java.
#This function makes a single request using one string from the list
def getname(id):
url = "https://api-public-service.battledash.co/fortnite/cosmetics/search/id?q=" + id
with requests.session() as ss:
l = ss.get(url).json()
#we return a value from the response
return(l['readableName'])
def getnamelist(ids):
result = []
ids = deque(ids)
#Turning the list into a dict and back to list in order to remove duplicated items
ids = list(dict.fromkeys(ids))
#max workers is set to 10
with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
#running the getname function which sends a single request and return a name from id
results = executor.map(getname,ids)
#appending all results into a list
for it in tuple(results):
result.append(it)
return(result)
Note: As you did not specify the Java version my answer targets Java >= 8.
Assuming you have the following function that is thread-safe: public SomeHttpResponseType performGetRequest(String url):
public List<SomeHttpResponseType> performGetRequests(List<String> urls) {
return urls.stream().parallelStream()
.map(this::performGetRequest)
.collect(Collectors.toList())
}
This uses the default ForkJoinPool. If you want to specify your own thread pool try something like this :
ForkJoinPool forkJoinPool = null;
try {
forkJoinPool = new ForkJoinPool(parallelism);
forkJoinPool.submit(() ->
urls.stream().parallelStream()
.map(this::performGetRequest)
.collect(Collectors.toList())
).get()
} catch(InterruptedException | ExecutionException e) {
e.printStackTrace();
} finally {
if (forkJoinPool != null) {
forkJoinPool.shutdown(); //always remember to shutdown the pool
}
}
(adapted version of https://www.codementor.io/nitinpuri/controlling-parallelism-of-java-8-collection-streams-umex0qbt1)

Multiple call to an api but restriction of time

I have to call an api with different #RequestParam values, but any of the request takes time cancel the request and call the api with next param value
I have used java 8 completableFuture with a time restriction of 3 seconds but sometime it fails to execute correctly on server
public class callApi {
public void requestApi(){
//I have a list of values
List<Integer> paramValues = Arrays.asList(100, 101, 102);
//Now I want to call an api throw service layer
for(Integer param : paramValues) {
List<ResponseResult> result = anyService.getResults(param);
//if any of the request take time more than two second i
//have to
// cancel the request and call the api with different param
//iterated
//through the for loop
//i am using completableFuture like this
/*
CompletableFuture<List<ResponseResult>> completableFuture =
CompletableFuture.supplyAsync(() -> {
return anyService.getResults(param);
}).exceptionally(ex -> null);
result = completableFuture.get(3, TimeUnit.SECONDS);
*/
}
}
}
Some time it gives null result and sometime it works fine. Is there any other method of implying this

Make two requests in worker verticle and merge response from two requests

I have vertx server application where I am getting single client requests and from the server, I need to make two blocking calls. For instance, one call to back-end system A and another call to back-end system B. I am looking to make two concurrent calls to both the systems. I need to wait for the responses from both the calls and then merge two data from both the calls and then send the response back to client. I am unable to figure out how to do this in worker verticle.
Could anyone recommend what would be the best approach in vertx?
This sounds like a good use case for Promises. Give the module vertx-promises a try.
create a CompositeFuture from your launched Futures and handle it normally.
public Future<JsonArray> getEntitiesByIndFields(String keyspace, String entidad, String field1, String field2) {
Promise<JsonArray> p = Promise.promise();
// launch in parallel
Future<JsonArray> f1 = getEntitiesByIndField1(keyspace, entidad, field1);
Future<JsonArray> f2 = getEntitiesByIndField2(keyspace, entidad, field2);
CompositeFuture.all(f1, f2).setHandler(done ->
{
if (done.failed()) {
p.fail(done.cause());
return;
}
List<JsonArray> ja = done.result().list();
JsonArray finalarray = ja.get(0);
ja.get(1).forEach(jo ->
{ // add one by one, don't duplicate ids
long id = ((JsonObject) jo).getLong("id");
if (!containsKey(finalarray, id)) {
finalarray.add(jo);
}
});
;
p.complete(finalarray); // send union of founds
});
return p.future();
}

How to wait for a method with result callback to complete before continuing (Android)?

So I am a noob at Android, and I'm writing a simple app that uses Google Fit to store the users fitness sessions and step count and then retrieve them.
I have two methods, one that fetches all the sessions from a given date range from the cloud, the next method iterates through these and adds up the step count.
Problem is, that although I call the the fetching method first, the result doesn't come back until after I've added the steps up, so step count is always zero.
Here's my code:
private ArrayList<> results;
#Override
public ArrayList<IndividualSession> readAllSessions(Date dateFrom, Date dateTo) {
/* I haven't included the following code in this question just to keep things clean, but here there was
- The initialisation of the results ArrayList
- Creating the calendar and date objects
- Building the session read request
*/
Fitness.SessionsApi.readSession(mGoogleApiClient, readRequest).setResultCallback(new ResultCallback<SessionReadResult>() {
#Override
public void onResult(SessionReadResult sessionReadResult) {
for (Session session : sessionReadResult.getSessions()) {
List<DataSet> dataSets = sessionReadResult.getDataSet(session);
for (DataSet dataSet : dataSets) {
for (DataPoint dataPoint : dataSet.getDataPoints()) {
// Create new IndividualSession object, add data to it then add it to arraylist
IndividualSession individualSessionObject = new IndividualSession();
individualSessionObject.setFromDate(new Date(session.getStartTime(TimeUnit.SECONDS)));
individualSessionObject.setToDate(new Date(session.getEndTime(TimeUnit.SECONDS)));
individualSessionObject.setStepCount(dataPoint.getValue(Field.FIELD_STEPS).asInt());
results.add(individualSessionObject);
}
}
}
Log.i(TAG, "Number of sessions found while reading: "+results.size());
}
});
return results;
}
#Override
public int getDaySteps(Date dateTo) {
int stepCount = 0; // to be returned
// Sort out the dates
Calendar calFrom = Calendar.getInstance();
calFrom.add(Calendar.HOUR, -24);
// Get the sessions for appropriate date range
ArrayList results = readAllSessions(calFrom.getTime(), dateTo);
Log.i(TAG, "Number of sessions found while trying to get total steps: "+results.size());
// Iterate through sessions to get count steps
Iterator<IndividualSession> it = results.iterator();
while(it.hasNext())
{
IndividualSession obj = it.next();
stepCount += obj.getStepCount();
}
return stepCount;
}
This outputs
"Number of sessions found while trying to get total steps: 0"
"Number of sessions found while reading: 8"
There are two solutions to this :
Option 1 : Use a blocking colleciton
Change the ArrayList<> results to an ArrayBlockingQueue<> results.
After the call to the readAllSessions method in the getDaySteps method, call while(results.take()!=null) { //rest of the logic }
You need some kind of mechanistm to exit the while loop in step 2 when all results are read
Option 2 : Use the await method from PendingResult
Looking at the documentation for SessionsAPI class, the readSessions method seems to return a PendingResult :
public abstract PendingResult readSession
(GoogleApiClient client, SessionReadRequest request)
Reads data from the user's Google Fit store of the specific type(s)
and for the specific session(s) selected from the request parameters.
Looking at the documentation of the await method in PendingResult class :
public abstract R await ()
Blocks until the task is completed. This is not allowed on the UI thread. The returned result object can have an additional failure mode
of INTERRUPTED.
This is what you can do. Instead of chaining the entire call to setResultCallBack, first call readSessions :
results = Fitness.SessionsApi.readSession(mGoogleApiClient, readRequest);
And then wait for the results in the getDaySteps method :
SessionReadResults sessionResults = results.await();
for (Session session : sessionReadResult.getSessions()) {
List<DataSet> dataSets = sessionReadResult.getDataSet(session);
for (DataSet dataSet : dataSets) {
for (DataPoint dataPoint : dataSet.getDataPoints()) {
// Create new IndividualSession object, add data to it then add it to arraylist
IndividualSession individualSessionObject = new IndividualSession();
individualSessionObject.setFromDate(new Date(session.getStartTime(TimeUnit.SECONDS)));
individualSessionObject.setToDate(new Date(session.getEndTime(TimeUnit.SECONDS)));
individualSessionObject.setStepCount(dataPoint.getValue(Field.FIELD_STEPS).asInt());
//use the results
}
}
}
*results must be declared as an instance/class level variable to be accessible in all the methods in the class. The variable result is of type PendingResult<SessionReadResults>. Also, looks like you can do away with the results ArrayList since everything you want can be extracted from the SessionReadResults returned by the await method. One last note, this answer has not been tested with your code because your code sample is not complete.

Categories