I'm using the Spring LDAP (docs) library in a Grails application. I have a class annotated with the #Entry annotation, so it is mapped to an LDAP server. This all works quite beautifully.
However, when I add the Grails #Validateable annotation (to enable validating the LDAP class similarly to Grails domain classes) and attempt to retrieve data from LDAP (i.e. a findAll operation on the LdapUserRepo, or similar), I get the following exception:
Message: Missing converter from class java.lang.String to interface org.springframework.validation.Errors, this is needed for field errors on Entry class com.ldap.portal.LdapUser
Basically, it seems like the AST transformation performed by the #Validateable annotation is producing extra fields (namely the errors field) on the LdapUser object. It appears that Spring LDAP, in processing the #Entry logic, assumes a default mapping for the fields property (probably interpreting it as a string field on the LDAP object). When it gets nothing from the LDAP server, it attempts to set the field of type ValidationErrors to a value of type String -- an empty string.
I did some looking in github and found this code that seems relevant and may support my theory.
My question is: is this behavior expected for annotations, and how can one prevent fields added by one annotation from being inappropriately processed by another annotation?
At present the best workaround I've come up with for my specific issue is to add an errors field to my LdapUser object and mark it as transient (so that LDAP ignores it):
#Transient
ValidationErrors errors
Related
I am trying to use Spring Boot Validation/ Hibernate Validator in one of our services and for the purpose of that service I created a DTO where I set customized constraints to the DTO root and on its fields.
The DTO looks like this:
#JobIsInUserQueue
#CanPerformTask
class PerformableJob(
#HasCurrentTaskList
val job: Job,
val user: Person
)
Given I have defined those annotations, and the implementation classes following the Jakarta validation framework API,
When I create an instance and let the spring boot provided validator bean process that bean:
val testable = PerformableJob(job, person)
val violations = validator.validate(testable)
Then the expected behaviour is that the three validators #JobIsInUserQueue, #CanPerformQCTask and #HasCurrentTaskList are checked,
But the actual behaviour is that only the class level annotations #JobIsInUserQueue and #CanPerformQCTask are checked.
I tried already to add #Valid to the field, but there are no nested validations within Job, so that would not be the right solution.
All custom constrains are marked with #Retention(AnnotationRetention.RUNTIME).
What do I miss here?
When using Spring Boot for a project, Jackson came as default to serialize objects back and from Jsons. I realize that Jackson fails if you don't have public accessors, e.g., getters/setters, or public fields in your POJO.
The behavior is different when I switch to Gson. It detects all fields regardless of their visibility. For this reason, I ended up using Gson.
I felt a little uncomfortable about switching my POJO access rules; It would force some refactoring in the project structure.
So, no problems using Gson, but is there a way of change Jackson's behavior?
Jackson does support reading values from private member fields, but does not do it by default.
You can configure the behavior globally in the Spring Boot config like
jackson:
visibility.field: any
visibility.getter: none
visibility.setter: none
visibility.is-getter: none
(this config will only look for member fields and no longer check get, set and is methods)
You could also use the #JsonAutoDetect annotation to do the same setting for a specific class.
Try to set visibility at ObjectMapper level,
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, Visibility.NONE);
mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
I have a controller with a method like
#PostMapping(value="/{reader}")
public String addToReadingList(#PathVariable("reader") String reader, Book book) {
book.setReader(reader);
readingListRepository.save(book);
return "redirect:/readingList/{reader}";
}
When I run a static code analysis with Sonarqube I get a vulnerability report stating that
Replace this persistent entity with a simple POJO or DTO object
But if I use a DTO (which has exactly the same fields as the entity class, then I get another error:
1 duplicated blocks of code must be removed
What should be the right solution?
Thanks in advance.
Enric
You should build a new separate class which represents your Entity ("Book" ) as Plain Old Java Object (POJO) or Data Transfer Object (DTO). If you use JSF or other stateful technology this rule is important. If your entity is stateful there might be open JPA sessions etc. which may modify your database (e.g. if you call a setter in JSF on a stateful bean).
For my projects I ignore this Sonar rule because of two reasons:
I alway you REST and REST will map my Java Class into JSON which can be seen as a DTO.
REST is stateless (no server session) so no database transaction will be open after the transformation to JSON
Information obtained from sonarsource official documentation.
On one side, Spring MVC automatically bind request parameters to beans
declared as arguments of methods annotated with #RequestMapping.
Because of this automatic binding feature, it’s possible to feed some
unexpected fields on the arguments of the #RequestMapping annotated
methods.
On the other end, persistent objects (#Entity or #Document) are linked
to the underlying database and updated automatically by a persistence
framework, such as Hibernate, JPA or Spring Data MongoDB.
These two facts combined together can lead to malicious attack: if a
persistent object is used as an argument of a method annotated with
#RequestMapping, it’s possible from a specially crafted user input, to
change the content of unexpected fields into the database.
For this reason, using #Entity or #Document objects as arguments of
methods annotated with #RequestMapping should be avoided.
In addition to #RequestMapping, this rule also considers the
annotations introduced in Spring Framework 4.3: #GetMapping,
#PostMapping, #PutMapping, #DeleteMapping, #PatchMapping.
See More Here
Let's imagine we have such a component in Spring:
#Component
public class MyComponent {
#Value("${someProperty}")
private String text;
}
If we define the property placeholder:
<context:property-placeholder location="classpath:myProps.properties"/>
And myPropos.properties contains the value for someProperty the value will be injected to the text field when the context is initialized. That's quite simple and easy.
But let's say that I have a service that enables user to change the value of the someProperty:
public void changeProp(String name, String newValue);
Is there a chance I can re-inject the newValue to text field. I mean it should be quite straight forward.. Basically it's nothing different than the after-initialization injection. I can not imagine that Spring does not have support for this? Can I fire some event or something?
I could do this on my own basically, but I wander is it maybe something there already? If not does anyone know what Spring class is in fact handling the injections at the first place? I could probably reuse the code there do perform this on my own if a solution does not exists.
I expect spring does not have a support for this, because the normal injection is done while creating the bean, but not will it is put in service.
Anyway: in this blog entry "Reloadable Application Properties with Spring 3.1, Java 7 and Google Guava", you can find the idea for an solution.
The key idea is to use a post processor to build a list of all fields with property fields. And if the properties are changed on can use this list to update the fields.
I'm using Spring 3 + JPA 2 (hibernate Impl) + Spring MVC's contentNegotiationResolver based on Json media type and got the following exception stack trace is thrown while leveraging Spring framework's
org.springframework.web.servlet.view.json.MappingJacksonJsonView
*
org.codehaus.jackson.map.JsonMappingException: No serializer found for class org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS) ) (through reference chain:..... )
at org.codehaus.jackson.map.ser.StdSerializerProvider$1.serialize(StdSerializerProvider.java:62)
at org.codehaus.jackson.map.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:268)
at org.codehaus.jackson.map.ser.BeanSerializer.serializeFields(BeanSerializer.java:146)
at org.codehaus.jackson.map.ser.BeanSerializer.serialize(BeanSerializer.java:118)
at org.codehaus.jackson.map.ser.ContainerSerializers$IndexedListSerializer.serializeContents(ContainerSerializers.java:236)
at org.codehaus.jackson.map.ser.ContainerSerializers$IndexedListSerializer.serializeContents(ContainerSerializers.java:189)
at org.codehaus.jackson.map.ser.ContainerSerializers$AsArraySerializer.serialize(ContainerSerializers.java:111)
at org.codehaus.jackson.map.ser.StdSerializerProvider._serializeValue(StdSerializerProvider.java:296)
at org.codehaus.jackson.map.ser.StdSerializerProvider.serializeValue(StdSerializerProvider.java:224)
at org.codehaus.jackson.map.ObjectMapper.writeValue(ObjectMapper.java:925)
at org.springframewor
*
The solution provided in the below thread is not working (using Json AutoDetect) doesnt work for me.
Strange Jackson exception being thrown when serializing Hibernate object
Providing Json AutoDetect and explicitly providing Json property annotation on getter method is not working - it still parses all the attributes. Also I've succeeded by avoiding certain attributes of JPA Entity class using #JsonIgnore which is kind of recursive or parent/child relation in nature. I've also tried detaching the entity class but no luck.
From my entity class, all the Jackson parser need to do is parse some three simple String attributes and rest of the attributes is marked with #JsonIgnore
Please let me know if any of you faced similar issue and resolved it.