I have a controller that accepts a dto object. I need to change the fields that are present in the dto object.
#PatchMapping(value = "/update/{uuid}")
public ResponseEntity<UserDto> update(
#RequestBody UserDto userDto,
#PathVariable("uuid")UUID uuid) throws UserNotFoundException {
User updatedUser = userService.update(
userMapper.userDtoToUser(userDto),
uuid
);
return .....
}
But a userService can only accept entities. I need to use mapper dto -> entity. But the entity cannot have empty fields that come in dto (Let's say that you need to change only one field). What to do in this situation? I know that the controller should not contain logic
Two possible ways to resolve this problem. You have to either change the service method to accept the dto and not the entity or you have to create a #Component class which implements Converter, override the convert method and do the necessary field changes there and then #Autowire GenericConversionService in your controller and call genericConversionService.convert(userDto, User.class);
The converter should look like this:
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
#Component
public class UserDtoToUser implements Converter<UserDto, User> {
#Override
public User convert(UserDto source) {
User user = new User();
// user.set ..... for all necessary fields
return user;
}
}
EDIT
In case you want to check the validity of the fields you're receiving you can simply use the following annotations to ensure your data is correct: #NotBlank - this checks if a field is null or an empty string, #NotNull - this checks if a field is null, #NotEmpty - this checks if a field is null or an empty collection. Important to remember - you must add the #Valid annotation before the object you want to validate (side note - in case of nested objects you also need to add it to your object fields) so it looks like this #RequestBody #Valid UserDto userDto,
And then for the dto it should look something like this:
import java.util.List;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
public class UserDto {
#NotNull
private Integer id;
#NotBlank
private String username;
#NotBlank
private String password;
#NotEmpty
private List<String> roles;
}
Change to fields to whatever is in your dto of course. Also in case you need to do more validations there are a number of other validation annotations you can add.
You could use reflection to check the null properties and BeanUtils for the copying
In Spring that would be the way that I'd to check the empty properties
public static String[] getNullPropertyNames (Object source) {
final BeanWrapper src = new BeanWrapperImpl(source);
PropertyDescriptor[] pds = src.getPropertyDescriptors();
Set<String> emptyNames = new HashSet<>();
for(PropertyDescriptor pd : pds) {
Object srcValue = src.getPropertyValue(pd.getName());
if (srcValue == null) emptyNames.add(pd.getName());
}
return emptyNames.toArray(new String[0]);
}
And then for the copying
User updatedUser = new User();
BeanUtils.copyProperties(userDto, updatedUser, getNullPropertyNames(userDto));
As you say the controller should not contain logic, so for this Spring has an interface, the interface is Validator
There are pros and cons for considering validation as business logic, and Spring offers a design for validation (and data binding) that does not exclude either one of them. Specifically validation should not be tied to the web tier, should be easy to localize and it should be possible to plug in any validator available. Considering the above, Spring has come up with a Validator interface that is both basic ands eminently usable in every layer of an application.
This is what you need to do:
Spring features a Validator interface that you can use to validate objects. The Validator interface works using an Errors object so that while validating, validators can report validation failures to the Errors object.
we have a DTO to which we will validate the fields:
public class Person {
private String name;
private int age;
// the usual getters and setters...
}
To do validations we must implement Validator interface:
public class PersonValidator implements Validator {
/**
* This Validator validates *just* Person instances
*/
public boolean supports(Class clazz) {
return Person.class.equals(clazz);
}
public void validate(Object obj, Errors e) {
if (supports(obj.getClass())) {
ValidationUtils.rejectIfEmpty(e, "name", "name.empty");
Person p = (Person) obj;
if (p.getAge() < 0) {
e.rejectValue("age", "negativevalue");
} else if (p.getAge() > 110) {
e.rejectValue("age", "too.darn.old");
}
}
}
}
If the DTO passes all validations you can map the DTO to an Entity object
You can find more information here Spring Validator
Related
I am have been trying to solve this challenge for a while now. I am a JavaScript developer but have been trying out Spring Boot and Java and am running into an issue. In the below code I need to figure out how to write the #RestController logic to return JSON from the list of users created below in the User class. I am not sure how to go about doing that since this is all new to me.
package usersExample;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
#RestController
public class UsersController {
/* Please insert your code here to make the tests pass */
}
#Service
class UserService {
public List<User> all() {
List<User> users = new ArrayList<User>();
users.add(new User("Bob"));
users.add(new User("Christina"));
users.add(new User("Steve"));
return users;
}
}
class User {
private final String name;
public User(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
Since you're using #RestController, the return value is already a JSON (spring-boot does the conversion without adding additional dependencies because it already include the Jackson library).
You can simply create a method in your controller which will return a List of users. One comment about your code: use proper method names like findAll() instead of all().
#GetMapping("/find-all")
public List<User> findAll()
{
return userService.findAll();
}
P.S.: And in case that the code you've added is the same with the one you're running, I suggest you to create separate classes for User and UserService.
EDIT: The conversion of a simple object into a JSON object is done using getters. How is it working? Let's take your example:
class User {
private final String name;
public String getName() {
return name;
}
}
The converter will look for methods starting with the get keyword and set everything after that as an attribute name with first letter converted to lower case, this means that the ONLY attribute (because you have only 1 getter) will look like this: name : -value here-.
Now you may be asking some questions like :
1)
Q: What happens if you change your getName() method to getname() ?
A: Will work the same
2)
Q: What happens if you don't provide a getter to a field?
A: The field won't be converted to a JSON property
3)
Q: What happens if you change getName() method to getMyName() ?
A: The attribute name will be myName: -value-
So, if these have been said, if you want to change your JSON structure, there are many solutions:
1) Change the getter names (not really recommended)
2) Use the #JsonProperty annotation. E.g.:
private String name;
#JsonProperty("name")
public String thismethodwontworkwithoutjsonproperty() {
return this.name;
}
3) Create a DTO object UserDTO and return this instead of User. And in your controller you can convert the user to an userDTO object.
public class UserDTO {
private String fullName;
//getter; setter;
}
and
public class User {
private String firstName;
private String lastName;
//getter;setter;
}
And eventually, you make the conversion like this:
#GetMapping("/getUser")
public User returnUser() {
User user = new User();
user.setFirstName("Stack");
user.setLastName("Overflow");
UserDto userDto = convertUser(user);
return userDto;
}
private UserDTO convertUser(User user) {
UserDTO userDTO = new UserDTO();
userDTO.setFullName(user.getFirstName() + " " + user.getLastName());
return userDTO;
}
But I would suggest you to structure your application and add a facade layer which will make this conversion instead of creating convertUser() method in the controller.
Output:
{
"fullName":"Stack Overflow";
}
I have a controller which produces JSON, and from this controller, I return an entity object, which is automatically serialized by Jackson.
Now, I want to avoid returning some fields based on a parameter passed to the controller. I looked at examples where this is done using FilterProperties / Mixins etc. But all the examples I saw requires me to use ObjectMapper to serialize / de-serialize the bean manually. Is there any way to do this without manual serialization? The code I have is similar to this:
#RestController
#RequestMapping(value = "/myapi", produces = MediaType.APPLICATION_JSON_VALUE)
public class MyController {
#Autowired
private MyService myService;
#RequestMapping(value = "/test/{variable}",method=RequestMethod.GET)
public MyEntity getMyEntity(#PathVariable("variable") String variable){
return myservice.getEntity(variable);
}
}
#Service("myservice")
public class MyService {
#Autowired
private MyEntityRepository myEntityRepository;
public MyEntity getEntity(String variable){
return myEntityRepository.findOne(1L);
}
}
#Entity
#Table(name="my_table")
#JsonIgnoreProperties(ignoreUnknown = true)
public class MyEntity implements Serializable {
#Column(name="col_1")
#JsonProperty("col_1")
private String col1;
#Column(name="col_2")
#JsonProperty("col_2")
private String col2;
// getter and setters
}
Now, based on the value of "variable" passed to the controller, I want to show/hide col2 of MyEntity. And I do not want to serialize/deserialize the class manually. Is there any way to do this? Can I externally change the Mapper Jackson uses to serialize the class based on the value of "variable"?
Use JsonView in conjunction with MappingJacksonValue.
Consider following example:
class Person {
public static class Full {
}
public static class OnlyName {
}
#JsonView({OnlyName.class, Full.class})
private String name;
#JsonView(Full.class)
private int age;
// constructor, getters ...
}
and then in Spring MVC controller:
#RequestMapping("/")
MappingJacksonValue person(#RequestParam String view) {
MappingJacksonValue value = new MappingJacksonValue(new Person("John Doe", 44));
value.setSerializationView("onlyName".equals(view) ? Person.OnlyName.class : Person.Full.class);
return value;
}
Use this annotation and set the value to null, it will not be serialised:
#JsonInclude(Include.NON_NULL)
I am using Spring web services REST API that gives the JSON response.
The API usage:
#ResponseBody
#RequestMapping(value="/user", method = {RequestMethod.POST})
Details user(#RequestParam("username")
String username, #RequestParam("password") String password)
The JSON coming is:
{
"result":{
"details" : {
"firstName":"My",
"lastName":"God",
"enabled": false,
"id":927878192,
"language":"en_US",
}
}
}
I am having a Details class with the getter and setter methods for firstName, lastName, enabled, id and language.
The class is annotated with #JsonIgnoreProperties(ignoreUnknown = true).
However I don't want to show language and enabled in JSON response.
So in my java code, I did the following for language:
details.setLanguage(null);
That worked fine.
But I can't do details.setEnabled(null) because the enabled variable is primitive that can take true or false but not null. So my JSON response always has "enabled": false.
What can be done so that this field will not be a part of JSON response.
Try to use #JsonIgnore annotation on field level.
For example:
#JsonIgnore
private boolean isActive;
If you want to ignore the property only for selected response I suggest using #JsonView from com.fasterxml.jackson.annotation package but it could be an overkill.
First you need to create a class with an interface inside:
public class View {
public interface UserDetailed {}
}
After that you specify in your class which field should be visible only for specific 'profile':
public class User {
// Other fields
#JsonView(View.UserDetailed.class)
private List<Role> roleCollection = new ArrayList<Role>();
// Other fields, getters and setters
}
Then on the controller's method that needs to display that property you do:
#Controller
public class UserController{
#RequestMapping(value = "/userWithRoles", method = RequestMethod.GET)
#JsonView(View.UserDetailed.class)
public User getUserWithRoles() {…}
#RequestMapping(value = "/userWithoutRoles", method = RequestMethod.GET)
public User getUserWithoutRoles() {…}
}
The result is: only the controller methods that have the same #JsonView as the field will display it. Other will ignore it. It allows you to manage the visibility of the fields depending on the use case.
Here you can read more about it:
http://wiki.fasterxml.com/JacksonJsonViews
Put #JsonIgnore on the enabled getter method.
If you want to ignore it only during serialization but you need it when deserializing (if you need to edit that field from some method of the REST API), you can annotate with #JsonSetter("enabled") the enabled setter method.
Example:
public class Details {
....
private boolean enabled;
...
#JsonIgnore
public boolean isEnabled()
#JsonSetter
public void setEnabled(boolean enabled)
}
If you for some API method need enabled and for others no, the cleanest way to do so is to have two DTOs:
public class DetailsBasic {
private String firstName;
private String lastName;
private long id;
// getters and setters
}
public class Details extends DetailsBasic {
private boolean enabled;
private boolean language;
// getters and setters
}
And then in your controller:
#ResponseBody
#RequestMapping(value="/user", method = {RequestMethod.POST})
DetailsBasic user(#RequestParam("username")
String username, #RequestParam("password") String password) {
return ...
}
#ResponseBody
#RequestMapping(value="/otherMethod", method = {RequestMethod.POST})
Details otherMethod(#RequestParam("username")
String username, #RequestParam("password") String password) {
return ...
}
How can I change validation rules based on which class given bean is enclosed in?
Example:
public class ParentA {
#Valid
private Child c;
}
public class ParentB {
#Valid
private Child c;
}
public class Child {
#NotNull // when in ParentA
#Null // when in ParentB
private String name;
}
There are validation groups, however, I do not know how to apply them in this case. Can I specify the following: if validating ParentA then apply GroupA for its fields, hopefully by some annotation and without instanceof? I really do not want to create two types ChildA and ChildB with different validation annotations. I am building REST service with spring 4. Thanks for any feedback.
Try this code
public class ParentA {
#Valid
#ConvertGroup(from=Default.class, to=ParentA.class)
private Child c;
}
public class ParentB {
#Valid
#ConvertGroup(from=Default.class, to=ParentB.class)
private Child c;
}
public class Child {
#NotNull(groups=ParentA.class)
#Null(groups=ParentB.class)
private String name;
}
You can find more info and other examples in hibernate validator reference guide
I am building a REST service platform in which we have to support following query pattern:
format=summary which means we have to deserialize only the POJO attributes annotated with our custom annotation #Summary
format=detail which means we have to deserialize only the POJO attributes annotated with our custom annotation #Detail
fields=prop1,prop2,prop3 which means we have to deserialize the POJO attributes provided in the query.
I am using Jackson 2 (v2.3.0) I tried followings:
Developed custom annotations (#Summary and #Detail)
Developed a JsonFilter (code shown below) and annotated #JsonFilter to my POJO classes.
Location.java
#JsonFilter("customFilter")
public class Location implements Serializable {
#Summary
#Detail
private String id;
#Summary
#Detail
private String name;
#Summary
#Detail
private Address address;
// ... getters n setters
Address.java
#JsonFilter("customFilter")
public class Address implements Serializable {
#Detail
private String addressLine1;
#Detail
private String addressLine2;
#Detail
private String addressLine3;
#Detail
#Summary
private String city;
#Summary
#Detail
private String postalCode;
// ... getters n setters
CustomFilter.java
public class CustomFilter extends SimpleBeanPropertyFilter {
#Override
protected boolean include(BeanPropertyWriter propertyWriter) {
if(logger.isDebugEnabled()) {
logger.debug("include(BeanPropertyWriter) method called..");
}
return this.deserialize(propertyWriter);
}
#Override
protected boolean include(PropertyWriter propertyWriter) {
if(logger.isDebugEnabled()) {
logger.debug("include(PropertyWriter) method called..");
}
return this.deserialize((BeanPropertyWriter) propertyWriter);
}
private boolean deserialize(final BeanPropertyWriter beanPropertyWriter) {
final String format = (String) AppContext.get("format");
if(StringUtils.isNotBlank(format)) {
return deserializeForAnnotation(format, beanPropertyWriter);
} else {
#SuppressWarnings("unchecked")
final Set<String> fieldNames = (Set<String>) AppContext.get("fieldNames");
if(null != fieldNames && !fieldNames.isEmpty()) {
final String serializedPropertyName = beanPropertyWriter.getSerializedName().getValue();
return fieldNames.contains(serializedPropertyName);
}
}
return false;
}
private boolean deserializeForAnnotation(final String format, final BeanPropertyWriter beanPropertyWriter) {
if(StringUtils.equalsIgnoreCase(format, "detail")) {
return (null != beanPropertyWriter.getAnnotation(Detail.class));
} else if(StringUtils.equalsIgnoreCase(format, "summary")) {
return (null != beanPropertyWriter.getAnnotation(Summary.class));
}
return false;
}
}
I am getting intended result with annotations, however my 3rd requirement to support property names to filter is not working.
Could someone help; if possible with some examples?
I wrote a library called Squiggly Filter, which selects fields based on a subset of the Facebook Graph API syntax. For example, to select the zipCode of the address field of the user object, you would use the query string ?fields=address{zipCode}. One of the advantages of Squiggly Filter is that as long as you have access to the ObjectMapper that renders the json, you do not to have to modify the code of any of your controller methods.
Assuming, you are using the servlet API, you can do the following:
1) Register a filter
<filter>
<filter-name>squigglyFilter</filter-name>
<filter-class>com.github.bohnman.squiggly.web.SquigglyRequestFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>squigglyFilter</filter-name>
<url-pattern>/**</url-pattern>
</filter-mapping>
2) Initialize the ObjectMapper
Squiggly.init(objectMapper, new RequestSquigglyContextProvider());
3) You can now filter your json
curl https://yourhost/path/to/endpoint?fields=field1,field2{nested1,nested2}
You can also select fields based on annotations as well.
More information on Squiggly Filter is available on github.
If you want to go down the route of having a custom object mappers for each set of fields then your best bet is to retain the created object mappers in a cache somewhere so that the next time a user requests the same fields the object mapper can be reused.
Your cache could be as simple as a Set<String,ObjectMapper>, with the key being the fields as passed in by the user.