I want to create an action that I can use with the #With annotation style. This action will need to proceed to an RPC call so if I understood correctly the documentation I should rather put this in an async way.
This is what I tried to do until now:
public class GetUserIdAction extends play.mvc.Action.Simple {
#Override
public CompletionStage<Result> call(Http.Context context) {
String token = "";
if (StringUtils.isEmpty(token)) {
return delegate.call(context);
}
CompletionStage<Http.Context> promiseOfUpdatedContext = CompletableFuture.supplyAsync(() -> setUserIdForToken(context, token));
return promiseOfUpdatedContext.thenApply(ctx -> delegate.call(ctx));
}
private Http.Context setUserIdForToken(Http.Context context, String token) {
context.args.put("user_id", authenticationManager.getUserIdForToken(token));
// The AuthenticationManager is issuing an RPC call and thus may take some time to complete.
return context;
}
}
Set aside the fact that token is always empty and authenticationManager is not set, this is just a quick meaningless example, my IDE is complaining on the thenApply part. For what I understand, it is expecting a CompletionStage<Result> and gets something more like a CompletionStage<CompletionStage<Result>>.
What is a way to deal with it? Cause here all I want is to put some information in the Context and then continue the delegate.call chain.
Or maybe I'm trying to do something stupid and composed actions are already asynchronous?
You have a CompletionStage<Something> and want to end with a CompletionStage<Result>. The easiest way to achieve that is using thenCompose.
Here is an example, with a small change: I have a CompletableFuture to get the token and only then I add it to the HttpContext
#Override
public CompletionStage<Result> call(final Http.Context context) {
final String token = "";
if (StringUtils.isEmpty(token)) {
return delegate.call(context);
}
return CompletableFuture.supplyAsync(() -> {
// do something to fetch that token
return "your_new_token";
}).thenCompose(tokenReceived -> {
context.args.put("user_id", tokenReceived);
return delegate.call(context);
});
}
Related
I have the following endpoint:
public void save(#Valid #RequestBody SaveRequest request) {
demoService.save(request);
}
In SaveRequest, I pass a single productUuid as shown below.
#Data
public class SaveRequest {
private UUID productUuid;
private DetailsRequest detailsRequest;
}
Here is my service method:
public void save(final SaveRequest request) {
detailService.getDetails(request.getProductUuid());
// code omitted
}
What I want to do is; I just want to pass productUuid as a list instead of single element. private List<UUID> productUuid;
I thought that I can call the service method for each productUuid in the SaveRequest, instead of modifying the service method by using a similar approach to the following. Then I call this methıd from the Controller instead of the save methof of the service.
public void saveList(final SaveRequest request) {
request.getProductUuidList().stream()
.map(this::save)
.collect(Collectors.toList());
}
But I could not use it. So, how should I fix this issue? If it is good approach to use saveList methood and call save method for each productUuid in the SaveRequest, it may be better to use it without changing the save method.
Updating the answer now. You were correct with your last answer, because Streams usually expect you to do something with what is inside (in your case UUIDs).
If you don't want to have a Consumer for UUIDs, you can use a for loop:
public void saveList(final SaveRequest request) {
for (UUID uuid: request.getProductUuidList()) {
save(request);
}
}
and if you need the UUID that is currently being iterated, change the save method to accept a UUID as an argument
public void saveList(final SaveRequest request) {
for (UUID uuid: request.getProductUuidList()) {
save(uuid, request);
}
}
As you can see, the last method has a consumer for UUIDs now, so it can also be done using streams:
public void saveList(final SaveRequest request) {
request.getProductUuidList().stream().forEach(
uuid -> save(uuid, request);
)
}
Using stream() here is optional, but can provide you with helpful features, such as filters.
I want to call the method when previous returned Mono<Void>:
#Override
public Mono<Void> sendEmail(EmailDto emailDto) {
return mailReactiveClient.sendEmail(message ->
createMessage(emailDto, emailDto.getBody(), message))
.doOnNext(saveNotificationLog(emailDto)); //it's not work
}
private void saveNotificationLog(EmailDto emailDto) {
notificationLogReactiveRepository.save(NotificationLog.builder()
...
.build());
}
Method sendEmailreturns Mono<Void>.
So how to call saveNotificationLog?
UPD: Tring to make my question simplier:
#Override
public Mono<Void> sendEmail(EmailDto emailDto) {
return mailReactiveClient.sendEmail(message ->
createMessage(emailDto, emailDto.getBody(), message))
.doOnNext(System.out.print("Hello world!");
}
How to call doOnNextor similar method after sendEmail return Mono<Void>?
The Mono will not emit data, so doOnNext will not be triggered. You should use the doOnSuccess instead.
Also, your Mono need to be consumed. Without the code, we don't know if it is or not.
Some example here: I added subscribe() to consume the mono. Depending on the use of your Mono, you will have to do or not the same thing.
This print nothing:
Mono<String> m=Mono.just("test");
Mono<Void> v=m.then();
v.doOnNext(x->System.out.println("OK")).subscribe();
This print "OK":
Mono<String> m=Mono.just("test");
Mono<Void> v=m.then();
v.doOnSuccess(x->System.out.println("OK")).subscribe();
doOnNext, and in general all doOn* reactor methods are side-effect methods. You're not supposed to call them to do I/O work or chain operations, but rather log things and not do anything that would affect the state of the application.
In your code sample, notificationLogReactiveRepository.save returns Mono<Void>. The saveNotificationLog returns void and does not subscribe to the publisher returned by notificationLogReactiveRepository.save. This means the notification will not be saved, because nothing happens until you subscribe.
In this case, it seems you're trying to chain operations - then operators are just made for that. Your code should look like this:
#Override
public Mono<Void> sendEmail(EmailDto emailDto) {
return mailReactiveClient.sendEmail(message ->
createMessage(emailDto, emailDto.getBody(), message))
.then(saveNotificationLog(emailDto));
}
private Mono<Void> saveNotificationLog(EmailDto emailDto) {
return notificationLogReactiveRepository.save(NotificationLog.builder()
...
.build());
}
Try it this way:
Mono.empty().then()
I need to validate request before different rpc methods being called with different validators.
So I implemented validators like
class BarRequestValidator {
public FooServiceError validate(BarRequest request) {
if (request.bar.length > 12) {
return FooServiceError.BAR_TOO_LONG;
} else {
return null;
}
}
}
and add a custom annotation before my rpc method
class FooService extends FooServiceGrpc.FooServiceImplBase {
#Validated(validator = BarRequestValidator.class)
public void bar(BarRequest request, StreamObserver<BarResponse> responseObserver) {
// Validator should be executed before this line, and returns error once validation fails.
assert(request.bar <= 12);
}
}
But I found that I can't find a way to get annotation information in gRPC ServerInterceptor. Is there any way to implement grpc request validation like this?
You can accomplish this without having the annotation at all, and just using a plain ServerInterceptor:
Server s = ServerBuilder.forPort(...)
.addService(ServerInterceptors.intercept(myService, myValidator))
...
private final class MyValidator implements ServerInterceptor {
ServerCall.Listener interceptCall(call, headers, next) {
ServerCall.Listener listener = next.startCall(call, headers);
if (call.getMethodDescriptor().getFullMethodName().equals("service/method")) {
listener = new SimpleForwardingServerCallListener(listener) {
#Override
void onMessage(request) {
validate(request);
}
}
}
return listener;
}
}
Note that I'm skipping most of the boilerplate here. When a request comes in, the interceptor gets it first and checks to see if its for the method it was expecting. If so, it does extra validation. In the generated code you can reference the existing MethodDescriptors rather than copying the name out like above.
Inside an Action, which is the best way to execute some asynchronous code if you don't need a Promise to produce the actual Result?
E.g I've created a RecordedAction to compose with other Actions. It executes an expensive operation (writes to the DB) but doesn't produce something needed by the annotated Action. In other words, I don't need a Promise but I do need a separate ExecutionContext.
I could do something like this:
public class RecordedAction extends Action<Recorded> {
#Override
public F.Promise<Result> call(Context ctx) throws Throwable {
Promise.promise(() -> {
// do asynchronous stuff
return Promise.pure(null, myExecutionContext);
});
return delegate.call(ctx);
}
}
But I'm just exploiting the Promise to use a separate ExecutionContext.
I am currently using Play 2.3 and I have to deal with similar URL mappings:
GET /api/companyId/employeeId/tasks
GET /api/companyId/employeeId/timesheets
etc.
In every GET I need to perform similar logic:
public Promise<Result> getEmployeeTimesheets(Long companyId, Long employeeId) {
return promise(() -> {
if (!companyRepository.one(companyId).isPresent()) {
return notFound("Company doesn't exist");
}
if (!employeeRepository.one(employeeId).isPresent()) {
return notFound("Employee doesn't exist");
}
if (!employeeRepository.employeeWorksForCompany(companyId, employeeId)) {
return forbidden("Employee doesn't work for this company");
}
// some actual logic here
});
}
This code repeats over and over again. So far I used plain old inheritance and moved that repeating code into the parent controller class. It gets the job done, but it certainly isn't perfect solution (because I have to invoke parent method and inspect results manually in every controller action).
Is there some more declarative approach in Play that would automatically handle fragment of URL (/api/companyId/employeeId in our case) and either delegate the execution to an appropriate controller, or return an error response (for example 404 - Not Found).
You said you are calling the method again and again in each controller function instead you can use #With annotation.For ex
create a class CheckUrl.java
public class CheckUrl extends play.mvc.Action.Simple {
public F.Promise<SimpleResult> call(Http.Context ctx) throws Throwable {
String host = request().uri();
if (condition one satisfied) {
return F.Promise.pure(redirect("/someurl"));
}else if (condition two satisfied){
return F.Promise.pure(redirect(controllers.routes.SomeController.index()));
}
}
Place #With(CheckUrl.class) in class to apply to all its function.
#With(CheckUrl.class)
public class MyController extends Controller {
}
and for a particular function
public class MyController extends Controller {
#With(CheckUrl.class)
public static Result index() {
}
}
In the above cases CheckUrl.java is invoked before function in a controller