Spring MVC: How to read and change a #PathVariable value - java

This question is very similar to this one, but I dont know where to start.
Suppose I have an action like this:
#GetMapping("/foo/{id}")
public Collection<Foo> listById(#PathVariable("id") string id) {
return null;
}
How could one intercept the listById method and change the value of id (Eg.: concat a string, pad with zeros etc)?
My scenario is that mostly of the IDs are left-padded with zeros (lengths differ) and I dont want to leave this to my ajax calls.
Expected solution:
#GetMapping("/foo/{id}")
public Collection<Foo> listById(#PathVariablePad("id", 4) string id) {
// id would be "0004" on "/foo/4" calls
return null;
}

Ok, here is how I've done it.
Since we can't inherit annotations and thus #PathVariable's target are only parameters, we have to create a new annotation, as follows:
#Target(ElementType.PARAMETER)
#Retention(RetentionPolicy.RUNTIME)
public #interface PathVariablePad {
int zeros() default 0;
#AliasFor("name")
String value() default "";
#AliasFor("value")
String name() default "";
boolean required() default true;
}
Now we need to create a HandlerMethodArgumentResolver. In this case, since all I want is to left-pad a #PathVariable with zeros, we're going to inherit PathVariableMethodArgumentResolver, like this:
public class PathVariablePadderMethodArgumentResolver extends PathVariableMethodArgumentResolver {
private String leftPadWithZeros(Object target, int zeros) {
return String.format("%1$" + zeros + "s", target.toString()).replace(' ', '0'); // Eeeewwwwwwwwwwww!
}
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(PathVariablePad.class);
}
#Override
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
PathVariablePad pvp = parameter.getParameterAnnotation(PathVariablePad.class);
return new NamedValueInfo(pvp.name(), pvp.required(), leftPadWithZeros("", pvp.zeros()));
}
#Override
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
PathVariablePad pvp = parameter.getParameterAnnotation(PathVariablePad.class);
return leftPadWithZeros(super.resolveName(name, parameter, request), pvp.zeros());
}
}
Finally, let's register our method argument resolver (xml):
<mvc:annotation-driven>
<mvc:argument-resolvers>
<bean class="my.package.PathVariablePadderMethodArgumentResolver" />
</mvc:argument-resolvers>
</mvc:annotation-driven>
The usage is pretty simple and heres how to do this:
#GetMapping("/ten/{id}")
public void ten(#PathVariablePad(zeros = 10) String id) {
// id would be "0000000001" on "/ten/1" calls
}
#GetMapping("/five/{id}")
public void five(#PathVariablePad(zeros = 5) String id) {
// id would be "00001" on "/five/1" calls
}

Spring #InitBinder annotation and WebDataBinder class will help you to intercept parameter and process it's value before controller method call.
Documentation:
InitBinder
WebDataBinder
Full code pattern:
#RestController
public class FooController {
#InitBinder
private void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(String.class, new PropertyEditorSupport() {
#Override
public void setAsText(String text) throws IllegalArgumentException {
super.setValue("000" + text);
}
} );
}
#GetMapping(value = "/foo/{id}")
public Foo sayHello(
#PathVariable(value = "id") String id
) {
return new Foo(id);
}
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public static class Foo {
#XmlElement(name = "id")
private String id;
public Foo(String id) {
this.id = id;
}
public Foo() {
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
}
And the usage:
curl http://localhost:8080/foo/10 | xmllint --format -
Response:
<foo>
<id>00010</id>
</foo>

This is quite similar, but for Decoding the #PathVariable value, as brought here by #yanefedor, but applied to all Controllers in the application:
#org.springframework.web.bind.annotation.ControllerAdvice
public class ControllerAdvice {
/**
* Just to decode the data parsed into the Controller's methods parameters annotated with #PathVariable.
*
* #param binder
*/
#InitBinder
private void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(String.class, new PropertyEditorSupport() {
#Override
public void setAsText(String text) throws IllegalArgumentException {
if (text == null) {
super.setValue(null);
} else {
super.setValue(UriUtils.decode(text, Charset.defaultCharset()));
}
}
});
}
}

Related

Multiple yml files configuring in corresponding Configuration class (Spring Boot)

I have more than one yml files in Spring Boot in resource classpath location like following structure of Spring Boot. Initially I have written only for application-abc.yml and at the time all the values of this file was loading in their corresponding class but when I have added on another file application-xyz.yml then also it loads into their corresponding configuration classes but at this time only loading the values of application-xyz.yml in both the configuration classes. So, want help to configure values of both the files in their corresponding configuration files in a single build :
-src
-main
-java
-packages
-config
-ApplicationAbcConfig.java
-ApplicationConfig.java
-ApplicationFactory.java
-ApplicationXyzConfig.java
-Authentication.java
-Operations.java
-Payload.java
-RequestPayload.java
-ResponsePayload.java
-services
-YmlConfigurationSelection.java
-resources
-application.yml
-application-abc.yml
-application-xyz.yml
-MultipleYmlDemoProject.java
Content of application-abc.yml
authentication:
name: name
type: type
payload:
request:
- sequence: 1
attributes:
- attributes1
- attributes2
response:
- sequence: 1
attributes:
- attributes3
- attributes4
operations:
name: name
type: type
payload:
request:
- sequence: 1
attributes:
- attributes5
- attributes6
response:
- sequence: 1
attributes:
- attributes7
- attributes8
Content of application-xyz.yml
authentication:
name: name
type: type
payload:
request:
- sequence: 1
attributes:
- attributes9
- attributes10
response:
- sequence: 1
attributes:
- attributes11
- attributes12
operations:
name: name
type: type
payload:
request:
- sequence: 1
attributes:
- attributes13
- attributes14
response:
- sequence: 1
attributes:
- attributes15
- attributes16
Content of ApplicationConfig.java
public interface ApplicationConfig {
public Authentication getAuthentication();
public void setAuthentication(Authentication authentication);
public Operations getOperations();
public void setOperations(Operations operations);
}
Content of Authentication.java
public class Authentication {
private String name;
private String type;
private Payload payload;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public Payload getPayload() {
return payload;
}
public void setPayload(Payload payload) {
this.payload = payload;
}
}
Content of Operations.java
public class Operations {
private String name;
private String type;
private Payload payload;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public Payload getPayload() {
return payload;
}
public void setPayload(Payload payload) {
this.payload = payload;
}
}
Content of Payload.java
public class Payload {
private List<RequestPayload> request;
private List<ResponsePayload> response;
public List<RequestPayload> getRequest() {
return request;
}
public void setRequest(List<RequestPayload> request) {
this.request = request;
}
public List<ResponsePayload> getResponse() {
return response;
}
public void setResponse(List<ResponsePayload> response) {
this.response = response;
}
}
Content of RequestPayload.java
public class RequestPayload {
private String sequece;
private List<String> attributes;
public String getSequece() {
return sequece;
}
public void setSequece(String sequece) {
this.sequece = sequece;
}
public List<String> getAttributes() {
return attributes;
}
public void setAttributes(List<String> attributes) {
this.attributes = attributes;
}
}
Content of ResponsePayload.java
public class ResponsePayload {
private String sequece;
private List<String> attributes;
public String getSequece() {
return sequece;
}
public void setSequece(String sequece) {
this.sequece = sequece;
}
public List<String> getAttributes() {
return attributes;
}
public void setAttributes(List<String> attributes) {
this.attributes = attributes;
}
}
Content of ApplicationAbcConfig.java
#Configuration
#SpringBootConfiguration
#EnableConfigurationProperties
#org.springframework.context.annotation.PropertySource("classpath:application-abc.yml")
public class ApplicationAbcConfig implements ApplicationConfig, PropertySourceFactory {
private Authentication authentication;
private Operations operations;
#Override
public Authentication getAuthentication() {
return authentication;
}
#Override
public void setAuthentication(Authentication authentication) {
this.authentication = authentication;
}
#Override
public Operations getOperations() {
return operations;
}
#Override
public void setOperations(Operations operations) {
this.operations = operations;
}
#Override
public PropertySource<?> createPropertySource(#Nullable String name, EncodedResource resource) throws IOException {
Properties propertiesFromYaml = loadYamlIntoProperties(resource);
String sourceName = name != null ? name : resource.getResource().getFilename();
return new PropertiesPropertySource(sourceName, propertiesFromYaml);
}
private Properties loadYamlIntoProperties(EncodedResource resource) throws FileNotFoundException {
try {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(resource.getResource());
factory.afterPropertiesSet();
return factory.getObject();
} catch (IllegalStateException e) {
// for ignoreResourceNotFound
Throwable cause = e.getCause();
if (cause instanceof FileNotFoundException)
throw (FileNotFoundException) e.getCause();
throw e;
}
}
}
Content of ApplicationXyzConfig.java
#Configuration
#SpringBootConfiguration
#EnableConfigurationProperties
#org.springframework.context.annotation.PropertySource("classpath:application-xyz.yml")
public class ApplicationXyzConfig implements ApplicationConfig, PropertySourceFactory {
private Authentication authentication;
private Operations operations;
#Override
public Authentication getAuthentication() {
return authentication;
}
#Override
public void setAuthentication(Authentication authentication) {
this.authentication = authentication;
}
#Override
public Operations getOperations() {
return operations;
}
#Override
public void setOperations(Operations operations) {
this.operations = operations;
}
#Override
public PropertySource<?> createPropertySource(#Nullable String name, EncodedResource resource) throws IOException {
Properties propertiesFromYaml = loadYamlIntoProperties(resource);
String sourceName = name != null ? name : resource.getResource().getFilename();
return new PropertiesPropertySource(sourceName, propertiesFromYaml);
}
private Properties loadYamlIntoProperties(EncodedResource resource) throws FileNotFoundException {
try {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(resource.getResource());
factory.afterPropertiesSet();
return factory.getObject();
} catch (IllegalStateException e) {
// for ignoreResourceNotFound
Throwable cause = e.getCause();
if (cause instanceof FileNotFoundException)
throw (FileNotFoundException) e.getCause();
throw e;
}
}
}
Content of ApplicationFactory.java
#Component
public class ApplicationFactory {
#Autowired
private ApplicationAbcConfig applicationAbcConfig;
#Autowired
private ApplicationXyzConfig applicationXyzConfig;
public ApplicationConfig getApplicationPropertiesConfig(String application) {
if (application.equalsIgnoreCase("abc")) {
return applicationAbcConfig;
} else if (application.equalsIgnoreCase("xyz")) {
return applicationXyzConfig;
} else {
return null;
}
}
}
Content of YmlConfigurationSelection.java
public class YmlConfigurationSelection {
#Autowired
private ApplicationFactory applicationFactory;
private ApplicationConfig applicationConfig;
public Object accessingProperties(String application) {
applicationConfig = applicationFactory.getApplicationPropertiesConfig(application);
return null;
}
}
Content of MultipleYmlDemoProject.java
#SpringBootApplication
#SpringBootConfiguration
#PropertySource(factory = ApplicationAbcConfig.class, value = "classpath:application-abc.yml")
#PropertySource(factory = ApplicationXyzConfig.class, value = "classpath:application-xyz.yml")
public class MultipleYmlDemoProject {
public class MultipleYmlDemo {
public static void main(String[] args) {
ConfigurableApplicationContext ctx =
SpringApplication.run(YamlPropertysourceApplication.class, args);
ConfigurableEnvironment env = ctx.getEnvironment();
}
}
}
It looks like you have an old spring application that was attempted to be migrated to spring boot.
Spring boot works natively with yaml files, so if you do the integration in a spring boot way, it will be possible to delete a lot of boilerplate code that you have to maintain. Also the naming of configurations is problematic: the names application-<something>.yml are reserved to be used with spring boot profiles, maybe if you'll rename to myprops-abc/xyz.yaml it will behave in a different way, I can't say for sure.
All-in-all I suggest you the following way, which is much better IMO:
Both configuration sets must be loaded into one configuration, so create a configuration properties file that denotes this one configuration:
#ConfigurationProperties(prexix="security")
public class SecurityConfigProperties {
private SecurityRealm abc;
private SecurityRealm xyz;
// getters, setters
}
public class SecurityRealm {
private Authentication autentication;
private Operations operations;
// getters setters
}
public class Authentication {...}
private class Operations {...}
Now place all the content from abc and xyz yaml into one file application.yaml and give a 'security' prefix:
security:
abc: // note, this 'abc' matches the field name of the configuration
authentication:
...
operations:
....
xyz:
authentication:
...
operations:
....
OK, everything is mapped, create the configuration like this:
#Configuration
#EnableConfigurationProperties(SecurityConfigProperties.class)
public class SecurityConfiguration {
#Bean
public SecurityBeanForABC(SecurityConfigProperties config) {
return new SecurityBeanForABC(config.getAbc().getAuthentication(), config.getAbc().getOperations());
}
... the same for XYZ
}
Note that with this approach you only maintain a configuration mapping in java objects and there is no code for loading / resolving properties) - everything is done by spring boot automatically. If you configure a special annotation processor and have a descent IDE you can get even auto-completion facilities for those yaml properties but its out of scope for this question. The point is that doing things in a way directly supported by spring boot has many advantages :)

JSR 303 Bean Validation Custom Constraint in Spring Boot Rest endpoint

I've got a Spring boot application serving some REST endpoints. I'm trying to create some reusable logic around filters, paging, sorting etc.
For the PagingInfoDto object, I'm wrapping limit and offset request params.
I'm doing some bean validation to ensure limit and offset meet some cusomt rules.
This all works fine when I implement it like so:
Controller:
#RequestMapping(value = "/somevalue", method = RequestMethod.GET)
public ResponseEntity<Object> list (
#Valid PagingInfoDto pagingInfoDto){
PagingInfoDto:
#Paging(limitDefault = 2)
public class PagingInfoDto
{
private boolean hasMore;
private Integer offset;
private Integer limit;
public Integer getOffset() {
return offset;
}
public void setOffset(Integer offset) {
this.offset = offset;
}
public Integer getLimit() {
return limit;
}
public void setLimit(Integer limit) {
this.limit = limit;
}
public boolean isHasMore()
{
return hasMore;
}
public void setHasMore(boolean hasMore)
{
this.hasMore = hasMore;
}
}
PagingRequestFiltervalidator:
public class PagingRequestFilterValidator implements ConstraintValidator<Paging, PagingRequestFilterDto> {
private static final String MISSING_OFFSET = "offset cannot appear without limit";
private static final String ZERO_LIMIT = "limit cannot be 0";
private static final String MISSING_LIMIT = "limit cannot appear without offset";
private int limit = 0;
#Override
public void initialize(Paging paging) {
this.limit = paging.limitDefault();
}
#Override
public boolean isValid(PagingRequestFilterDto pagingRequestFilterDto, ConstraintValidatorContext constraintValidatorContext) {
boolean validity = true;
constraintValidatorContext.disableDefaultConstraintViolation();
/**
* Offset has to be greater than or equal to Zero
*/
if(pagingRequestFilterDto.getOffset() == null) {
pagingRequestFilterDto.setOffset(0);
}
/**
* Set undefined or invalid limit to default to value from the annotation
*/
if(pagingRequestFilterDto.getLimit() == null
|| pagingRequestFilterDto.getLimit() > limit) {
pagingRequestFilterDto.setLimit(limit);
}
Paging Annotation:
#Constraint(validatedBy = {PagingRequestFilterValidator.class})
#Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.TYPE, ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface Paging {
String message() default "Invalid Paging Details";
int limitDefault() default 20;
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
What I'd like to be able to do is annotate the parameter PagingInfoDto parameter with the #Paging Custom Constraint so that each endpoint can set it's own limitDefault.
Like this:
#RequestMapping(value = "/somevalue", method = RequestMethod.GET)
public ResponseEntity<Object> list (
**#Paging(limitDefault = 2)** PagingInfoDto pagingInfoDto){ ...
#RequestMapping(value = "/someothervalue", method = RequestMethod.GET)
public ResponseEntity<Object> otherlist (
**#Paging(limitDefault = 5)** PagingInfoDto pagingInfoDto){...
The above doesn't fire validation of the object. I feel like it's as simple as I'm missing something very obvious...
I found a blog post which seems to indicate that you need to implement a custom Bean validator:
http://blog.trifork.com/2009/08/04/bean-validation-integrating-jsr-303-with-spring/
My implementation
#Component
public class BeanValidator implements org.springframework.validation.Validator, InitializingBean {
private Validator validator;
public void afterPropertiesSet() throws Exception {
ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
validator = validatorFactory.usingContext().getValidator();
}
public boolean supports(Class clazz) {
return true;
}
public void validate(Object target, Errors errors) {
Set<ConstraintViolation<Object>> constraintViolations = validator.validate(target);
for (ConstraintViolation<Object> constraintViolation : constraintViolations) {
String propertyPath = constraintViolation.getPropertyPath().toString();
String message = constraintViolation.getMessage();
errors.rejectValue(propertyPath, "", message);
}
}
}
Still no luck.

Java binary serializing fails because of jackson

I use jackson 2 to convert json into a java object. So far so good. But I also use hazelcast to distribute the objects in a cluster. Therefore all beans have to be java.io.Serializable. When I read the Object from json like so:
ObjectMapper mapper = new ObjectMapper();
mapper.addMixInAnnotations(AbstractBean.class, MongoIdMixIn.class);
// this is to prevent from failing on missing type class property: #JsonProperty("#class")
Object tgtObject = targetClass.newInstance();
mapper.readerForUpdating(tgtObject).readValue(dbo.toString());
// put into hazelcast map
target.put(dbo.get(keyColumn), tgtObject);
I will get an exception from hazelcast:
java.io.NotSerializableException: com.fasterxml.jackson.databind.deser.impl.TypeWrappedDeserializer
I am wondering where the com.fasterxml.jackson.databind.deser.impl.TypeWrappedDeserializer is coming from since the Object is a plain java bean (but using inheritance).
My Abstract class is:
#JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.PROPERTY, property="#javaClass")
public abstract class AbstractBean implements Serializable {
#JsonIgnore public static final transient IMarkupParser MARKUP_PARSER = new WikiMarkupParser();
#JsonProperty("id")
private String id;
#JsonProperty("#class")
private String clazz;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getClazz() {
return this.getClass().getSimpleName();
}
}
And my child is:
public class Posting extends AbstractBean {
private String postingSource;
private String languageCode;
public String getPostingSource() {
return postingSource;
}
public void setPostingSource(String postingSource) {
this.postingSource = postingSource;
}
public String getLanguageCode() {
return languageCode;
}
public void setLanguageCode(String languageCode) {
this.languageCode = languageCode;
}
}
I have no Idea why the serailizer would even try to serialize the mixins since the are not part of the bean but here they are (yes I have tried to make them serializable too, just as a test, no luck):
public interface IdMixins extends Serializable {
}
public interface MongoIdMixIn extends IdMixins {
#JsonProperty("_id")
#JsonSerialize(using = MongoIdSerializer.class)
public String getId();
#JsonProperty("_id")
#JsonDeserialize(using = MongoIdDeserializer.class)
public void setId(String id);
}
public class MongoIdDeserializer extends JsonDeserializer<String> implements Serializable {
private static final long serialVersionUID = -5404276857799190647L;
#Override
public String deserialize(JsonParser jp, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
String value = null;
String tmp = jp.getText(); // {
validate(jp, tmp,"{");
int curly = 1;
while (jp.nextToken() != null) {
String v = jp.getText();
if (v.equals("{")) curly++;
if (v.equals("$oid")) {
jp.nextToken();
value = jp.getText();
}
if (v.equals("}")) curly--;
if (curly<=0) return value;
}
return null;
}
private void validate(JsonParser jsonParser, String input, String expected) throws JsonProcessingException {
if (!input.equals(expected)) {
throw new JsonParseException("Unexpected token: " + input, jsonParser.getTokenLocation());
}
}
}
public class MongoIdSerializer extends JsonSerializer<String> implements Serializable {
private static final long serialVersionUID = 3435689991839324194L;
#Override
public void serialize(String s, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
jsonGenerator.writeStartObject();
jsonGenerator.writeFieldName("$oid");
jsonGenerator.writeString(s);
jsonGenerator.writeEndObject();
}
}
Stupid me! Somewhere in the serialization chain was a completely unnecessary ObjectMapper object. But it was hard to find because not the Posting object was the real reason, instead it was another object. But the Stacktrace and the com.fasterxml.jackson.databind.deser.impl.TypeWrappedDeserializer Exception were completely miss leading! ... clustered software is sometimes really painful to debug :-)
I'm 1 Rep. Point away from being able to comment. So I have to make a suggestion as an answer ;-).
Perhaps one of the Annotations do inject an instance of TypeWrappedDeserializer as a private property into the AbstractBean. Maybe as hint for the deserialization mechanism.
Could you inspect the created object with reflection to verify?
for (Field field : tgtObject.getClass().getDeclaredFields() )
{
// you can replace this by your logging method
System.out.println("Field: " + field.getName() + ":" + field.getType());
}
for (Field field : tgtObject.getClass().getSuperclass().getDeclaredFields() )
{
// you can replace this by your logging method
System.out.println("Field: " + field.getName() + ":" + field.getType());
}
If you find the apropriate type in the listing the Class was added by Byte Code Enhancement.

Getting JsonMappingException while sending data to view

I am trying to show DB data to my webpage.
I have made following code when GET request to the #RequestMapping(value = "/api/binder").
but when get request came to this method it will fetch data (I have print on console and display well) but it doesn't map to my Java Script Ajax call, it's showing me an error.
Following is my code for to fetch data :
#Autowired
IBinderViewRepository repository;
#RequestMapping(method= RequestMethod.GET)
public #ResponseBody
List<BinderResponse> getBinders(){
List<BinderView> binders = repository.getBinders();
List<BinderResponse> responses = new ArrayList<>();
ModelMapper mapper = Mapper.getInstance();
for(int i = 0; i < binders.size(); i++){
System.out.println("In Loop");
BinderResponse response = mapper.map(binders.get(i),BinderResponse.class);
System.out.println("Data :: " + response.getBinderName());
responses.add(response);
}
return responses;
}
but it shows me following error :
HTTP Status 500 - Could not write JSON: (was java.lang.NullPointerException) (through reference chain: java.util.ArrayList[0]->com.ngl.dto.outgoing.BinderResponse["valid"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: (was java.lang.NullPointerException) (through reference chain: java.util.ArrayList[0]->com.ngl.dto.outgoing.BinderResponse["valid"])
Here is ajax call from knockout js :
ajax.get('api/binder').done(function(response){ ... }
Here BinderView and BinderResponse have same fields :
private String binderName;
private String binderAddress1;
and getter setter as well in both.
and repository.genBinders() method bring data from DB.
Here is insert method and works fine for me :
#RequestMapping(method= RequestMethod.POST,consumes = "application/json")
public #ResponseBody
IWebApiResponse addBinder(#RequestBody AddBinderForm binder){
.....
}
Shall I have to put any json annotation on my BinderResponse class ?
I don't understand where am i wrong ?Anyone pleas guide me.
UPDATE :
public class BinderResponse extends WebApiResponseBase {
private String binderName;
private String binderAddress1;
public String getBinderName() {
return binderName;
}
public void setBinderName(String binderName) {
this.binderName = binderName;
}
public String getBinderAddress1() {
return binderAddress1;
}
public void setBinderAddress1(String binderAddress1) {
this.binderAddress1 = binderAddress1;
}
}
BinderView :
public class BinderView extends BaseView {
private String binderName;
private String binderAddress1;
public String getBinderName() {
return binderName;
}
public void setBinderName(String binderName) {
this.binderName = binderName;
}
public String getBinderAddress1() {
return binderAddress1;
}
public void setBinderAddress1(String binderAddress1) {
this.binderAddress1 = binderAddress1;
}
}
In console it prints data / BinderName :
In Loop
Data :: ada
In Loop
Data :: tya
New Update :
Here is BaseView :
#MappedSuperclass
public abstract class BaseView implements IEntity {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue
#Column(name="id")
private long id;
public long getId() {
return id;
}
public void setId(long id) {
if (this.id != 0 && this.id != id) {
throw new IllegalStateException(
"The ID must not be changed after it is set.");
}
this.id = id;
}
}
and In IEntity :
public interface IEntity extends Serializable {
long getId();
void setId(long id);
}
WebApiResponseBase :
public class WebApiResponseBase implements IWebApiResponse {
private String _uri;
#Override
public String getUri() {
return _uri == null ? "" : _uri;
}
#Override
public void setUri(String uri) {
_uri = uri;
}
}
Jackson, by default, serializes an object's whole inheritance hierarchy, ie. the parent class fields as well. In the case of
public class BinderResponse extends WebApiResponseBase {
it seems like
Could not write JSON: (was java.lang.NullPointerException) (through reference chain: java.util.ArrayList[0]->com.ngl.dto.outgoing.BinderResponse["valid"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: (was java.lang.NullPointerException) (through reference chain: java.util.ArrayList[0]->com.ngl.dto.outgoing.BinderResponse["valid"])
Jackson tries to serialize a field called valid from a getter called isValid (which is a conventional bean property name). The getter method, however, seems to throw a NullPointerException for whatever reason.
If you want Jackson to ignore it, you can annotate the getter with #JsonIgnore or your class with #JsonIgnoreProperties and specify the property name, ie. valid.
In my case when I used #JsonIgnore the exception has been gone but the problem was it couldn't receive that value from API Request anymore and Spring ignored it (obviously because of #JsonIgnore) So I investigated about the issue and figured out that the problem was the getter and setter.
I had the Integer property while my getter was int. So when I changed the getter to Integer my problem solved and error's gone.
private Integer purchaseId;
#JsonIgnore
public int getPurchaseId() {
return purchaseId;
}
public void setPurchaseId(int purchaseId) {
this.purchaseId = purchaseId;
}
Changed to :
private Integer purchaseId;
public Integer getPurchaseId() {
return purchaseId;
}
public void setPurchaseId(Integer purchaseId) {
this.purchaseId = purchaseId;
}
#Column(name="createddate")
private Date createdDate;
#Transient
private String formatedCreatedDate;
public String getFormatedCreatedDate() {
DateFormat dateFormat = new SimpleDateFormat("dd/mm/yyyy");
return dateFormat.format(this.getCreatedDate());
}
It throws the same exception because here may be null by calling getCreatedDate() value come so it can't format null date so keep null check here like:
Solution
public String getFormatedCreatedDate() {
DateFormat dateFormat = new SimpleDateFormat("dd/mm/yyyy");
Date createDdate=this.getCreatedDate();
if(createDdate!=null){
return dateFormat.format(createDdate);
}
return "-";
}

LazyInitializationException encountered when using load instead of get with Hibernate

I am using JPA, Hibernate and Spring MVC. In the controller class all the methods works greatly. When I test them in the web browser the public String getModuleFormation(long id) method, that returns an object, and it gives me the following error:
org.hibernate.LazyInitializationException: could not initialize proxy - no Session
as a root cause, but yesterday I tried it, and it worked without problem in the localhost:45045/GestionModules/detail/xx URL.
What could cause this problem?
My detail.jsp:
<c:if test="${!empty detailModule}">
${detailModule.idModule}
${detailModule.libModule}
</c:if>
POJO Class + JPA :
#Entity
#Table(name="ModuleFormation")
public class ModuleFormation {
private long idModule;
private String libModule;
public ModuleFormation() {
// TODO Auto-generated constructor stub
}
public ModuleFormation(String libModule) {
this.libModule = libModule;
}
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator = "seqModule")
#SequenceGenerator(name="seqModule", sequenceName = "seqModuleFormation")
#Column(name="idModule")
public long getIdModule() {
return this.idModule;
}
public void setIdModule(long idModule) {
this.idModule = idModule;
}
#Column(name="libModule", nullable=false, length = 100)
public String getLibModule() {
return this.libModule;
}
public void setLibModule(String libModule) {
this.libModule = libModule;
}
}
DAO Class :
#Repository
public class ModuleFormationDAOImpl implements ModuleFormationDAO {
#Autowired
private SessionFactory sessionFactory;
public void ajouterModuleFormation(ModuleFormation module) {
sessionFactory.getCurrentSession().save(module);
}
public void supprimerModuleFormation(long idModule) {
ModuleFormation module = (ModuleFormation) sessionFactory.getCurrentSession().load(ModuleFormation.class, idModule);
if(module != null)
sessionFactory.getCurrentSession().delete(module);
}
public List<ModuleFormation> listModuleFormation() {
return sessionFactory.getCurrentSession().createQuery("from ModuleFormation")
.list();
}
public ModuleFormation getModuleFormation(long idModule) {
return (ModuleFormation) sessionFactory.getCurrentSession().load(ModuleFormation.class, idModule);
}
public void majModuleFormation(ModuleFormation module) {
sessionFactory.getCurrentSession().merge(module);
}
}
Service Class :
#Service
public class ModuleFormationServiceImpl implements ModuleFormationService {
#Autowired
private ModuleFormationDAO moduleDao;
#Transactional
public void ajouterModuleFormation(ModuleFormation module) {
moduleDao.ajouterModuleFormation(module);
}
#Transactional
public void supprimerModuleFormation(long idModule) {
moduleDao.supprimerModuleFormation(idModule);
}
#Transactional
public List<ModuleFormation> listModuleFormation() {
return moduleDao.listModuleFormation();
}
#Transactional
public ModuleFormation getModuleFormation(long idModule) {
return moduleDao.getModuleFormation(idModule);
}
#Transactional
public void majModuleFormation(ModuleFormation module) {
moduleDao.majModuleFormation(module);
}
}
Controller Class :
#Controller
public class ModuleFormationController {
#Autowired
private ModuleFormationService moduleService;
#RequestMapping("/module")
public String listModulesFormations(Map<String, Object> map) {
map.put("module", new ModuleFormation());
map.put("moduleList", moduleService.listModuleFormation());
return "module";
}
#RequestMapping(value = "/ajouter", method = RequestMethod.POST )
public String ajouterModuleFormation(#ModelAttribute("module")
ModuleFormation module,BindingResult result) {
moduleService.ajouterModuleFormation(module);
return "redirect:/module";
}
#RequestMapping(value = "/supprimer/{idModule}")
public String supprimerModuleFormation(#PathVariable("idModule")
long idModule) {
moduleService.supprimerModuleFormation(idModule);
return "redirect:/module";
}
#RequestMapping(value= "/detail/{idModule}")
public String getModuleFormation(#PathVariable("idModule")
long idModule,Map<String, Object> map) {
map.put("detailModule", moduleService.getModuleFormation(idModule));
return "/detail";
}
#RequestMapping(value= "/detail/modifier", method = RequestMethod.POST )
public String majModuleFormation(#ModelAttribute("detailModule")
ModuleFormation module, BindingResult result) {
moduleService.majModuleFormation(module);
return "detail/{idModule}";
}
}
The Javadoc on the Hibernate Session#load(Class, Serializable) method says:
Return the persistent instance of the given entity class with the given identifier,
assuming that the instance exists. This method might return a proxied instance that
is initialized on-demand, when a non-identifier method is accessed.
When you access a property on the object in your JSP the session which loaded the object has been closed.
Use Session#get(Class, Serializable) to ensure that you don't load a proxy.
Instead of sessionFactory.getCurrentSession().load(ModuleFormation.class, idModule), have you tried sessionFactory.getCurrentSession().get(ModuleFormation.class, idModule)?

Categories