I am attempting to create a registration handler for my Play Framework application but I am running into some trouble. The goal of this code is to grab the data that was submitted and then check if the username or email is in use. This is done using methods built into my User class, which is a model.
The register method:
public static F.Promise<Result> register() {
// Get the POST data and turn it into something we can read from
DynamicForm form = Form.form().bindFromRequest();
// Get the email, username, and password
String email = form.get("email");
String username = form.get("username");
String password = form.get("password");
return User.findByUsername(username).map(user -> {
// For .map to run we must not have encountered an error, this means
// a user already has this username (otherwise the doc would not exist)
return redirect("/register");
}).recover(userCheckError -> {
// For .recover to run there must have been an error. This will happen
// if a doc was not found with the username (therefore it is not in use)
return User.findByEmail(email).map(option -> { // ERROR START
// We now have an F.Option object. If the email is already taken,
// then the Option is defined (has a value within it), if the email
// is free, the Option will be undefined (no value within it)
if (option.isDefined()) {
return redirect("/register");
} else {
return redirect("/register");
}
}); // ERROR END
});
}
The findByUsername and findByEmail methods (in the User class):
public static F.Promise<User> findByUsername(String username) {
return bucket.get(username.toLowerCase(), User.class);
}
public static F.Promise<F.Option<User>> findByEmail(String email) {
return bucket.find("users", "by_email", QueryHelper.findOne(email), User.class).map(result ->{
if (result.isEmpty()) {
return F.Option.None();
} else {
User user = result.iterator().next();
return F.Option.Some(user);
}
});
}
Edit: I have added comments to the register method and removed some unneeded code. I have also labeled where the error starts and ends (the lines that IntelliJ is highlighting. The exact error is "bad return type in lambda expression". The register method needs to return a Promise<Result> but that block of code is returning a Promise<B> (generic?).
Javadocs:
http://www.playframework.com/documentation/2.3.x/api/java/play/libs/F.Promise.html
http://www.playframework.com/documentation/2.3.x/api/java/play/libs/F.Option.html
Related
I have a DTO class like this :
public class User {
#Field("id")
private String id;
private String userName;
private String emailId;
}
I have to provide an update and delete feature through API.
I have written the following code to delete the record:
public Mono<String> userData(User body) {
repo.removeUserDetails(userObj).subscribe();
return Mono.just("Remove Successful");
}
RemoveUserDetails method is something like this :
public Mono<User> removeUserDetails(User userObj) {
return findByUsername(userObj.getUsername())
.flatMap(existingUser -> {
// logic to delete the data from database which working as expected
}).switchIfEmpty(
Mono.defer(() -> {
return Mono.error(new Exception("User Name " + userObj.getUsername() + " doesn't exist."));
})
);
}
The problem with this code is even if the user is not existing, it is not showing the Mono error I'm returning. In every case, this always returns "Remove Successful".
How can I change my service layer method so that it can return whatever is received by the repo method? I'm new to Reactor code, so unable to figure out how to write it.
Whenever you call subscribe, consider it an immediate red flag. Subscription is something that should be handled by the framework you're using (Webflux in this case.)
If you subscribe yourself, such as in this example:
public Mono<String> userData(User body) {
repo.removeUserDetails(userObj).subscribe();
return Mono.just("Remove Successful");
}
...then you've essentially created a "fire and forget" type subscription, where you have no way of knowing if that publisher completed successfully, if it caused an error, how long it took to complete, whether it completed at all, or whether it emitted an element. So in this case, you're saying "send a request to remove user details, forget you sent it, and then before waiting for any kind of result, always return 'Remove successful'." This is almost never what you want.
You could use something like:
public Mono<String> userData(User body) {
return repo.removeUserDetails(userObj)
.then(Mono.just("Remove Successful"));
}
...which is much better as it includes everything as part of the reactive chain. In this case, you'll either get an error signal, or you'll get "Remove Successful".
However, chances are you don't need that String to be returned at all - you just need to know if it's successful or not. The standard way of doing that (I just need to know that it's completed successfully or not, I don't need it to return a value) is to use Mono<Void> as the return type and then(), something like:
public Mono<Void> userData(User body) {
return repo.removeUserDetails(userObj).then();
}
...which will give you a standard completion if the deletion was successful, and an error signal otherwise.
A common pattern you find when using reactive java code is handling nulls when collecting a list.
The following code is a simple example showing how to handle nulls returned by a Location by wrapping getLocation in a Mono.defer then handling a null using onErrorReturn.
The test code
List<String> items = inventory.testList().block();
items.forEach(System.out::println);
USA
Not Found
SPAIN
private List<Integer> clusters;
private List<Mono<Location>> locations;
private List<String> countryCodes;
public Mono<List<String>> testList() {
clusters = Arrays.asList(0, 1, 2);
locations = Arrays.asList(Mono.just(new Location(0)), null, Mono.just(new Location(2)));
countryCodes = Arrays.asList("USA", "FRANCE", "SPAIN");
return Flux.fromIterable(clusters)
.flatMap(cluster -> getLocation(cluster))
.collectList();
}
public Mono<String> getLocation(int clusterID) {
return Mono.defer(() -> locations.get(clusterID))
.flatMap(location -> Mono.just(location.id))
.flatMap(id -> Mono.just(countryCodes.get(id)))
.onErrorReturn(Exception.class, "Not Found");
}
I have my validate method in my TestValidator as follows
#Override
public void validate(Object target, Errors errors) {
Test test = (Test) target;
String testTitle = test.getTestTitle();
//**ErrorCheck1** - This works, and I am able to pull the value in my controller
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "testTitle", "test.testTitle.projec", "My msg");
if (testTitle != null && testTitle.length() < 4) {
logger.error("Inside custom validation"+test.testTitle().length());
//**Error Check2**
//***HOW DO I RETRIEVE THE BELOW VALUE in My controller
errors.rejectValue(testTitle, "test.testTitle.lessThen4");
errors.addAllErrors(errors);
logger.error("Entered If condition of validate");
}
}
And my controller is
#RequestMapping(value = "/test", method = RequestMethod.PUT)
public ResponseEntity<BasicResponseDTO> newTest(#Valid #RequestBody Test test, BindingResult result) {
if (result.hasErrors()){
logger.error("Entered Errors");
BasicResponseDTO basicResponseDTO = new BasicResponseDTO();
basicResponseDTO.setCode(ResponseCode.BAD_REQUEST);
return new ResponseEntity<BasicResponseDTO>(basicResponseDTO, HttpStatus.BAD_REQUEST);
}
}
When my ErrorCheck1 condition is activated, my IF condition inside the controller is able to retrieve it.
However, in my ErrorCheck2, because of of the errors.rejectValue I immediately get an error on the console and am not able to gracefully handle the situation when the testTitle length is less than 4.
What is the alternative to errors.rejectValue so that I may handle the
error in my controller ?
Ok - Got it. All i had to do was change
errors.rejectValue(testTitle, "test.testTitle.lessThen4");
to
errors.reject(testTitle, "test.testTitle.lessThen4");
RejectValue is a Field error and is not global in nature.
Reject is a Global error and can be accessed from inside the errors list in the controller.
From the Documentation
void reject(String errorCode, String defaultMessage);
Register a global error for the entire target object, using the given error description.
#Override
public void validate(Object target, Errors errors) {
Test test = (Test) target;
String testTitle = test.getTestTitle();
//**ErrorCheck1** - This works, and I am able to pull the value in my controller
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "testTitle", "test.testTitle.projec", "My msg");
if (testTitle != null && testTitle.length() < 4) {
logger.error("Inside custom validation"+test.testTitle().length());
//**Error Check2**
//***HOW DO I RETRIEVE THE BELOW VALUE in My controller
errors.reject(testTitle, "test.testTitle.lessThen4");
errors.addAllErrors(errors);
logger.error("Entered If condition of validate");
}
}
Hope that helps someone.
You cannot access the value directly but there is a way to include the value into error message
${validatedValue}
If you annotate a field like this
#Size(min = 10, message =”Phone number entered [${validatedValue}] is invalid. It must have at least {min} digits”)
private String phoneNumber;
and enter 123 your error message should be
Phone number entered [123] is invalid. It must have at least 10 digits. Thus you can access the value.
See https://raymondhlee.wordpress.com/2014/07/26/including-field-value-in-validation-message-using-spring-validation-framework-for-jsr-303/
I have implemented my own Authenticator from Play Framework and DeadboltHandler from Deadbolt.
Using the methods onUnauthorized respective onAuthFailure I can send users that are not logged in to the "login page" instead of the actual page they are trying to access.
However, instead of sending a user directly to the "login page", I want to specify what page the user should be sent to depending on which page the user tries to reach. For example, if the user tries to access /settings the user should be redirected to the login page. If the user tries to access /player/1 the user should be redirected to another page, say, "create user" page.
I was hoping that there is some smart way to do this with annotations, something like: #someannotation(redirect = route/id) so I can redirect to the relevant route if the user is not logged in, else to the standard "login page".
Any one got any ideas?
Code snippet example for controller and route method:
#Security.Authenticated(Secured.class)
#SubjectPresent(content = "createuser")
#DeferredDeadbolt
public class Settings extends Controller {
#SubjectPresent(content = "login")
#CustomRestrict(value = { #RoleGroup({ UserRole.player}), #RoleGroup(UserRole.server_owner) })
public static Result settings() {
Code snippet example for DeadboltHandler onAuthFailure:
#Override
public F.Promise<Result> onAuthFailure(Http.Context context, String content) {
return F.Promise.promise(new F.Function0<Result>() {
#Override
public Result apply() throws Throwable {
System.out.println(content);
There are a couple of different ways you can do this.
Approach 1: Repurpose the content value
In this approach, you can use the content value of the constraint annotations to give a hint to the handler. You can use a class-level constraint to define the default redirect, e.g. go to the login page, and method-level constraints to override the default redirect. All constraints have the content value, I'm just using SubjectPresent as an example; you can also mix constraints, e.g. have SubjectPresent at the class level and Restrict at the method level.
#SubjectPresent(content = "login")
public class FooController extends Controller {
public Result settings() {
// ...
}
public Result somethingElse() {
// ...
}
#SubjectPresent(content = "create-user")
public Result viewUser() {
// ...
}
}
In your DeadboltHandler implementation, you would then need a test on the content:
public CompletionStage<Result> onAuthFailure(final Http.Context context,
final Optional<String> content) {
return CompletableFuture.supplyAsync(() -> content.map(redirectKey -> {
final Result result;
if ("login".equals(redirectKey)) {
result = [redirect to login action]
}
else if ("create-user".equals(redirectKey)) {
result = [redirect to create user action]
} else {
result = [redirect to default authorization failure action]
}
}).orElseGet(() -> [redirect to default authorization failure action]), executor);
}
Approach 2: Use the ROUTE_PATTERN tag
Instead of specifying keys in the constraint annotations, you can instead use the route specified in the request to determine the requested action.
public CompletionStage<Result> onAuthFailure(final Http.Context context,
final Optional<String> content) {
final String route = requestHeader.tags().get(Router.Tags.ROUTE_PATTERN);
// examine the route and work out what you want to do
}
I am new to the Java Play Framework and I'm trying to get the authentication to work. So I am following this tutorial: https://www.playframework.com/documentation/2.1.0/JavaGuide4
Here is my code:
public static Result authenticate()
{
Form<Login> loginForm = form(Login.class).bindFromRequest();
return ok(loginForm.toString());
}
public static class Login
{
public String email;
public String password;
public String validate()
{
return "VALIDATE "+email+password;
}
}
In the method autheticate() I can see the submitted values of the form, but the method validate() in the Login class does not see them (the variables are always null).. The output of loginForm.toString() contains:
Form(of=class controllers.Application$Login, data={email=asdf#asdf, password=asdf}, value=None, errors={=[ValidationError(,[VALIDATE nullnull],[])]})
As you can see, the data is received.. But in the validate method the data suddenly is equal to null. So how do I fix this?
You don't mention how you are calling validate() however I think this might do the trick, do something along the lines of:
public static Result authenticate() {
Form<Login> form = form(Login.class).bindFromRequest();
// handle errors
if (!form.hasErrors()) {
Login login = form.get();
Logger.debug(login.validate());
} else {
// bad request
}
}
This works for me.
Method validate in your model should return null if you think that validation has passed, otherwise you should return error message text. Then you need to check form if it contains error by "hasGlobalError" method. globalError is filled when validate() method returns String instead of null. But in your case you should use some model field annotations - https://www.playframework.com/documentation/2.3.x/api/java/play/data/validation/Constraints.html.
If you want to check if form fails on those - then you use "hasErrors" method.
public static class Login {
#Constraints.Email
public String email;
#Constraints.MinLength(value = 6)
public String password;
}
Such model will check if provided emails is really email and if password is longer or equal 6 characters.
ps. Do not use toString on template, you should use render()
I have a Struts 2 textfield tag where I just need to get a user enter value and send to the action.
<s:textfield name="user.firstAnswer" size="110" cssClass="FormObjectCompulsary" autocomplete="off" />
Even when this page loads user object contains value for first answer, I don't want to display it in the text field instead I want the text field to be blank.
But with out specify the value attribute still the value in user object shows in this field.
If you are adding a new object user, then you should create this object with new operator before you show it in the JSP. It will contain null references that are not displayed. If the value attribute is not specified, then name is used to show the value.
Make your user object null after inside the execute(). So again it will not show value inside text box.
eg. user = null;
I am showing you piece of code, may be it will help you.
See the execute().
package online.solution;
import com.opensymphony.xwork2.Action;
public class MyAction implements Action {
UserBean user = new UserBean();
public UserBean getUser() {
return user;
}
public void setUser(UserBean user) {
this.user = user;
}
#SuppressWarnings("finally")
#Override
public String execute() throws Exception {
String result = "";
try {
user.setGuest("Bye bye");
System.out.println(user.getUsername() + " " + user.getPassword());
if (user.getUsername().equals(user.getPassword())) {
result = SUCCESS;
}
else {
result = ERROR;
}
user = null; //Make it null when all task completed.
}
catch (Exception exception) {
System.out.println("Exception -> " + exception);
}
finally {
return result;
}
}
#Override
protected void finalize() throws Throwable {
super.finalize();
}
}
By looking at name="user.firstAnswer" I am thinking that you are implementing ModelDriven<> to your action class. What might be happening is that when you return success in your action class and come to the jsp page, and if in action your user model had some values on it.. model driven will set those fields for your on your JSP page.
I have used this approach for update form functionality while learning struts2. Just make sure that user object contains nothing before you return...