Expose public field of POJO to FTL in Spring - spring

I can't figure out how to send a POJO to my template in Spring Boot.
Here's my POJO and my controller:
class DebugTest {
public String field = "Wooowee";
public String toString() {
return "testie " + field;
}
}
#Controller
#RequestMapping("/debug")
public class WebDebugController {
#RequestMapping(value = "/ftl", method = RequestMethod.GET)
public ModelAndView ftlTestPage(Model model) {
DebugTest test = new DebugTest();
ModelAndView mnv = new ModelAndView("debug");
mnv.addObject("test", test);
return mnv;
}
}
Here's my template:
HERES THE TEST: ${test}$
HERES THE TEST FIELD: ${test.field}$
Here's the output (GET /debug/ftl):
HERES THE TEST: testie Wooowee$
HERES THE TEST FIELD: FreeMarker template error (DEBUG mode; use RETHROW in production!):
The following has evaluated to null or missing:
==> test.field [in template "debug.ftl" at line 3, column 25]
[Java stack trace]

The class itself (DebugTest) must be public too, as per the JavaBeans Specification. Also, fields by default aren't exposed. Defining getter methods is generally the best (with Lombok maybe), but if you want to go with fields, configure the ObjectWrapper as such. As you are using Spring Boot, I think that will be something like this in your application.properites:
spring.freemarker.settings.objectWrapper=DefaultObjectWrapper(2.3.28, exposeFields = true)

Related

Spring boot configuration properties validation not working

Trying to implement configuration properties validation for immutable beans as described in Spring boot docs:
#Validated
#ConstructorBinding
I'm using Spring boot 2.4.0.
Sample immutable properties class:
#Validated
#ConstructorBinding
#ConfigurationProperties("prefix")
public class Props {
private final String suffix;
public Props(#NotBlank String suffix) {
this.suffix = suffix;
}
public String getSuffix() {
return suffix;
}
#Override
public String toString() {
return "Props [suffix=" + suffix + "]";
}
}
For a test, I did this:
System.setProperty("prefix.suffix", "value");
and I get the correct binding, i.e.:
Props [suffix=value]
However, by making a typo in the property name (added 1 to the property name):
System.setProperty("prefix.suffix1", "value");
I get this:
Props [suffix=null]
I see validation activated in the logs:
HV000001: Hibernate Validator 6.1.6.Final
Form their docs, it is supposed to do constructor parameter validation:
As of Bean Validation 1.1, constraints can not only be applied to JavaBeans and their properties, but also to the parameters and return values of the methods and constructors of any Java type.
Why is #NotBlank (full import: import javax.validation.constraints.NotBlank;) not causing validation exceptions?

Spring Validation Errors for RequestParam

I want to pass org.springframework.validation.Errors to CodeValidator class.
But, since I am not using RequestBody/RequestPart/ModelAttribute, I cannot put Errors in method param after variable.
I use #RequestParam for code variable, and I want to validate that using CodeValidator class that implement org.springframework.validation.Validator.
Here is my code
#RequestMapping(value = "/check-code", method = RequestMethod.POST)
public ResponseEntity<Object> checkCode(#RequestParam("code") String code, Errors errors) {
codeValidator.validate(code, errors);
if(errors.hasErrors()) {
return ResponseEntity.badRequest().body("Errors");
}
return ResponseEntity.ok("");
}
and here error result for my code:
An Errors/BindingResult argument is expected to be declared immediately after the model attribute, the #RequestBody or the #RequestPart arguments to which they apply: public org.springframework.http.ResponseEntity com.example.myapp.controller.CodeController.checkCode(java.lang.String,org.springframework.validation.BindingResult)
what should I do to be able using CodeValidator with #RequestParam?
Updated:
Code for CodeValidator
#Service
public class CodeValidator implements Validator {
#Override
public void validate(Object target, Errors errors) {
String code = ((String) target);
if(code == null || code.isEmpty()) {
errors.rejectValue("code", "", "Please fill in Code.");
}
}
}
Did you create an annotation with your validator?
Otherwise take a look at a small example/tutorial for custom validating with spring: https://www.baeldung.com/spring-mvc-custom-validator
(edit) if you are using spring boot you might need add a MethodValidationPostProcessor bean to your spring config to enable custom valdation for the #requesParam

Custom serialization of single #RestController endpoint

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

Map #CookieValue, #RequestHeader etc. to POJO in Spring Controller

I have a bunch of params in my controller and want to map all of them to a separate POJO to keep readability. There is also a #CookieValue, #RequestHeader I need to evaluate and aim for a solution to also map them to that POJO. But how?
I saw a possible solution on a blog but it doesn't work, the variable stays null.
Controller:
#RequestMapping(path = MAPPING_LANGUAGE + "/category", produces = MediaType.TEXT_HTML_VALUE)
#ResponseBody
public String category(CategoryViewResolveModel model) {
doSomething();
}
And my POJO is this:
public class CategoryViewResolveModel {
private String pageLayoutCookieValue;
public CategoryViewResolveModel() {
}
public CategoryViewResolveModel(
#CookieValue(value = "SOME_COOKIE", required = false) String pageLayoutCookieValue) {
this.pageLayoutCookieValue = pageLayoutCookieValue;
}
... some other RequestParams, PathVariables etc.
}
According to the documentation it's not possible for #CookieValue and #RequestHeader.
This annotation is supported for annotated handler methods in Servlet
and Portlet environments.
Take a look at:
https://www.petrikainulainen.net/programming/spring-framework/spring-from-the-trenches-creating-a-custom-handlermethodargumentresolver/
instead of using getParameter to access request parameters you can use getHeader to retrieve the header value and so define your CategoryViewResolveModel just as you were requesting

Spring MVC Test with Hamcrest: How test size and value for a Generic Collection

I am working with:
Spring MVC
Spring Rest
Spring MVC Test
it for generate data in:
XML
JSON
HTML
I have this class:
#XmlRootElement(name="generic-collection")
public class GenericCollection<T> {
private Collection<T> collection;
public GenericCollection(){
}
public GenericCollection(Collection<T> collection){
this.collection = collection;
}
#XmlElement(name="item")
public Collection<T> getCollection() {
return collection;
}
public void setCollection(Collection<T> collection) {
this.collection = collection;
}
#Override
public String toString() {
StringBuilder builder = new StringBuilder();
for(Object object : collection){
builder.append("[");
builder.append(object.toString());
builder.append("]");
}
return builder.toString();
}
}
I need that wrap class for XML. It can be reused in peace for JSON.
The #Controller has (observe how the collection is created):
#RequestMapping(method=RequestMethod.GET, produces={MediaType.APPLICATION_XML_VALUE, MediaType.APPLICATION_JSON_UTF8_VALUE})
public class PersonaFindAllController {
private GenericCollection<Persona> personas;
public PersonaFindAllController(){
personas = new GenericCollection<>(PersonaFactory.crearPersonas());
}
The #RequestMapping for XML/JSON is
#RequestMapping(value={PersonaFindAllURLSupport.FINDALL})
public #ResponseBody GenericCollection<Persona> findAll(){
return personas;
}
Consider above Rest since it uses #ResponseBody
Through Spring MVC Test and Hamcrest
I can check the content for XML and JSON as follows respectively:
resultActions.andExpect(xpath("generic-collection").exists())
.andExpect(xpath("generic-collection").nodeCount(is(1)))
.andExpect(xpath("generic-collection/item").exists())
.andExpect(xpath("generic-collection/item").nodeCount(is(5)))
.andExpect(xpath("generic-collection/item[1]").exists())
.andExpect(xpath("generic-collection/item[1]/*").nodeCount(is(4)))
.andExpect(xpath("generic-collection/item[1]/id").exists())
.andExpect(xpath("generic-collection/item[1]/id").string(is("88")))
….
and
resultActions.andExpect(jsonPath('collection').exists())
.andExpect(jsonPath('collection').isArray())
.andExpect(jsonPath('collection',hasSize(is(5))))
.andExpect(jsonPath('collection[0]').exists())
.andExpect(jsonPath('collection[0].*', hasSize(is(4))))
.andExpect(jsonPath('collection[0].id').exists())
.andExpect(jsonPath('collection[0].id').value(is("88")))
….
My problem is with Spring MVC. In the same #Controller below the other #RequestMapping method:
#RequestMapping(value={PersonaFindAllURLSupport.FINDALL}, produces=MediaType.TEXT_HTML_VALUE)
public String findAll(Model model){
model.addAttribute(personas);
return "some view";
}
It returns a view name and use a Model. It how is common in Spring MVC
Thanks to Spring MVC Test print() method I can confirm the following:
ModelAndView:
View name = persona/findAll
View = null
Attribute = genericCollection
value = [Persona [id=88, nombre=Manuel, apellido=Jordan, fecha=Mon Jul 06 00:00:00 PET 1981]][Persona [id=87, nombre=Leonardo, apellido=Jordan, fecha=Sun Jul 05 00:00:00 PET 1981]]...]
errors = []
See carefully:
the value data
remember the GenericCollection<T>'s toString() method.
For testing I have:
resultActions.andExpect(model().attribute("genericCollection", notNullValue()))
Until there works. Therefore some data and not null has been returned.
How I can check the size and data?
I have tried for the size:
.andExpect(model().attribute("genericCollection", hasSize(5)))
And I get
java.lang.AssertionError: Model attribute 'genericCollection'
Expected: a collection with size <5>
but: was <[Persona [id=88, nombre=Manuel, apellido=Jordan, fecha=Mon Jul 06 00:00:00 PET 1981]….]
If I use
.andExpect(model().attribute("genericCollection", hasItem("collection")))
I always
java.lang.AssertionError: Model attribute 'genericCollection'
Expected: a collection containing "collection"
but: was <[Persona [id=88, nombre=Manuel, apellido=Jordan, fecha=Mon Jul 06 00:00:00 PET 1981]]
So what is the correct syntax.
Because you try to write assertion for a Collection that is wrapped inside the GenericConnection class, you need to first get a reference to the actual Collection before you can write an assertion for it. This should should do the trick:
.andExpect(model().attribute("genericCollection",
hasProperty("collection", hasSize(5))
))
To check the content is in the following way:
.andExpect(model().attribute("genericCollection",
hasProperty("collection",
hasItem(
allOf(
hasProperty("id", is("100")),
hasProperty("nombre", is("Jesús")),
hasProperty("apellido", is("Mão"))
)
)
)
)
)

Resources