Reputation: 410
Trying to make an API in java play framework 2.8. I do not know what is wrong with the code I wrote but it doesnt work.
I get this response in localhost:9000
{"result":null,"stack":null,"done":false,"cancelled":false,"completedExceptionally":false,"numberOfDependents":0}
I think I am missing something when it comes to async calls because when I am trying to make run the code separately it works (using postman or and another playground class).
I am uploading the code and would be happy to know what is wrong with it. Thanks in advance!
package utils;
import com.fasterxml.jackson.databind.JsonNode;
import models.Commit;
import play.libs.ws.WSResponse;
import javax.inject.Inject;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.stream.Collectors;
public class CommitsService {
private BBService bbService;
@Inject
public CommitsService(BBService bbService) {
this.bbService = bbService;
}
public CompletionStage<CompletableFuture<List<Commit>>> retrieveCommitsForUser(String username) {
CompletionStage<List<String>> reposPromise = getRepos();
return reposPromise
.thenApplyAsync(repos -> sequence(repos.stream()
.map(repo -> getRepoCommits(repo))
.collect(Collectors.toList()))
.thenApplyAsync(responses -> {
List<Commit> allCommits = responses.stream()
.map(wsResponse -> extractCommits(wsResponse))
.flatMap(List::stream).filter(commit -> isByUser(commit, username) && isLastDay(commit))
.collect(Collectors.toList());
return allCommits;
}));
}
private CompletionStage<List<String>> getRepos() {
return bbService.getRepos()
.thenApplyAsync(response -> extractRepos(response));
}
private List<String> extractRepos(WSResponse wsResponse) {
Vector<String> repos = new Vector<>();
JsonNode reposAsJson = wsResponse.asJson().findValue("values");
for (JsonNode repo : reposAsJson) {
String repoName = repo.path("name").asText();
repos.add(repoName);
}
return repos;
}
private CompletableFuture<WSResponse> getRepoCommits(String repo) {
return bbService.getRepoCommits(repo).toCompletableFuture();
}
private boolean isByUser(Commit commit, String username) {
return commit.getAuthor().equals(username);
}
private boolean isLastDay(Commit commit) {
String sDate = commit.getDate().substring(0, commit.getDate().indexOf('T'));
String sTime = commit.getDate().substring(commit.getDate().indexOf('T'), commit.getDate().length() - 1);
return true;
}
private List<Commit> extractCommits(WSResponse wsResponse) {
Vector<Commit> allCommits = new Vector<>();
JsonNode commits = wsResponse.asJson().findValue("values");
for (JsonNode commit : commits) {
String HASH = commit.path("hash").asText();
String date = commit.path("date").asText();
String repo = commit.path("repository").path("name").asText();
String message = commit.path("message").asText();
String author = commit.path("author").path("user").path("display_name").asText();
Commit currCommit = new Commit(HASH, date, repo, message, author);
allCommits.add(currCommit);
}
return allCommits;
}
public <T> CompletableFuture<List<T>> sequence(List<CompletableFuture<T>> futuresList) {
return CompletableFuture
.allOf(futuresList.toArray(new CompletableFuture[0])) // (1)
.thenApply(v ->
futuresList.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList())
);
}
}
package controllers;
import play.libs.Json;
import play.mvc.*;
import utils.CommitsService;
import javax.inject.Inject;
import java.util.concurrent.CompletionStage;
/**
* Commits controller is handling in and out http traffic regarding bitbucket commits.
* Supporting get but can be expanded to other crud operations.
*/
public class CommitsController extends Controller {
private final CommitsService commitsService;
@Inject
public CommitsController(CommitsService commitsService) {
this.commitsService = commitsService;
}
public CompletionStage<Result> showCommits(String username) {
return commitsService.retrieveCommitsForUser(username)
.thenApplyAsync(res -> ok(Json.toJson(res)));
}
}
package utils;
import com.fasterxml.jackson.databind.JsonNode;
import play.libs.ws.*;
import play.mvc.Result;
import javax.inject.Inject;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.stream.Collectors;
public class BBService {
private final String BB_BASE_URL = "https://api.bitbucket.org/2.0/";
private final String URL = BB_BASE_URL + "repositories/******/";
private final String COMMITS = "commits";
private final String bbUsername = "*******";
private final String bbPassword = "*******";
private final WSClient ws;
@Inject
public BBService(WSClient ws) {
this.ws = ws;
}
public CompletionStage<WSResponse> getAsync(String url) {
WSRequest req = ws.url(url).setAuth(bbUsername, bbPassword);
return req.get();
}
public List<String> extractNames(WSResponse wsResponse) {
Vector<String> usersNames = new Vector<>();
JsonNode users = wsResponse.asJson().findValue("values");
for (JsonNode user : users) {
String userName = user.findValue("display_name").asText();
usersNames.add(userName);
}
return usersNames;
}
public CompletionStage<WSResponse> getRepos() {
return getAsync(URL);
}
public CompletionStage<WSResponse> getRepoCommits(String repoName) {
return getAsync(URL + repoName + "/" + COMMITS);
}
public Result withFullAccess(Result noAccessibleRes) {
return noAccessibleRes.withHeader("Access-Control-Allow-Origin", "*");
}
public <T> CompletableFuture<List<T>> sequence(List<CompletableFuture<T>> futuresList) {
return CompletableFuture
.allOf(futuresList.toArray(new CompletableFuture[0])) // (1)
.thenApply(v ->
futuresList.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList())
);
}
}
Upvotes: 0
Views: 492
Reputation: 4063
Taking into account the response you got, the problem is that you give as the response object of type CompletableFuture<List<Commit>>
and not List<Commit>
as you probably want, in CommitsController
:
retrieveCommitsForUser(username)
.thenApplyAsync(res -> ok(Json.toJson(res)))
where you give res
of type CompletableFuture<List<Commit>>
- that's why you see in response all fields from this class.
What you want to do is to return in method CommitsService.retrieveCommitsForUser
to return CompletionStage<List<Commit>>
instead of CompletionStage<CompletableFuture<List<Commit>>>
.
CompletionStage.thenComposeAsync
can help you with this - it can chain execution current CompletionStage
with next CompletionStage
execution.
So final soolution might look like:
public CompletionStage<List<Commit>> retrieveCommitsForUser(String username) {
CompletionStage<List<String>> reposPromise = getRepos();
return reposPromise
.thenComposeAsync(repos ->
sequence(repos.stream().map(repo -> getRepoCommits(repo)).collect(Collectors.toList()))
.thenApplyAsync(responses -> {
List<Commit> allCommits = responses.stream()
.map(wsResponse -> extractCommits(wsResponse))
.flatMap(List::stream).filter(commit -> isByUser(commit, username) && isLastDay(commit))
.collect(Collectors.toList());
return allCommits;
}));
}
Hope this helps!
Upvotes: 1