Spring Cloud Stream Source - Not Pulling - java

I'm trying to develop a customized Source for a proof of concept in Spring Cloud Dataflow.
I managed to deploy it correctly, but it seems that the bean is not pulled.
Here's a part of the parent pom.xml
...
<properties>
<spring-cloud.version>Hoxton.SR8</spring-cloud.version>
...
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
...
Here's the project pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binder-kafka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cloud-connectors</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
<version>2.3.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.4.RELEASE</version>
<configuration>
<excludes>
<exclude>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-app-starter-metadata-maven-plugin</artifactId>
<version>2.0.2.RELEASE</version>
<executions>
<execution>
<id>aggregate-metadata</id>
<phase>compile</phase>
<goals>
<goal>aggregate-metadata</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
SourceApplication.java
#SpringBootApplication
#EnableBinding(Source.class)
#EnableConfigurationProperties(ReportingProperties.class)
public class SourceApplication {
public static void main(String[] args) {
SpringApplication.run(SourceApplication.class, args);
}
}
ReportingProperties.java
#Validated
#ConfigurationProperties("reporting-properties")
public class ReportingProperties {
/**
* The starting date of the reporting.
*/
private LocalDateTime fromDate = LocalDateTime.now().minusDays(1000);
/**
* The end date of the reporting.
*/
private LocalDateTime toDate = LocalDateTime.now();
public LocalDateTime getFromDate() {
return fromDate;
}
public ReportingProperties setFromDate(LocalDateTime fromDate) {
this.fromDate = fromDate;
return this;
}
public LocalDateTime getToDate() {
return toDate;
}
public ReportingProperties setToDate(LocalDateTime toDate) {
this.toDate = toDate;
return this;
}
}
And finally the service :
#Configuration
#EnableBinding(Source.class)
public class PullUsersService {
#Bean
#Publisher(channel = Source.OUTPUT)
#SendTo(Source.OUTPUT)
public Supplier<String> pullUsers() {
return () -> "Test";
}
}
I'm wondering how to trigger the pulling mechanism so when deployed I can see "Test" in the logs
(I believe everything is setup correctly on SCDF, if I do "time | log" i can see some results in the log, but if I do "myservice | log" nothing appears.
What am I doing wrong ? (maybe there's some redundancy in my code)

The answer above is correct, you can also do it the following way:
Note: Remove all #EnableBinding(Source.class) anotations
#Component
public class PullUsersService {
#PollableBean
public Supplier<String> pullUsers() {
return () -> "Test";
}
}
Add the following configurations if you want queue to rabbitMq or kafka based on your binder configuration
spring.cloud.function.definition=pullUsers
spring.cloud.stream.bindings.pullUsers-out-0.destination=users
spring.cloud.stream.bindings.pullUsers-out-0.group=users-service
spring.cloud.stream.bindings.pullUsers-out-0.producer.requiredGroups=users-service
spring.cloud.stream.bindings.pullUsers-out-0.producer.requiredGroups configuration forces producer to create the queues

That's interesting what made you to use this:
#Publisher(channel = Source.OUTPUT)
#SendTo(Source.OUTPUT)
If you take a look into that time source code you mention, you'll see something like this:
#PollableSource
public String publishTime() {
return new SimpleDateFormat(this.triggerProperties.getDateFormat()).format(new Date());
}
Consider to use that #PollableSource instead and don't use a Supplier.
The point is that all your current annotations do nothing with polling.
The #Publisher works only when we call the method.
The #SendTo is fully ignored here since #Publisher does exactly the same and it "sends to".

Related

Java MongoDb getting id as a timestamp but need the hexadecimal string

I'm doing a CRUD for a collection named Schema and need to retrieve de _id as a hexadecimal string, but when i use the collection.find() i get the timestamp and date instead of the string.
I'm receving this structure:
{
"_id": {
"timestamp": 1604689898,
"date": "2020-11-06T19:11:38.000+00:00"
}
}
But i need something like this:
{
"_id": "5fa5a085a4b09b307d53ed57"
}
Here are my configurations
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.3.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>co.com.itau</groupId>
<artifactId>crypto-mongodb-java-ms</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>crypto-mongodb-java-ms</name>
<description>Microservice to save and read data from mongoDB using CSFLE</description>
<properties>
<java.version>1.8</java.version>
<version.fabric8-maven-plugin>3.5.41</version.fabric8-maven-plugin>
<swagger.version>3.0.0</swagger.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.plugin</groupId>
<artifactId>spring-plugin-core</artifactId>
<version>2.0.0.RELEASE</version>
</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-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web-services</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>${swagger.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.1.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.plugin</groupId>
<artifactId>spring-plugin-core</artifactId>
<version>2.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-crypt</artifactId>
<version>1.0.1</version>
</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>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>openshift</id>
<build>
<plugins>
<plugin>
<groupId>io.fabric8</groupId>
<artifactId>fabric8-maven-plugin</artifactId>
<version>${version.fabric8-maven-plugin}</version>
<executions>
<execution>
<id>fmp</id>
<phase>package</phase>
<goals>
<goal>resource</goal>
<goal>build</goal>
</goals>
</execution>
</executions>
<configuration>
<generator>
<config>
<spring-boot>
<fromMode>isTag</fromMode>
<from>redhat-openjdk18-openshift:1.2</from>
</spring-boot>
</config>
</generator>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
MongoClient configuration
CodecRegistry pojoCodecRegistry = fromProviders(PojoCodecProvider.builder().automatic(true).build());
CodecRegistry codecRegistry = fromRegistries(MongoClientSettings.getDefaultCodecRegistry(), pojoCodecRegistry);
MongoClient mongoAux = MongoClients.create(MongoClientSettings.builder()
.applyConnectionString(new ConnectionString(connectionString))
.codecRegistry(codecRegistry)
.build());
return mongoAux;
Schema Model
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Schema {
#JsonProperty("_id")
#SerializedName("_id")
public ObjectId id;
public String db;
public String collection;
public Document schema;
}
** Repository **
#Repository
public class SchemaMongoRepository implements SchemaRepository{
#Value("${mongodb.schema.db}")
private String mongoDatabase;
#Value("${mongodb.schema.collection}")
private String mongoCollection;
private static final Logger logger = LoggerFactory.getLogger(SchemaMongoRepository.class);
#Autowired
private MongoClient mongoClient;
#Autowired
private DataKey datakey;
private MongoCollection<Schema> schemaCollection;
#PostConstruct
void init() {
schemaCollection = mongoClient.getDatabase(mongoDatabase).getCollection(mongoCollection, Schema.class);
}
#Override
public List<Schema> getSchemas() {
List<Schema> schemaList = new ArrayList<Schema>();
FindIterable<Schema> result = schemaCollection.find();
for(Schema currentSchema: result) {
schemaList.add(currentSchema);
}
return schemaList;
}
}
The problem you face is ObjectId becomes extended (Deserialized). we make it serialization.Add this dependency in pom
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.11.0.rc1</version>
</dependency>
Add the following method in main class.
#Bean
public Jackson2ObjectMapperBuilderCustomizer customizer()
{
return builder -> builder.serializerByType(ObjectId.class,new ToStringSerializer());
}
This will give you expected output.
In my case, using Kotlin it was so simple as to add a Jackson JSON serializer:
#JsonSerialize(using = ToStringSerializer::class)
val id: ObjectId = ObjectId.get(),
In order to get:
{
"id": "622b34af87c70514f6452363",
...
}
instead of:
{
"id": {
"timestamp": 1646998703,
"date": "2022-03-11T11:38:23.000+00:00"
},
...
}

Spring microservice: Servlet.service() for servlet [jsp] threw exception

I work with a Spring microservice and received the following errors,
2020-01-20 09:58:22.504 ERROR 13758 --- [nio-8080-exec-1] o.a.c.c.C.[.[localhost].[/].[jsp] : Servlet.service() for servlet [jsp] threw exception
java.lang.ClassNotFoundException: org.apache.tomcat.util.security.Escape
at java.net.URLClassLoader.findClass(URLClassLoader.java:382) ~[na:1.8.0_211]
at java.lang.ClassLoader.loadClass(ClassLoader.java:424) ~[na:1.8.0_211]
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349) ~[na:1.8.0_211]
at java.lang.ClassLoader.loadClass(ClassLoader.java:357) ~[na:1.8.0_211]
at org.apache.jasper.compiler.JspUtil.getExprInXml(JspUtil.java:85) ~[tomcat-embed-jasper-9.0.29.jar:9.0.29]
at org.apache.jasper.compiler.PageDataImpl$SecondPassVisitor.printAttributes(PageDataImpl.java:736) ~[tomcat-embed-jasper-9.0.29.jar:9.0.29]
In the browser, its provided the error,
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Mon Jan 20 09:58:22 BDT 2020
There was an unexpected error (type=Internal Server Error, status=500).
org/apache/tomcat/util/security/Escape
I provide a few code snippets from the respective micro-service,
#SpringBootApplication
#EnableCircuitBreaker
#EnableDiscoveryClient
#EnableFeignClients
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.build();
}
}
The controller class,
#Controller
#Transactional
#RequestMapping("/website/vehicles")
public class VehicleController {
#Autowired
private VehicleRepository data;
#Autowired
private PositionTrackingExternalService externalService;
#PostMapping(value = "/newVehicle.html")
public String newVehicle(Vehicle vehicle) {
data.save(vehicle);
return "redirect:/website/vehicles/list.html";
}
#PostMapping(value = "/deleteVehicle.html")
public String deleteVehicle(#RequestParam Long id) {
data.delete(id);
return "redirect:/website/vehicles/list.html";
}
#GetMapping(value = "/newVehicle.html")
public ModelAndView renderNewVehicleForm() {
Vehicle newVehicle = new Vehicle();
return new ModelAndView("newVehicle", "form", newVehicle);
}
#GetMapping(value = "/list.html")
public ModelAndView vehicles() {
List<Vehicle> allVehicles = data.findAll();
return new ModelAndView("allVehicles", "vehicles", allVehicles);
}
...... ..... ...... ..... ...... .....
...... ..... ...... ..... ...... .....
}
The feign client,
#FeignClient(name = "fleetman-position-tracker")
public interface RemotePositionMicroserviceCalls {
#GetMapping(value = "/vehicles/{name}")
public Position getLatestPositionForVehicle(#PathVariable(value = "name") String name);
}
The service classes,
#Service
public class PositionTrackingExternalService {
#Autowired
private ObjectProvider<RemotePositionMicroserviceCalls> remoteService;
#Autowired
private VehicleRepository repository;
#HystrixCommand(fallbackMethod = "handleExternalServiceDown")
public Position getLatestPositionForVehicleFromRemoteMicroservice(String name) {
Position response = remoteService.getObject().getLatestPositionForVehicle(name);
response.setUpToDate(true);
return response;
}
public Position handleExternalServiceDown(String name) {
// Read the last known position for this vehicle
Position position = new Position();
Vehicle vehicle = repository.findByName(name);
position.setLat(vehicle.getLat());
position.setLongitude(vehicle.getLongitude());
position.setTimestamp(vehicle.getLastRecordedPosition());
position.setUpToDate(false);
return position;
}
}
The project structure,
As requested, the pom.xml file is provided below,
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.transport.visualizer</groupId>
<artifactId>transport-visualizer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>TransportVisualizer</name>
<description>Vehicle Transport Visualizer</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Camden.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
<resources>
<resource>
<filtering>true</filtering>
<directory>src/main/resources</directory>
<includes>
<include>application.properties</include>
</includes>
</resource>
</resources>
</build>
</project>
What's the issue here and how do I solve it?
Change your jasper dependency as
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope>
</dependency>
The dependency should be configured as with scope provided:
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope>
</dependency>

Jbehave 0 stories run

I'm new in java and jbehave. I wanted to try BDD with jbehave but seems doing something wrong - test run but step were not executed.
POJO CLASS:
public class Addition {
public int result;
public void addValues(int a,int b){
result=a+b;
}
public int getResult(){
return result;
}
}
STEPS CLASS:
public class AdditionStep {
private Addition calc;
#Given("open calc")
public void openCalc(){
calc=new Addition();
System.out.println("Opened calc");
}
#When("i add $number1 to $number2")
public void addition(#Named("number1")int a,#Named("number2")int b){
calc.addValues(a, b);
}
#Then("outcome is $result")
public void checkSum(#Named("result")int output){
Assert.assertEquals(output, calc.getResult());
System.out.println("Sum is : "+output);
}
}
STORY RUNNER:
public class AdditionStory extends JUnitStories {
#Override
public Configuration configuration(){
return new MostUsefulConfiguration().useStoryLoader(new LoadFromClasspath(this.getClass())).
useStoryReporterBuilder(new StoryReporterBuilder().withDefaultFormats().
withFormats(Format.CONSOLE,Format.TXT));
}
#Override
public InjectableStepsFactory stepsFactory(){
return new InstanceStepsFactory(configuration(),new AdditionStep());
}
#Override
protected List<String> storyPaths() {
return new StoryFinder().findPaths(CodeLocations.codeLocationFromClass(this.getClass()),"**/*addition*.story","");
}
}
STORY FILE:
Scenario: Add two valid numbers
Given open calc
When i add 1 to 1
Then outcome is 2`
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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>JbehaveCalculatorTest</groupId>
<artifactId>JbehaveCalculatorTest</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<jbehave.core.version>4.1</jbehave.core.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-library</artifactId>
<version>1.2.1</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.jbehave</groupId>
<artifactId>jbehave-core</artifactId>
<version>${jbehave.core.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.12</version>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
<version>1.3</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
<build>
<testResources>
<testResource>
<directory>src/test/java</directory>
<includes>
<include>**/*.story</include>
</includes>
</testResource>
</testResources>
<plugins>
<plugin>
<groupId>org.jbehave</groupId>
<artifactId>jbehave-maven-plugin</artifactId>
<version>${jbehave.core.version}</version>
<executions>
<execution>
<id>run-stories</id>
<phase>test</phase>
<configuration>
<!--<scope>test</scope>-->
<includes>
<include>**/stories/*.java</include>
</includes>
</configuration>
<goals>
<goal>run-stories-as-embeddables</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
My story file placed in src/test/java/Stories/...
OUTPUT:
Processing system properties {}
Using controls EmbedderControls[batch=false,skip=false,generateViewAfterStories=true,ignoreFailureInStories=false,ignoreFailureInView=false,verboseFailures=false,verboseFiltering=false,storyTimeouts=300,threads=1,failOnStoryTimeout=false]
(BeforeStories)
(AfterStories)
Generating reports view to 'D:\KnowledgeCentre\Workspace\JbehaveCalculatorTest\target\jbehave' using formats '[stats, console, txt]' and view properties '{navigator=ftl/jbehave-navigator.ftl, views=ftl/jbehave-views.ftl, reports=ftl/jbehave-reports.ftl, nonDecorated=ftl/jbehave-report-non-decorated.ftl, decorated=ftl/jbehave-report-decorated.ftl, maps=ftl/jbehave-maps.ftl}'
Reports view generated with 0 stories (of which 0 pending) containing 0 scenarios (of which 0 pending)
Disconnected from the target VM, address: '127.0.0.1:49908', transport: 'socket'
Process finished with exit code 0

Null Autowired Spring Bean (Cassandra Repository) in Service

I am getting a NullPointerException on an autowired bean in a service class. The class I'm trying to autowire is a Cassandra Repository.
My main class Application.java
#SpringBootApplication
#EnableAutoConfiguration
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
My Cassandra configuration CassandraConfig.java
#Configuration
#EnableCassandraRepositories(basePackages = "com.myretail")
public class CassandraConfig extends AbstractCassandraConfiguration {
#Override
protected String getKeyspaceName() {
return "myretail";
}
#Bean
public CassandraClusterFactoryBean cluster() {
CassandraClusterFactoryBean cluster =
new CassandraClusterFactoryBean();
cluster.setContactPoints("127.0.0.1");
cluster.setPort(9042);
return cluster;
}
#Bean
public CassandraMappingContext cassandraMapping()
throws ClassNotFoundException {
return new BasicCassandraMappingContext();
}
#Bean
public ProductService productService() {
return new ProductService();
}
}
My repository (dao) ProductPriceRepository.java
public interface ProductPriceRepository extends CassandraRepository<ProductPrice> {
#Query("select * from productprice where productId = ?0")
ProductPrice findByProductId(String productId);
}
My service class ProductService.java
#Path("/product")
#Component
public class ProductService {
#Autowired
ProductPriceRepository productPriceRepository;
#GET
#Path("/{id}")
#Produces(MediaType.APPLICATION_JSON)
public Product getTargetProduct(#PathParam("id") String productId) {
String urlString = "https://api.vendor.com/products/v3/" + productId + "?fields=descriptions&id_type=TCIN&key=43cJWpLjH8Z8oR18KdrZDBKAgLLQKJjz";
JSONObject json = null;
try {
json = new JSONObject(JsonReader.getExternalJsonResponse(urlString));
} catch (JSONException e) {
e.printStackTrace();
}
Product product = new Product();
product.setId(productId);
try {
JSONObject productCompositeResponse = json.getJSONObject("product_composite_response");
JSONArray items = productCompositeResponse.getJSONArray("items");
JSONObject item = items.getJSONObject(0);
JSONObject onlineDescription = item.getJSONObject("online_description");
product.setName(onlineDescription.getString("value"));
} catch (JSONException e) {
e.printStackTrace();
}
ProductPrice productPrice = productPriceRepository.findByProductId(productId);
product.setProductPrice(productPrice);
return product;
}
}
My pom.xml
<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 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.myretail</groupId>
<artifactId>MyRetail</artifactId>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>
<name>MyRetail</name>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>com.datastax.cassandra</groupId>
<artifactId>cassandra-driver-core</artifactId>
<version>2.1.5</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-cassandra</artifactId>
<version>1.4.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.cassandraunit</groupId>
<artifactId>cassandra-unit-spring</artifactId>
<version>2.1.9.2</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.cassandraunit</groupId>
<artifactId>cassandra-unit</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.cassandraunit</groupId>
<artifactId>cassandra-unit-shaded</artifactId>
<version>2.1.9.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hectorclient</groupId>
<artifactId>hector-core</artifactId>
<version>2.0-0</version>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-servlet</artifactId>
<version>1.18.3</version>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-json</artifactId>
<version>1.18.3</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>4.3.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>1.3.6.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<url>http://localhost:8080/manager/text</url>
<server>my-tomcat</server>
<path>/myRetail</path>
</configuration>
</plugin>
</plugins>
</build>
</project>
It is my understanding that the annotations should pick up the repository and create the bean based off of the #EnableCassandraRepositories annotation. The #Autowired ProductPriceRepository in ProductService.java is always null though when I run this on tomcat. HOWEVER, if I run a junit test against the service call, the bean is properly created, the object is not null, and the tests pass (via #ContextConfiguration annotation).
I've looked at a couple different patterns that I thought might help, but none of them have worked. I can't create an implementation of my interface because Cassandra handles that internally and I'm forced to implement the Cassandra methods.
I feel like something is just slightly off with the annotations somewhere. Any ideas?
The problem is with your pom.xml
For spring-boot Cassandra application, you have to include below dependencies and parent pom in pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.5.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</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-cassandra</artifactId>
</dependency>
</dependencies>

NodeEntity attributes are NULL after transaction closed when using AspectJ

I've got a strange problem when using AspectJ, Neo4j NodeEntities and transactions.
I'm developing a spring application and using spring-data-neo4j with aspectj weaving. The problem is, that my fetched entities are empty outside of the transaction. Without AspectJ everything works as expected.
Here is a full test-case with maven:
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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.test</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.4.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
<neo4j.version>2.1.7</neo4j.version>
<spring-data-commons.version>1.10.0.RELEASE</spring-data-commons.version>
<spring-data-neo4j.version>3.3.0.RELEASE</spring-data-neo4j.version>
</properties>
<dependencies>
<!-- spring dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
<version>${spring-data-commons.version}</version>
</dependency>
<!-- spring + neo4j dependencies -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-neo4j</artifactId>
<version>${spring-data-neo4j.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-neo4j-tx</artifactId>
<version>${spring-data-neo4j.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-neo4j-aspects</artifactId>
<version>${spring-data-neo4j.version}</version>
</dependency>
<!-- for aspectj -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
</dependency>
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>persistence-api</artifactId>
<version>1.0</version>
<optional>true</optional>
<scope>provided</scope>
</dependency>
<!-- for testing -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.neo4j</groupId>
<artifactId>neo4j-kernel</artifactId>
<version>${neo4j.version}</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>aspects</id>
<build>
<plugins>
<!-- disable default compiler -->
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<executions>
<execution>
<id>default-compile</id>
<phase>none</phase>
</execution>
</executions>
</plugin>
<!-- enable aspectj compiler -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.7</version>
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.5</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>1.8.5</version>
</dependency>
</dependencies>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
<configuration>
<outxml>true</outxml>
<aspectLibraries>
<aspectLibrary>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</aspectLibrary>
<aspectLibrary>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-neo4j-aspects</artifactId>
</aspectLibrary>
</aspectLibraries>
<complianceLevel>${java.version}</complianceLevel>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<repositories>
<repository>
<id>spring-libs-milestone</id>
<name>Spring Milestone</name>
<url>https://repo.spring.io/libs-milestone</url>
</repository>
</repositories>
</project>
demo/DemoNode.java
package demo;
import org.springframework.data.neo4j.annotation.GraphId;
import org.springframework.data.neo4j.annotation.Indexed;
import org.springframework.data.neo4j.annotation.NodeEntity;
#NodeEntity
public class DemoNode {
#GraphId
private Long id;
#Indexed(unique = true)
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
demo/DemoRepository.java
package demo;
import org.springframework.data.neo4j.repository.GraphRepository;
public interface DemoRepository extends GraphRepository<DemoNode> {
public DemoNode findByName(String name);
}
demo/DemoApplicationTests.java
package demo;
import static org.junit.Assert.assertEquals;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Transaction;
import org.neo4j.test.TestGraphDatabaseFactory;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.support.AnnotationConfigContextLoader;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.neo4j.aspects.config.Neo4jAspectConfiguration;
import org.springframework.data.neo4j.config.EnableNeo4jRepositories;
import org.springframework.data.neo4j.support.node.Neo4jHelper;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.EnableTransactionManagement;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(loader = AnnotationConfigContextLoader.class)
public class DemoApplicationTests {
#Configuration
#ComponentScan("demo")
#EnableTransactionManagement
#EnableNeo4jRepositories(basePackages = "demo")
public static class DemoConfiguration extends Neo4jAspectConfiguration {
public DemoConfiguration() {
setBasePackage("demo");
}
#Bean(destroyMethod = "shutdown")
public GraphDatabaseService graphDatabaseService() {
return new TestGraphDatabaseFactory().newImpermanentDatabase();
}
}
#Autowired
private GraphDatabaseService graphDbService;
#Autowired
private DemoRepository demoRepo;
#Before
public void resetDb() {
Neo4jHelper.cleanDb(graphDbService);
}
// NOT successful
#Test
public void testNeo4jTransaction() {
DemoNode node;
final String name = "demo";
try (Transaction tx = graphDbService.beginTx()) {
node = new DemoNode();
node.setName(name);
node = demoRepo.save(node);
tx.success();
}
assertEquals(name, node.getName());
try (Transaction tx = graphDbService.beginTx()) {
node = demoRepo.findByName(name);
tx.success();
}
assertEquals(name, node.getName()); // expected:<demo> but was:<null>
}
// successful
#Test
public void testNeo4jWithGetTransaction() {
DemoNode node;
final String name = "demo";
try (Transaction tx = graphDbService.beginTx()) {
node = new DemoNode();
node.setName(name);
node = demoRepo.save(node);
tx.success();
}
assertEquals(name, node.getName());
try (Transaction tx = graphDbService.beginTx()) {
node = demoRepo.findByName(name);
tx.success();
}
try (Transaction tx = graphDbService.beginTx()) {
assertEquals(name, node.getName());
}
}
}
When you test this with mvn test everything is fine. But when using aspectj (mvn test -Paspects) it will fail:
DemoApplicationTests.testNeo4jNativeTransaction:64 expected:<demo> but was:<null>
What I found out when debugging this: Inside the transaction with AspectJ I could retrieve the name with the getter. BUT: All properties are always null. So my guess is: AspectJ weaving stores the values somewhere else and overrides the setter/getter for this. The new getter requires an active transaction, so outside of the transaction it will not work/will use the default getter that doesn't work with the null properties.
When I'm increasing the debug level I could see some message regarding this:
DEBUG o.s.d.n.f.DetachedEntityState - Outside of transaction, GET value from field class java.lang.String name rel: false idx: true
Am I doing something wrong? Do I need to create a clone and return this? Imho this would be an ugly way ...
Thanks in advance! I'm already searched and tried multiple hours but couldn't find any solution and any other problems here on stackoverflow or somewhere else in the internet. Maybe it's a bug ... maybe expected behaviour or maybe I'm doing some wrong ... I don't know.
UPDATE
I added a second test that works. It has a transaction around the getter. So when the getter is called in a transaction everything is fine. But I think this is a curious behaviour. Outside of the transaction the entity should be detached, but usable.

Categories

Resources