Spring Boot Actuator 2.x #WriteOperation with an object as parameter - spring-boot

What I try to achieve:
I want to create an endpoint that is accessible from web and jmx with an action that takes an object as a parameter
here is a simple example:
pom.xml
....
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.jolokia</groupId>
<artifactId>jolokia-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
The Object class:
#Data
#NoArgsConstructor
#AllArgsConstructor
public class Dog {
protected String name;
protected int age;
}
The endpoint class
#Component
#Endpoint(id = "dogs")
#Slf4j
public class EndpointDogsExperiment {
protected List<Dog> dogs = new ArrayList<>();
public EndpointDogsExperiment() {
dogs.add(new Dog("dog0", 5));
dogs.add(new Dog("dog1", 7));
log.debug("dogs created {}", dogs.toString());
}
#WriteOperation
public List<Dog> addDog(Dog dog) {
log.debug("adding a dog \n{}", dog );
dogs.add(dog);
return dogs;
}
}
the problem:
how do I "call" this operation
when I try HTTP POST with
/actuator/dogs
and body
{"dog":{"name":"aaaa", "age":33}}
client gets
Response:
status: 400
date: Sat, 31 Mar 2018 14:59:41 GMT
connection: close
transfer-encoding: chunked
content-type: application/vnd.spring-boot.actuator.v2+json;charset=UTF-8
{"timestamp":"2018-03-31T14:59:41.778+0000","status":400,"error":"Bad Request","message":"No message available","path":"/actuator/dogs"}
and server log say
2018-03-31 17:59:41.766 WARN 12828 --- [nio-8081-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Failed to read HTTP message: org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize instance of java.lang.String out of START_OBJECT token; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of java.lang.String out of START_OBJECT token
at [Source: (PushbackInputStream); line: 1, column: 8] (through reference chain: java.util.LinkedHashMap["dog"])
I have tried to create a #WriteOperation that has a String parameter and then
HTTP POST body
{"paramName":"StringParamValue"}
works ok
why is it?
Thanks!

Endpoint operations don’t support complex input such as your Dog type. You could, however, consume separate name and age parameters and create the Dog in the operation’s implementation:
#WriteOperation
public List<Dog> addDog(String name, int age) {
Dog dog = new Dog(name, age);
log.debug("adding a dog \n{}", dog );
dogs.add(dog);
return dogs;
}
You can learn more in Spring Boot's reference documentation.

Just use the #RestControllerEndpoint(id = "features") class level annotation.
On the methods you then have to use your regular #PostMapping annotation and it should work

Related

#Validated + javax annotations not working for controller method parameters

I have a Restcontroller in a Spring project. And I have to validate its method's parameter.
Mainly Spring #Validated annotation on class + javax validation annotations are proposed to be a good practice working examples.
But it failed: Spring just ignores this validation: ConstraintViolationException supposed to be thrown is probably smoking somewhere outside and lets other exceptions do the dirty work following the code.
#RestController
#RequiredArgsConstructor
#RequestMapping("/reducedPageNumbers")
#Validated
#Tag(
name = "Page number reducer controller",
description = "This controller is responsible for the reduction of a given range of page numbers for printer"
)
public class PageReducerController {
private final PageReducerService service;
#Operation(
method = "GET",
summary = "Finding a user by ID",
responses = {
#ApiResponse(responseCode = "200", description = "Successful Request"),
#ApiResponse(responseCode = "400", description = "Bad Request. " +
"All page numbers must be integers, separated by comas", content = {
#Content(
mediaType = "application/json",
array = #ArraySchema(schema = #Schema(implementation = ErrorContainer.class)))
}),
#ApiResponse(responseCode = "500", description = "Unexpected Internal Server Error", content =
#Content)
},
description = "This method transforms a list of page numbers in one String line, separated by ',' " +
"into ascending reduced format of a String line for printer. " +
"E.g.: \"1,3,32,5,11,7,6,19,2,21,4,8,22,23\" -> \"1-8,11,19,21-23,32\". " +
"And returns a ReducerResponse object with both initial String input line 'original' " +
"and reduced String line 'reduced'"
)
#GetMapping
public ResponseEntity<ReducerResponse> show(#NotNull #NotBlank String rawPageNumbers) {
ReducerResponse response = service.reduce(rawPageNumbers);
return new ResponseEntity<>(response, HttpStatus.OK);
}
What can be a problem here?
Tried to validate a method parameter through #Validated + javax validation annotations. Expected: ConstraintViolationException. Actually resulted: validation totally ignored.
I also made sure I use spring validation starter, hibernate-validator 6.0.0 and configured MethodValidationPostProcessor as follows:
#Configuration
public class ValidationConfig {
#Bean
public MethodValidationPostProcessor getMethodValidationPostProcessor(){
MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
processor.setValidator(this.validator());
return processor;
}
#Bean
public LocalValidatorFactoryBean validator(){
return new LocalValidatorFactoryBean();
}
}
My Pom:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.8</version>
<relativePath/>
</parent>
<groupId>by.smirnov</groupId>
<artifactId>pagenumberreducer</artifactId>
<version>1.0.1</version>
<name>pagenumberreducer</name>
<description>Page number reducer test project</description>
<properties>
<java.version>11</java.version>
<springdoc.version>1.6.14</springdoc.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>${springdoc.version}</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-webflux-ui</artifactId>
<version>${springdoc.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.0.Final</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
It started to work properly on Boot 3 with Jakarta 3.

Tomcat redirects all post request to get

I have issue with tomcat 9.0.50.
I have a basic Springboot application to deploy and all my post requests are actually redirected to GET request.
here is my Main class:
#SpringBootApplication
public class WsApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(WsApplication.class, args);
}
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(WsApplication.class);
}
}
Here i simply extend my class with SpringBootServletInitializer and I added the configure method.
Second point: I created a GET and a POST request in a controller.
#RestController
#RequestMapping("/")
public class AppController {
#GetMapping
public String hello() {
return "Hello";
}
#PostMapping
public String helloName(#RequestBody String name) {
return "Hello " + name;
}
}
I also have my pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.6</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>ws-application</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>ws-facturx</name>
<description>ws-facturx</description>
<packaging>war</packaging>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
In my application.prpperties I configured SQL and application name:
# ws-application
spring.application.name=ws-application
# postgresql
spring.datasource.url=jdbc:postgresql://localhost:5432/mydb
spring.datasource.username=root
spring.datasource.password=root
spring.jpa.hibernate.ddl-auto=create
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQL81Dialect
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQL81Dialect
It should normally work correctly. I can send GET request to my base URL and do retrieve the expected response. When I send a POST request, i obtain a 302 response. I created a dummy application to check if issue was coming from the application.
I tried the call using postman and with a curl request:
curl -i -X POST -H "Content-Type: text/plain" -d "John" http://localhost:8080/ws-application
Is there any issue with tomcat or Springboot ? The java version used is provided in jdk-11.0.6
I don't really know "why" I have been able to solve this issue but here is the "how".
#Slf4j
#RestController
#RequestMapping("/api")
public class FacturxController {
#GetMapping
public String hello() {
return "Hello";
}
#PostMapping
public String helloName(#RequestBody String name) {
log.info("Hello Name");
return "Hello " + name;
}
}
this app has just one controller, but it seems that it required to set a path to the RequestMapping.

How to use Spring's BackendIdConverter?

I have an entity named VendorProductMapping with embedded primary key. Hence I want to use BackendIdConverter to convert the embedded primary key to string and vice-versa.
I followed a few posts on stackoverflow and learnt that I need to use BackendIdConverter.
I created the following class:
public class VendorProductMappingIdConverter implements BackendIdConverter {
#Override
public Serializable fromRequestId(String id, Class<?> entityType) {
--
}
#Override
public String toRequestId(Serializable source, Class<?> entityType) {
--
}
#Override
public boolean supports(Class<?> type) {
--
}
}
The very first line gives me an error saying
Cannot resolve symbol 'BackendIdConverter'
Do I explicitly need to create a BackendIdConverter interface ?
Here is my pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>UI</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>UI</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-validation</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
EDIT:
Now that I've written a converter class, how do I use this ?
I need to send the primary key of VendorProductMapping class (i.e, VendorProductMappingPk) may be in an anchor tag. The error I see is
Failed to convert value of type 'java.lang.String' to required type 'com.example.UI.entity.VendorProductMappingPk'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'com.example.UI.entity.VendorProductMappingPk': no matching editors or conversion strategy found
BackendIdConverter is part of Spring Boot Data REST, so try adding the following dependency. However, I am not sure this is what you need for your case.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
I would say you need a JPA converter to achieve this:
#Converter
public class PersonNameConverter implements AttributeConverter<YourEmbeddedPrimaryKeyClass, String> {
#Override
public String convertToDatabaseColumn(YourEmbeddedPrimaryKeyClass id) {
// your conversion to String logic
}
#Override
public YourEmbeddedPrimaryKeyClass convertToEntityAttribute(String id) {
// your conversion to YourEmbeddedPrimaryKeyClass logic
}
}
Then you can configure this converter in your model:
#Entity
#IdClass(PK.class)
public class YourClass {
#Id
private YourEmbeddedPrimaryKeyClass id;
...
}
public class PK implements Serializable {
#Column(name = "MY_ID_FIELD")
#Convert(converter = IdConverter.class)
private YourEmbeddedPrimaryKeyClass id;
}
You can check additional details at https://www.baeldung.com/jpa-attribute-converters.

Repository bean cannot be found (No idea why)

I'm trying to run my application and I'm receiving this error:
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of constructor in com.danielturato.product.services.ProductServiceImpl required
a bean of type 'com.danielturato.product.persistence.ProductRepository' that could not be
found.
Action:
Consider defining a bean of type 'com.danielturato.product.persistence.ProductRepository' in
your configuration.
I have no idea why this is occurring. I've tried to look at past solutions where its said to use the #EnableMongoRepository to point to the correct package however I have tried this and it doesn't work. I've also tried adding the #Repository annotation above my repository (even though its not needed) and it doesn't work still. Here is my code for my repository, application & where the repository is failing to be injected.
Application:
#SpringBootApplication
#ComponentScan("com.danielturato")
public class ProductServiceApplication {
private static final Logger LOG = LoggerFactory.getLogger(ProductServiceApplication.class);
public static void main(String[] args) {
SpringApplication.run(ProductServiceApplication.class, args);
}
}
Repository:
public interface ProductRepository extends ReactiveCrudRepository<ProductEntity, String> {
Mono<ProductEntity> findByProductId(int productId);
}
ProductServiceImpl:
#RestController
public class ProductServiceImpl implements ProductService {
private static final Logger LOG = LoggerFactory.getLogger(ProductServiceImpl.class);
private final ServiceUtil serviceUtil;
private final ProductRepository repository;
private final ProductMapper mapper;
#Autowired
public ProductServiceImpl(ProductRepository repository, ProductMapper mapper, ServiceUtil serviceUtil) {
this.repository = repository;
this.mapper = mapper;
this.serviceUtil = serviceUtil;
}
Pom file:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.danielturato</groupId>
<artifactId>product-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>product-service</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.4.0.Beta1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.danielturato</groupId>
<artifactId>api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.danielturato</groupId>
<artifactId>util</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
<version>2.3.1.RELEASE</version>
</dependency>
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo</artifactId>
<version>2.2.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-kafka</artifactId>
<version>3.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
<version>3.0.6.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.3.0.Beta2</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</project>
If anyone can suggest for me anything to try or would like me to put more info here please let me know.
The stack trace you have included in your question does point to the ProductRepository not being available for autowiring and as a previous comment has pointed out this is because you are missing the #Repository annotation from your repository.
Another reason is if your repository cannot find a matching entity. This might be for example if the Id is the wrong type or is missing the #Id annotation, if the entity doesn't have either #Entity/#Document or if it is missing an all args constructor. Plus in your case you have added a findByProductId() method so your entity will need a productId field too.
I would also remove the #ComponentScan annoatation from your application class as this is included by default in #SpringBootApplication unless you are intentionally pointing it at a different class path.
If this still fails as you have mentioned in your comments is it failing for the same reason or is there a different error now related to ProductMapper or ServiceUtil?

How to cache PageImpl with Spring Data Geode?

When trying to cache a PageImpl response from a Spring Data JpaRepository using Spring Data Geode, it fails to cache the result with the following error:
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.data.domain.PageImpl]: No default constructor found; nested exception is java.lang.NoSuchMethodException: org.springframework.data.domain.PageImpl.<init>()
at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:127) ~[spring-beans-5.0.6.RELEASE.jar:5.0.6.RELEASE]
at org.springframework.data.convert.ReflectionEntityInstantiator.createInstance(ReflectionEntityInstantiator.java:64) ~[spring-data-commons-2.0.7.RELEASE.jar:2.0.7.RELEASE]
at org.springframework.data.convert.ClassGeneratingEntityInstantiator.createInstance(ClassGeneratingEntityInstantiator.java:86) ~[spring-data-commons-2.0.7.RELEASE.jar:2.0.7.RELEASE]
at org.springframework.data.gemfire.mapping.MappingPdxSerializer.fromData(MappingPdxSerializer.java:422) ~[spring-data-gemfire-2.0.7.RELEASE.jar:2.0.7.RELEASE]
at org.apache.geode.pdx.internal.PdxReaderImpl.basicGetObject(PdxReaderImpl.java:741) ~[geode-core-9.1.1.jar:?]
at org.apache.geode.pdx.internal.PdxReaderImpl.getObject(PdxReaderImpl.java:682) ~[geode-core-9.1.1.jar:?]
at org.apache.geode.internal.InternalDataSerializer.readPdxSerializable(InternalDataSerializer.java:3054) ~[geode-core-9.1.1.jar:?]
It looks like the MappingPdxSerializer looks for a default constructor but doesn't find it for a PageImpl class.
Here is maven pom for the dependencies I have:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.0.BUILD-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.BUILD-SNAPSHOT</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-hateoas</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-gemfire</artifactId>
</dependency>
<dependency>
<groupId>javax.cache</groupId>
<artifactId>cache-api</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>1.3</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.11</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.6</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.2.2</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
The JpaRepository I am using is:
#RepositoryRestResource
public interface RecordRepository extends JpaRepository<Record, Long>
{
#Override
#CacheEvict(cacheNames = { "Records" })
<S extends Record> S save(S s);
#Override
#Cacheable(value = "Records")
Optional<Record> findById(Long id);
#Override
#Cacheable(value = "Records", key = "#pageable.pageNumber + '.' + #pageable.pageSize + '.records'")
Page<Record> findAll(Pageable pageable);
#Override
#Cacheable(value = "Records")
Record getOne(Long aLong);
}
The code used to invoke a repository paged result is:
int PAGE=0,PAGE_SIZE=100;
Page<Record> recordPage;
do {
recordPage = recordRepository.findAll(PageRequest.of(PAGE, PAGE_SIZE));
log.info("Retrieved page: [{}]", recordPage);
} while (recordPage.hasNext());
I feel like it maybe a possible bug with the MappingPdxSerializer, but I'm not 100% sure. Any help in resolving this issue would be awesome!
Thanks
Why do you feel this is a possible bug with Spring Data Geode's (SDG) o.s.d.g.mapping.MappingPdxSerializer?
It is quite common, and even expected, that not all objects passed through SDG's MappingPdxSerializer will have a default (i.e. public, no-arg) constructor.
When using such types in your application (e.g. like the SD PageImpl class) and an instance of that type is read from Apache Geode (e.g. get(key)), the object is de-serialized and reconstructed on the (Region) data access operation (providing Apache Geode's read-serialized configuration attribute is not set to true; which cause you other problems and not recommended in this case), then you need to register an EntityInstantiator that informs SDG's MappingPdxSerializer how to instantiate the object, using an appropriate constructor.
The "appropriate" constructor is determined by the persistent entity's PreferredConstructor, which is evaluated during type evaluation by the SD Mapping Infrastructure, and can be specified with the #PersistenceContructor annotation, if necessary. This is useful in cases where you are using 1 of SD's canned EntityIntantiator types, e.g. ReflectionEntityInstantiator, and your application domain type has more than 1 non-default constructor.
Therefore, you can register 1 or more EntityInstantiator objects per application domain object by type using the EntityIntantiatiors composite class, perhaps with a "mapping" between application domain object Class type (e.g. Page) and EntityInstantiator, and then register the EntityInstantiators on SDG's MappingPdxSerializer.
Of course, you need to make sure that custom configured MappingPdxSerializer gets used by Apache Geode...
#Configuration
class ApacheGeodeConfiration {
#Bean
MappingPdxSerializer pdxSerializer() {
Map<Class<?>, EntityInstantiator> customInstantiators = new HashMap<>();
customInstantiators.put(Page.class, new MyPageEntityInstantiator());
customInstantiators.put...
MappingPdxSerializer pdxSerializer =
MappingPdxSerializer.newMappingPdxSerializer();
pdxSerializer.setGemfireInstantiators(
new EntityInstantiators(customInstantiators));
return pdxSerializer;
}
#Bean
CacheFactoryBean gemfireCache(MappingPdxSerializer pdxSerializer) {
CacheFactoryBean gemfireCache = new CacheFactoryBean();
gemfireCache.setPdxSerializer(pdxSerializer);
gemfireCache.set...
return gemfireCache;
}
...
}
Hope this helps!
-j

Resources