I'm trying to inject an model attribute using the annotation #ModelAttribute on method argument.
#RequestMapping({"/", "/index"})
public String home(Principal principal, Model model, #ModelAttribute("commerceId") Long commerceId) {
if (commerceId == null) {
LOGGER.info("Initializing commerce code...");
String name = principal.getName();
commerceId = Long.parseLong(name);
model.addAttribute("commerceId", commerceId);
}
return "index";
}
But always I get the next exception:
java.lang.NoSuchMethodException: java.lang.Long.<init>()
I can see that spring is trying to create the Long value using a no-arg constructor, but obviously It will fail because Long type doesn't have no-arg constructor.
Why Spring is unable to create correctly a Long type with #ModelAttribute ?
Can any Wrapper type (Integer, Long, etc...) be injected with #ModelAttribute ?
I'm using Spring 3.1.1
eg:
#ModelAttribute("peson") Person person
First it tries to crate a Person object with no-arg constructor. Then it calls the setter methods to set/populate the values for the respective properties.
In case of
#ModelAttribute("commerceId") Long commerceId
There is no no-arg constructor, and no setter method to set the value. Primitive Wrapper class provides arg-base constructor, also no setter method. Because once you initialize it, it does not allow you to change the value.
Try with #RequestParam("commerceId") Long commerceId
As suggested by #ParagFlume, I could change the argument type to String, but that would force me to do a casting to Long in all method using the #ModelAttribute("commerceId").
My solution was create a Wrapper object containing all concerning commerce.
public class CommerceData {
private Long commerceId;
public Long getCommerceId() {
return commerceId;
}
public void setCommerceId(Long commerceId) {
this.commerceId = commerceId;
}
}
When user has logged, I create my POJO CommerceData and then I set the Long value as attribute. Since I need the model attribute lives in session, I had create manually ant not being injected with #ModelAttribute annotation, because Spring MVC claimed the value doesn't exist.
#Controller
#SessionAttributes("data")
public class IndexController {
private static final Logger LOGGER = LoggerFactory.getLogger(IndexController.class);
#RequestMapping({"/", "/index"})
public String home(Principal principal, ModelMap model) {
if (model.get("data") == null) {
LOGGER.info("Inicializando código de comercio");
CommerceData data = new CommerceData();
String name = principal.getName();
data.setCommerceId(Long.parseLong(name));
model.addAttribute("data", data);
}
return "index";
}
}
But I believe that Spring MVC should support inject Wrappers values (Long, Integer, Double) using #ModelAttribute and not only POJO classes. Maybe I wrong but I could not do work with #ModelAttribute("commerceId") Long commerceId.
primitives work better with #modelattribute in spring .
i also faced issue with Long type but when used long then issue is resolved.
Related
I have an annotation set over objects of type dto, the same as over objects of type Entity. The annotation works on entities, but it does not work on objects of type dto.
I work in SpringBoot.
application.properties
validate.packageid.size = "The field 'PACKAGEID' can contain only {max} symbols.";
config file
#Configuration
public class ServiceConfig implements WebMvcConfigurer {
#Bean
public MessageSource messageSource() {
ResourceBundleMessageSource source = new ResourceBundleMessageSource();
source.setDefaultEncoding("UTF-8");
source.setBasename("classpath:ValidationMessages");
return source;
}
#Nullable
#Override
public Validator getValidator() {
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.setValidationMessageSource(messageSource());
return validator;
}
}
dto
#Size(message = "{validate.packageid.size}", max = 36)
private String documentId
entity
#Column(name = "DOCUMENTID")
#Size(message = "{validate.packageid.size}", max = 36)
private String documentId;
I cannot use the annotation #Valid because I fill an object of dto type with reflection technology.
public static <S> S fillData (S object, List<Object> values){
return obtainMetadataOfObject(object, values);
}
I need to be able to get the constraint annotation, or rather its parameters, set on the fields of the dto object (but in the case of the dto object I get null, since Spring may not know what to use the constraint annotations set on the fields of the dto object), in the case of entity - it turns out, but the entity validator is engaged by Spritg, since it manages the entity as a component from the Application context.
To validate the dto on the web client side, I use the #Valid annotation in the parameters of the method that handles the request from the client
For validation dto from
Update
I put the annotation #Validation over dto and after that I got the data I need.
It work for classes that don't have classes-heir.
I get data of annotation #Size
private static int getMaxLimitSize(Field field){
field.setAccessible(true);
Size annotation = field.getAnnotation(Size.class);
int zero = 0;
if(annotation == null) return zero;
return annotation.max();
}
But this does not work for objects whose fields are split into several classes : several abstract and one produce.
Validation does not work for composite objects of type DTO, Any help is appreciated?
The validation needs to be triggered somewhere, for entities in your case the spring framework does it (or jpa perhaps). DTOs never make it there. So you have to trigger the validation (validator.validate) on your own, as per the documentation. Here's a related question asking at which application layer to do it.
#PostMapping("/test")
public void test( #RequestBody #Valid final UserDto dto) {
// logic
}
u should add #Valid annotation in controller.
If u want validate dto in service layers u should add #Validate and #Valid :
#Service
#Validated
public class Service {
public void test(#Valid UserDto dto){
// logic
}
}
So let's say I have a simple entity defined as such:
#Entity
public class Person implements Serializable {
#Id
#GeneratedValue
private Long id;
private String fieldOne;
private String fieldTwo;
//...
private String fieldN;
}
Let's consider a simple controller for an endpoint which handles updating Person's, but only updating fields passed in that aren't empty/blank:
#Controller
#RequestMapping(value = "/api/person")
public class PersonController {
#Autowired
PersonRepository personRepository;
#RequestMapping(value = "/update", method = RequestMethod.PUT)
public void updatePerson(#RequestParam("personId") Long personId,
#RequestParam("fieldOne") String fieldOne,
#RequestParam("fieldTwo") String fieldTwo,
//...
#RequestParam("fieldN") String fieldN) {
Person toUpdate = personRepository.findOne(personId);
if(fieldOne != null && !fieldOne.isEmpty())
toUpdate.setFieldOne(fieldOne);
if(fieldTwo != null && !fieldTwo.isEmpty())
toUpdate.setFieldTwo(fieldTwo);
//...
if(fieldN != null && !fieldN.isEmpty())
toUpdate.setFieldN(fieldN);
personRepository.save(toUpdate);
}
}
Is there a more efficient approach for such updating of an instance of an entity? I thought about using the DTO and #RequestBody approach (i.e. updatePerson(#Valid #RequestBody PersonDTO personDTO) but this does more or less the same thing.
My main concern is that regardless of what approach I use, I obviously do not want to receive a null/empty string and set some field of an entity to be blank, but then I want to do this as efficiently as possible (efficiency in terms of code readability/portability and of course, runtime) and I'm not entirely sure if having hundreds of if-statements similar to what I have above is the most efficient option.
The #RequestParam actually has another argument which is defaultValue, you can use this so the argument won't never be empty & so you can reduce the checking againts empty & null values
#RequestParam(value="field" , defaultValue="****")
by using that you have already eliminated if(fieldOne != null && !fieldOne.isEmpty()) and calling toUpdate.setFieldOne(...)that wrapping the fields directly. Using defaultValue automatically sets required to false, and inserts a default value into your input argument when the request parameter is missing from your URL.
I have a class that is already annotated with various constraints:
public class SomeBean
{
#NotNull
public String name;
public String description;
}
I have a resource that accepts a HashMap of SomeBean:
public class SomeBeans extends LinkedHashMap<String, SomeBean>
{
}
When I try to pass in #Valid final SomeBeans, it does not validate each individual SomeBean. For example, they can send in a SomeBean with a null name:
#POST
#Consumes( MeadiaType.APPLICATION_JSON )
public SomeBeans makeSomeBeans( #Valid final SomeBeans beans )
{
// beans is not validated!
}
Do I need to write a custom validator for SomeBeans or is this supported already?
Thanks in advance!
Unfortunately, there is no collection(or map in this case) validation support like that. Either you need to write custom validators or use this. Not sure if it supports maps yet though.
I have a Spring managed bean...
#Component("Foobean")
#Scope("prototype")
public class foobean {
private String bar1;
private String bar2;
public String getBar1() {
return bar1;
}
public void setBar1(String bar1) {
this.bar1 = bar1;
}
public String getBar2() {
return bar2;
}
public void setBar2(String bar2) {
this.bar2 = bar2;
}
}
...and because I am using Dojo Dgrid to display an ArrayList of this bean, I am returning it into the controller as a JSON string:
#Controller
#RequestMapping("/bo")
public class FooController {
#Autowired
private FooService fooService
#RequestMapping("action=getListOfFoos*")
#ResponseBody
public String clickDisplayFoos(
Map<String, Object> model) {
List<Foobean> foobeans = fooService.getFoobeans();
ObjectMapper objMapper = new ObjectMapper();
String FooJson = null;
try {
FooJson = objMapper.writeValueAsString(foobeans);
} catch (JsonGenerationException e) {
etc.
}
However, my grid needs an additional column which will contain a valid action for each Foo; that action is not really dependent on any data in individual Foos -- they'll all have the same valid action -- repeated on each line of the resulting DGrid -- but that value is actually dependent upon security roles on the session...which can't be sent to the front end in a Json. So, my solution is twofold:
First I need to add a "virtual" Json property to the bean... which I can do in the bean with #JsonProperty on a method...
#JsonProperty("validActions")
public String writeValidActions {
return "placeHolderForSerializerToChange";
}
...but it just generates a placeholder. To really generate a valid value,
I need to reference the security role of the session,
which I am very reluctant to code in the above method. (A service call in
the domain bean itself? Seems very wrong.) I
think I should create a custom serializer and put the logic -- and the reference
to the Session.Security role in there. Are my instincts right, not to
inject session info into a domain bean method? And if so, what would such a
custom serializer look like?
Yes, I wouldn't put Session Info in to the domain or access session directly in my domain.
Unless there is a specific reason, you could simply add the logic in your action class.
public String clickDisplayFoos(){
List<Foo> foos = service.getFoos();
for(iterate through foos){
foo.setValidAction(session.hasSecurityRole())
}
String json = objMapper.writeValueAsString(foobeans);
return json;
}
I don't like the idea of setting new values as part of the serialization process. I feel custom serializers are meant to transform the representation of a particular property rather than add new values to a property.
This is how it all looks now:
#SessionAttributes("shoppingCart")
public class ItemController {
#ModelAttribute
public ShoppingCart createShoppingCart() {
return new ShoppingCart();
}
#RequestMapping(value=RequestMappings.ADD_TO_CART + RequestMappings.PARAM_ITEM_ID, method=RequestMethod.GET)
public String addToCart(#PathVariable("itemId") Item item, #ModelAttribute ShoppingCart shoppingCart) {
if(item != null) {
shoppingCartService.addItem(shoppingCart, item);
}
return ViewNamesHolder.SHOPPING_CART;
}
}
When the addToCart method is called first time, the shoppingCart object will be initialized by the createShoppingCart method. After the addToCart method runs, the initialized object will be added to the session and it will be used from the session for the later use. That means the createShoppingCart methode is called just once (as long as it does not removed from the session).
Why does Spring eliminate the need for the ModelAttribute annotated initializer method, by simply creating this object whenever is needed? Then it would all look simpler like this:
#SessionAttributes("shoppingCart")
public class ItemController {
#RequestMapping(value=RequestMappings.ADD_TO_CART + RequestMappings.PARAM_ITEM_ID, method=RequestMethod.GET)
public String addToCart(#PathVariable("itemId") Item item, #ModelAttribute ShoppingCart shoppingCart) {
if(item != null) {
shoppingCartService.addItem(shoppingCart, item);
}
return ViewNamesHolder.SHOPPING_CART;
}
}
Whenever the shoppingCart object will not be found in the session, it would be initialized by its default constructor..
What do you think the reason is for that decision?
I can't speak directly for the Spring team, but your suggestion would limit the desired ModelAttribute to a newly created instance on each request (prior to being stored in the session,) but what if you wanted to start with a fully populated object, say, fetched from a datastore? Your method offers no way to do that. This, however, works well:
#ModelAttribute
public ShoppingCart createShoppingCart() {
...
return someShoppingCartRepo.find(...);
}
This, of course, is just one possible scenario where the usefulness of a separate method should be evident.
EDIT AFTER COMMENTS
You could easily create your own HandlerMethodArgumentResolver that would give you a new instance of your object if none existed, but it might be overkill considering how easy it is to use your createShoppingCart() method. If you are using xml configs, it would be something like this:
<mvc:annotation-driven ...>
<mvc:argument-resolvers>
<bean class="yourpackage.YourCustomArgumentResolver" />
</mvc:argument-resolvers>
</mvc:annotation-driven>
You could extend any number of existing HandlerMethodArgumentResolver base classes, or you could implement the interface directly yourself, but most likely you would use something like this:
public class YourCustomArgumentResolver extends AbstractNamedValueMethodArgumentResolver {
// Implement/override methods for creating your model object when encountered as a method argument
}
To identify your argument, you could create a custom annotation:
#Target(ElementType.PARAMETER)
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface YourAutoCreateModelAttribute {
String value() default "";
boolean required() default true;
String defaultValue() default ValueConstants.DEFAULT_NONE;
}
Then annotate your method like this to kick off your custom resolver:
#RequestMapping(...)
public String doStuff(#YourAutoCreateModelAttribute ShoppingCart shoppingCart, ...) {
// Now your shoppingCart will be created auto-magically if it does not exist (as long as you wrote your resolver correctly, of course.
}