Is there a way (preferably some type of annotation) to register a custom serializer for a single endpoint in a #RestController? Extending the bean and putting a #JsonSerialize on it would be an option, but that demands an otherwise pretty useless new bean class. I tried the following:
#JsonSerialize(using = CustomSerializer.class)
#RequestMapping(value = "/some_endpoint/", method = RequestMethod.GET)
public SomeType someEndpoint() {
return someObject;
}
But the #JsonSerialize annotation doesn't appear to have any meaning to Spring in that context. Is there an alternative or is the extra bean class my only option?
You can use #JsonView(View.Summary::class) in the attributes you want to add or ignore and in the method you want to apply that view, for example:
public class View {
public interface Summary
}
public class A{
#JsonView(View.Summary.class)
private String serialized = "",
private String notSerialized = ""}
and then in the controller:
#JsonView(View.Summary.class)
#GetMapping("/")
#ResponseBody
public A getA(){
return A()
}
If you want to reverse the JsonView (serialize the atributtes who doesnt have the view). you can add the following propertie: spring.jackson.mapper.default-view-inclusion=true
Related
I'm doing request parameter validation from a Spring controller. I have an Enum validator, similar to https://funofprograming.wordpress.com/2016/09/29/java-enum-validator/, which works fine if the enum field is directly in the object I'm using for validation. But it doesn't work if that object contains other objects.
For example, here is the request in the Controller
#PostMapping("/")
public ResponseEntity<?> performOperation(#Valid #RequestBody MyModel model) {
Here is the model I'm using to validate the request params
#ApiModel
public class MyModel {
#ApiModelProperty
#EnumValueValidator(enumClass = EnumName.class)
public String provider;
MyObject obj;
}
public class MyObject {
#EnumValueValidator(enumClass = SomeEnum.class)
public String anotherEnum;
}
In the above example, provider is validated with no problem. But anotherEnum is not. Is there a way for a Spring model to do a deep validation into objects?
You should annotate MyObject obj with #Valid annotation as well. Just keep in mind that null objects are not validated, so probably you should do both:
#NotNull
#Valid
MyObject obj;
I have created a custom controller which needs to convert entities to resources. I have annotated my repositories with #RepositoryRestResource annotation. I want to know if there is a way I can invoke the default functionality of spring Data REST from my custom controller which serializes the entities to resources with links to other entities embedded in them.
I don't want to return entities from my handler method but Resources.
Thanks.
Very simple, using objects Resource or Resources. For example - in this controller we add custom method which return list of all user roles which are enums:
#RepositoryRestController
#RequestMapping("/users/roles")
public class RoleController {
#GetMapping
public ResponseEntity<?> getAllRoles() {
List<Resource<User.Role>> content = new ArrayList<>();
content.addAll(Arrays.asList(
new Resource<>(User.Role.ROLE1),
new Resource<>(User.Role.ROLE2)));
return ResponseEntity.ok(new Resources<>(content));
}
}
To add links to resource you have to use object RepositoryEntityLinks, for example:
#RequiredArgsConstructor
#RepositoryRestController
#RequestMapping("/products")
public class ProductController {
#NonNull private final ProductRepo repo;
#NonNull private final RepositoryEntityLinks links;
#GetMapping("/{id}/dto")
public ResponseEntity<?> getDto(#PathVariable("id") Integer productId) {
ProductProjection dto = repo.getDto(productId);
return ResponseEntity.ok(toResource(dto));
}
private ResourceSupport toResource(ProductProjection projection) {
ProductDto dto = new ProductDto(projection.getProduct(), projection.getName());
Link productLink = links.linkForSingleResource(projection.getProduct()).withRel("product");
Link selfLink = links.linkForSingleResource(projection.getProduct()).slash("/dto").withSelfRel();
return new Resource<>(dto, productLink, selfLink);
}
}
For more example see my 'how-to' and sample project.
I have problems with save some values in #Service method.
My code:
#Service(value = "SettingsService")
public class SettingsService {
...
public String getGlobalSettingsValue(Settings setting) {
getTotalEhCacheSize();
if(!setting.getGlobal()){
throw new IllegalStateException(setting.name() + " is not global setting");
}
GlobalSettings globalSettings = globalSettingsRepository.findBySetting(setting);
if(globalSettings != null)
return globalSettings.getValue();
else
return getGlobalEnumValue(setting)
}
#Cacheable(value = "noTimeCache", key = "#setting.name()")
public String getGlobalEnumValue(Settings setting) {
return Settings.valueOf(setting.name()).getDefaultValue();
}
My repository class:
#Repository
public interface GlobalSettingsRepository extends CrudRepository<GlobalSettings, Settings> {
#Cacheable(value = "noTimeCache", key = "#setting.name()", unless="#result == null")
GlobalSettings findBySetting(Settings setting);
It should work like this:
get value form DB if data exist,
if not save value from enum.
but it didn't save any data from DB or enum.
My cache config:
#Configuration
#EnableCaching
public class CacheConfig {
#Bean
public EhCacheCacheManager cacheManager(CacheManager cm) {
return new EhCacheCacheManager(cm);
}
#Bean
public EhCacheManagerFactoryBean ehcache() {
EhCacheManagerFactoryBean ehCacheManagerFactoryBean = new EhCacheManagerFactoryBean();
ehCacheManagerFactoryBean.setConfigLocation(new ClassPathResource("ehcache.xml"));
return ehCacheManagerFactoryBean;
}
}
I have some example to make sure that cache is working in my project in rest method:
#RequestMapping(value = "/system/status", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> systemStatus() {
Object[] list = userPuzzleRepository.getAverageResponseByDateBetween(startDate, endDate);
...
}
public interface UserPuzzleRepository extends CrudRepository<UserPuzzle, Long> {
#Cacheable(value = "averageTimeAnswer", key = "#startDate")
#Query("select AVG(case when up.status='SUCCESS' OR up.status='FAILURE' OR up.status='TO_CHECK' then up.solvedTime else null end) from UserPuzzle up where up.solvedDate BETWEEN ?1 AND ?2")
Object[] getAverageResponseByDateBetween(Timestamp startDate, Timestamp endDate);
and it's work well.
What am I doing wwrong?
You have two methods in your SettingsService, one that is cached (getGlobalEnumValue(...)) and another one that isn't cached, but calls the other method (getGlobalSettingsValue(...)).
The way the Spring cache abstraction works however is by proxying your class (using Spring AOP). However, calls to methods within the same class will not call the proxied logic, but the direct business logic beneath. This means caching does not work if you're calling methods in the same bean.
So, if you're calling getGlobalSettingsValue(), it will not populate, nor use the cache when that method calls getGlobalEnumValue(...).
The possible solutions are:
Not calling another method in the same class when using proxies
Caching the other method as well
Using AspectJ rather than Spring AOP, which weaves the code directly into the byte code at compile time, rather than proxying the class. You can switch the mode by setting the #EnableCaching(mode = AdviceMode.ASPECTJ). However, you'll have to set up load time weaving as well.
Autowire the service into your service, and use that service rather than calling the method directly. By autowiring the service, you inject the proxy into your service.
The problem is in the place you call your cacheable method from. When you call your #Cacheable method from same class, you just call it from this reference, which means it doesn't wrapped by Spring's proxy, so Spring can't catch your invocation to handle it.
One on ways to solve this problem is to #Autowired service to itself and just call methods you expected spring have to handle by this reference:
#Service(value = "SettingsService")
public class SettingsService {
//...
#Autowired
private SettingsService settingsService;
//...
public String getGlobalSettingsValue(Settings setting) {
// ...
return settingsSerive.getGlobalEnumValue(setting)
//-----------------------^Look Here
}
#Cacheable(value = "noTimeCache", key = "#setting.name()")
public String getGlobalEnumValue(Settings setting) {
return Settings.valueOf(setting.name()).getDefaultValue();
}
}
But if you have such problems it means your classes are take on too much and aren't comply with the principle of "single class - single responsibility". The better solution would be to move method with #Cacheable to dedicated class.
I am pretty new in Spring MVC and I have the following problem.
Into an home() controller method defined into a class named **HomeController I retrieve an object using a service. This object have to be put as Session Attribute so it can be used from other methods in other controller classes.
So I have done in this way:
#Controller
#SessionAttributes({"progettoSelezionato"})
#PropertySource("classpath:messages.properties")
public class HomeController {
private static final Logger logger = Logger.getLogger(LoginController.class);
private #Autowired HomeService homeService;
#RequestMapping(value = "/home", method = RequestMethod.GET)
public String home(Model model) {
List<Tid023Intervento> listaInterventi = homeService.getListaInterventiRUP(18);
model.addAttribute("progettoSelezionato", listaInterventi.get(0));
model.addAttribute("listaProgettiRUP", listaInterventi);//TODO
return "home";
}
}
As you can see I have used this annotation on the class level:
#SessionAttributes({"progettoSelezionato"})
and then I put this object by:
model.addAttribute("progettoSelezionato", listaInterventi.get(0));
I am not sure that this is correct because, from what I know, this put the retrieved listaInterventi.get(0) object into the model with the voice progettoSelezionato. So I am absolutly not sure that it is putted as SessionAttributes.
Then, after that this object is putted as SessionAttriibute I have to retrieve and use if from a gestioneDatiContabiliEnte() method defined into another controller class, so I am doing in this way:
#Controller
#SessionAttributes({"progettoSelezionato"})
#PropertySource("classpath:messages.properties")
public class GestioneDatiContabiliEnteController {
private static final Logger logger = Logger.getLogger(LoginController.class);
#RequestMapping(value = "/gestioneDatiContabiliEnte", method = RequestMethod.GET)
public String gestioneDatiContabiliEnte(Model model) {
System.out.println("INTO gestioneDatiContabiliEnte()");
System.out.println("PROGETTO SELEZIONATO: " + progettoSelezionato);
return "gestioneDatiContabiliEnte/gestioneDatiContabiliEnte";
}
}
But it seems can't work because Eclipse sign me error on this line:
System.out.println("PROGETTO SELEZIONATO: " + progettoSelezionato);
The error is: progettoSelezionato cannot be resolved to a variable.
How can I correctly put the listaInterventi.get(0) as SessionAttribute into my HomeController? And how can I retrieve and use it into the gestioneDatiContabiliEnte() method defined into my GestioneDatiContabiliEnteController class?
Soled by myself, I can access to this object by:
model.asMap().get("progettoSelezionato");
I have UsersController with method:
#RequestMapping(value={"/new"}, method=RequestMethod.GET)
public String showCreationForm(#ModelAttribute User user){
return "user_registration_form";
}
which displays registration form. I want to keep modularity (would be nice to use this controller in some other project) in my project so User is an interface and there is its implementation - UserImpl. The problem is that Spring cannot instatiate User interface. Is there a way to configure spring to use some default implementation of User?
You can provide an object to be populated with request data using #ModelAttribute-annotated method:
#ModelAttribute
public User createUser() {
return new UserImpl();
}
Create a simple class that implements the interface minimally. It is the same idea as an interface, but it is a class. It does not contain any of your logic or validation or anything else. It is just the simplest implementation of the interface, call it UserSimple, and it implements your interface. It is called a Data Transfer Object.
public class UserSimple implements User {
String name;
String address;
//getters and setters only
}
Add a converter that copies the real properties of the UserImpl into the UserSimple.
#Component
public class ImplToSimpleConverter
implements Converter<UserImpl, UserSimple> {
#Override
public UserSimple convert(UserImpl source) {
UserSimple target = new UserSimple();
BeanUtils.copyProperties(source, target);
return target;
}
}
Use UserSimple in the handler.
#RequestMapping(value={"/new"}, method=RequestMethod.GET)
public String showCreationForm(#ModelAttribute UserSimple user){
return "user_registration_form";
}
This allows you to keep the code generic. Adding a different converter is all you would have to do to use the same class in a different application.