AXON: Use MySql for EventStore and MongoDb for Projection - Events recreating records - axon

I have an existing app that saved collections in MongoDb. As mentioned in documentation and on https://www.youtube.com/watch?v=342tqAORbAM that MongoDB is not right and that I should use plain RDBMS like MySQL /- Axon Server.
I am able to do that but when I restart the server, all the events are replayed and I get collections repeated again.
Here's how my code looks like.
Below, I am using MongoDb for saving Products in ProductDB and MySQL for eventStore.
Application Yaml
spring:
data:
mongodb:
database: productsDB
port: 27017
host: localhost
mvc:
pathmatch:
matching-strategy: ant_path_matcher
datasource:
url: jdbc:mysql://localhost:3306/mysql?autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true
username: dinesh
password: abc123
name: mysql
jpa:
show-sql: true
hibernate:
ddl-auto: create
properties:
hibernate:
dialect: org.hibernate.dialect.MySQL8Dialect
##Axon configuration
axon:
serializer:
events: jackson
general: jackson
messages: jackson
In Pom.Xml, I tried excluding axon-server-connector and including as well.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
<version>2.7.6</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.7.6</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</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>
<version>${spring.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>${springfox.version}</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>${springfox.version}</version>
</dependency>
<dependency>
<groupId>org.axonframework</groupId>
<artifactId>axon-spring-boot-starter</artifactId>
<version>${axon-spring-boot-starter.version}</version>
<exclusions>
<exclusion>
<groupId>org.axonframework</groupId>
<artifactId>axon-server-connector</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>${commons-beanutils.version}</version>
</dependency>
<dependency>
<groupId>org.axonframework.extensions.mongo</groupId>
<artifactId>axon-mongo</artifactId>
<version>${axon-mongo.version}</version>
</dependency>
<dependency>
<groupId>org.axonframework</groupId>
<artifactId>axon-metrics</artifactId>
<version>${axon-metrics.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.0.31</version>
</dependency>
Here's my AxonConfig
#Configuration
public class AxonConfig {
#Bean
public EntityManagerProvider getEntityManagerProvider(){
return new ContainerManagedEntityManagerProvider();
}
/* #Bean
public EventStore eventStore(EventStorageEngine storageEngine, GlobalMetricRegistry metricRegistry) {
return EmbeddedEventStore.builder()
.storageEngine(storageEngine)
.messageMonitor(metricRegistry
.registerEventBus("eventStore"))
.build();
}*/
#Bean
public TokenStore tokenStore( EntityManagerProvider entityManagerProvider) {
return JpaTokenStore.builder().entityManagerProvider(entityManagerProvider)
.serializer(JacksonSerializer.defaultSerializer()).build();
}
#Bean
public EventStorageEngine eventStorageEngine(Serializer serializer,
PersistenceExceptionResolver persistenceExceptionResolver,
#Qualifier("eventSerializer") Serializer eventSerializer,
EntityManagerProvider entityManagerProvider,
TransactionManager transactionManager) throws SQLException {
JpaEventStorageEngine eventStorageEngine = JpaEventStorageEngine.builder()
.snapshotSerializer(serializer)
.persistenceExceptionResolver(persistenceExceptionResolver)
.eventSerializer(serializer)
.entityManagerProvider(entityManagerProvider)
.transactionManager(transactionManager)
.build();
return eventStorageEngine;
}
#Bean
public EventUpcasterChain eventUpcasters(){
return new EventUpcasterChain();
}
}
I start my axon server and Application.
Step 1: Http POST Create a new product (WORKS)
Step 2: On Axon Server dashboard , event is created (WORKS)
Step 3: In MongoDb, Product collection is created and entry can be verified (WORKS)
Step 4: Stop the Springboot applicatio and restart.
Step 5: MongoDb has an additional record because event was played again.
What's missing here?
Here's what I already tried:
Create EventStore also in MOngo (Works) but I don't want to use Mongo for eventStore and want to stick to MySql/PostgreSql for Axon server ( All examples on Youtube, documents just show with H2)
Tried adding and also removing the axon-server-connector (DomainEventEntry is not created in either case)
When I enable the code to exclude
<exclusions>
<exclusion>
<groupId>org.axonframework</groupId>
<artifactId>axon-server-connector</artifactId>
</exclusion>
</exclusions>
I get below error
2022-12-03 03:24:47.844 WARN 43476 --- [cessor[event]-0] o.a.e.TrackingEventProcessor : Fetch Segments for Processor 'event' failed: org.hibernate.hql.internal.ast.QuerySyntaxException: DomainEventEntry is not mapped [SELECT MIN(e.globalIndex) - 1 FROM DomainEventEntry e]. Preparing for retry in 1s
java.lang.IllegalArgumentException: org.hibernate.hql.internal.ast.QuerySyntaxException: DomainEventEntry is not mapped [SELECT MIN(e.globalIndex) - 1 FROM DomainEventEntry e]
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:138) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:181) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:188) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:757) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:848) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:114) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:311) ~[spring-orm-5.3.24.jar:5.3.24]
at com.sun.proxy.$Proxy147.createQuery(Unknown Source) ~[na:na]
at org.axonframework.eventsourcing.eventstore.jpa.JpaEventStorageEngine.createTailToken(JpaEventStorageEngine.java:361) ~[axon-eventsourcing-4.6.2.jar:4.6.2]
at org.axonframework.eventsourcing.eventstore.AbstractEventStore.createTailToken(AbstractEventStore.java:171) ~[axon-eventsourcing-4.6.2.jar:4.6.2]
at org.axonframework.eventhandling.TrackingEventProcessor$WorkerLauncher.lambda$run$1(TrackingEventProcessor.java:1218) ~[axon-messaging-4.6.2.jar:4.6.2]
at org.axonframework.common.transaction.TransactionManager.executeInTransaction(TransactionManager.java:47) ~[axon-messaging-4.6.2.jar:4.6.2]
at org.axonframework.eventhandling.TrackingEventProcessor$WorkerLauncher.run(TrackingEventProcessor.java:1216) ~[axon-messaging-4.6.2.jar:4.6.2]
at java.base/java.lang.Thread.run(Thread.java:834) ~[na:na]
Caused by: org.hibernate.hql.internal.ast.QuerySyntaxException: DomainEventEntry is not mapped [SELECT MIN(e.globalIndex) - 1 FROM DomainEventEntry e]
at org.hibernate.hql.internal.ast.QuerySyntaxException.generateQueryException(QuerySyntaxException.java:79) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
at org.hibernate.QueryException.wrapWithQueryString(QueryException.java:103) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.doCompile(QueryTranslatorImpl.java:220) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.compile(QueryTranslatorImpl.java:144) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
at org.hibernate.engine.query.spi.HQLQueryPlan.<init>(HQLQueryPlan.java:113) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
at org.hibernate.engine.query.spi.HQLQueryPlan.<init>(HQLQueryPlan.java:73) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
at org.hibernate.engine.query.spi.QueryPlanCache.getHQLQueryPlan(QueryPlanCache.java:162) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
at org.hibernate.internal.AbstractSharedSessionContract.getQueryPlan(AbstractSharedSessionContract.java:636) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:748) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
... 14 common frames omitted
Caused by: org.hibernate.hql.internal.ast.QuerySyntaxException: DomainEventEntry is not mapped
at org.hibernate.hql.internal.ast.util.SessionFactoryHelper.requireClassPersister(SessionFactoryHelper.java:170) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
at org.hibernate.hql.internal.ast.tree.FromElementFactory.addFromElement(FromElementFactory.java:91) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
at org.hibernate.hql.internal.ast.tree.FromClause.addFromElement(FromClause.java:77) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
at org.hibernate.hql.internal.ast.HqlSqlWalker.createFromElement(HqlSqlWalker.java:334) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.fromElement(HqlSqlBaseWalker.java:3782) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.fromElementList(HqlSqlBaseWalker.java:3671) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.fromClause(HqlSqlBaseWalker.java:746) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.query(HqlSqlBaseWalker.java:602) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.selectStatement(HqlSqlBaseWalker.java:339) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.statement(HqlSqlBaseWalker.java:287) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.analyze(QueryTranslatorImpl.java:276) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.doCompile(QueryTranslatorImpl.java:192) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
... 20 common frames omitted
What else can I try?

I found the issue. Here's short summary how I fixed it.
Addinng Entity Scan fixed the issue
#SpringBootApplication
#EnableSwagger2
**#EntityScan(basePackageClasses = {DomainEventEntry.class, SagaEntry.class, TokenEntry.class})**
public class EventEventApplication {
public static void main(String[] args) {
SpringApplication.run(EventEventApplication.class, args);
}
}
Here's the Axon documentation
https://docs.axoniq.io/reference-guide/axon-framework/spring-boot-integration#jpa-and-persistence-contexts
TLDR : Long explanation
I figured it out what was happening. I think this will help people who are getting same issue.
Summary:
Scenario 1
By default when you use
<dependency>
<groupId>org.axonframework</groupId>
<artifactId>axon-spring-boot-starter</artifactId>
<version>${axon-spring-boot-starter.version}</version>
</dependency>
the axon-server is included and app the wants to use the axon-server to store events. The Axon framework, will pick the first available JPA from you code and use it to store the events.
Ex: If you use "JUST" H2 or MySQL database, the "Your tables" along with Axon-server event tables like Saga/Token Entry etc are saved in the same schema. This is when you don't create any AxonConfiguration and override to create a different Schema.
Senario2:
You have a table called "Product" in schema "ProductDB". On an event handler senario, your data will get saved in ProductDB/Product table. If you have not added any Axon configuration class, AxonAutoConfigure will save the Axon-server table also in "ProductsDB" ex: ProductsDB/Token-entry.
If you want to keep Same DB but different Schemas ex: Products in ProductDB and Axon tables in AXONDB schema then you need to write AxonConfiguration, provide EntityManagerProvider, TokenStore and EventStorageEngine.
Scenario 3
This was my scenario. My entity Product is saved in MONGODB and I wanted to save Axon events in MySQL. Now I added Axon configuration and provide the three amigo beans
#Bean
public EntityManagerProvider getEntityManagerProvider(){
return new ContainerManagedEntityManagerProvider();
}
#Bean
public TokenStore tokenStore(EntityManagerProvider entityManagerProvider) {
return JpaTokenStore.builder().entityManagerProvider(entityManagerProvider)
.serializer(JacksonSerializer.defaultSerializer()).build();
}
#Bean
public EventStorageEngine eventStorageEngine(Serializer serializer,
PersistenceExceptionResolver persistenceExceptionResolver,
#Qualifier("eventSerializer") Serializer eventSerializer,
EntityManagerProvider entityManagerProvider,
TransactionManager transactionManager) throws SQLException {
JpaEventStorageEngine eventStorageEngine = JpaEventStorageEngine.builder()
.snapshotSerializer(serializer)
.persistenceExceptionResolver(persistenceExceptionResolver)
.eventSerializer(serializer)
.entityManagerProvider(entityManagerProvider)
.transactionManager(transactionManager)
.build();
return eventStorageEngine;
}
Now the problem as I stated was that everything was fine, but in MySQL, I was not seeing domain_entry_event table and snapshot_entry_event tables. I assumed it was OK as I was using AXON-SERVER. The problem came when I restarted the application and I saw that Events were getting reapplied which was already executed to save Product entry in DB.
Root Cause: When you use a Jpa then you must also override default configuration to tell axon to stop using Axon-server (exlude axon-server-connector from starter), then you must tell the persistent context to scan table
#EntityScan(basePackageClasses = {DomainEventEntry.class, SagaEntry.class, TokenEntry.class})
Addinng Entity Scan fixed the issue
#SpringBootApplication
#EnableSwagger2
**#EntityScan(basePackageClasses = {DomainEventEntry.class, SagaEntry.class, TokenEntry.class})**
public class EventEventApplication {
public static void main(String[] args) {
SpringApplication.run(EventEventApplication.class, args);
}
}

I figured it out what was happening. I think this will help people who are getting same issue.
Summary:
Scenario 1
By default when you use
<dependency>
<groupId>org.axonframework</groupId>
<artifactId>axon-spring-boot-starter</artifactId>
<version>${axon-spring-boot-starter.version}</version>
the axon-server is included and app the wants to use the axon-server to store events. The Axon framework, will pick the first available JPA from you code and use it to store the events.
Ex: If you use “JUST” H2 or MySQL database, the “Your tables” along with Axon-server event tables like Saga/Token Entry etc are saved in the same schema. This is when you don’t create any AxonConfiguration and override to create a different Schema.
Senario2:
You have a table called “Product” in schema “ProductDB”. On an event handler senario, your data will get saved in ProductDB/Product table. If you have not added any Axon configuration class, AxonAutoConfigure will save the Axon-server table also in “ProductsDB” ex: ProductsDB/Token-entry.
If you want to keep Same DB but different Schemas ex: Products in ProductDB and Axon tables in AXONDB schema then you need to write AxonConfiguration, provide EntityManagerProvider, TokenStore and EventStorageEngine.
Scenario 3
This was my scenario. My entity Product is saved in MONGODB and I wanted to save Axon events in MySQL. Now I added Axon configuration and provide the three amigo beans
#Bean
public EntityManagerProvider getEntityManagerProvider(){
return new ContainerManagedEntityManagerProvider();
}
#Bean
public TokenStore tokenStore(EntityManagerProvider entityManagerProvider) {
return JpaTokenStore.builder().entityManagerProvider(entityManagerProvider)
.serializer(JacksonSerializer.defaultSerializer()).build();
}
#Bean
public EventStorageEngine eventStorageEngine(Serializer serializer,
PersistenceExceptionResolver persistenceExceptionResolver,
#Qualifier("eventSerializer") Serializer eventSerializer,
EntityManagerProvider entityManagerProvider,
TransactionManager transactionManager) throws SQLException {
JpaEventStorageEngine eventStorageEngine = JpaEventStorageEngine.builder()
.snapshotSerializer(serializer)
.persistenceExceptionResolver(persistenceExceptionResolver)
.eventSerializer(serializer)
.entityManagerProvider(entityManagerProvider)
.transactionManager(transactionManager)
.build();
return eventStorageEngine;
}
Now the problem as I stated was that everything was fine, but in MySQL, I was not seeing domain_entry_event table and snapshot_entry_event tables. I assumed it was OK as I was using AXON-SERVER. The problem came when I restarted the application and I saw that Events were getting reapplied which was already executed to save Product entry in DB.
Root Cause: When you use a Jpa then you must also override default configuration to tell axon to stop using Axon-server (exclude axon-server-connector from starter), then you must tell the persistent context to scan table
#EntityScan(basePackageClasses = {DomainEventEntry.class, SagaEntry.class, TokenEntry.class})
Adding Entity Scan fixed the issue
#SpringBootApplication
#EnableSwagger2
**#EntityScan(basePackageClasses = {DomainEventEntry.class, SagaEntry.class, TokenEntry.class})**
public class EventEventApplication {
public static void main(String[] args) {
SpringApplication.run(EventEventApplication.class, args);
}
}

Related

Spring Security 6 and JSP view rendering

I'm upgrading an application from Spring Boot 2.7 to Spring Boot 3 which includes updating to Spring Security 6.
We have the following properties set:
spring.mvc.view.prefix=/WEB-INF/view/
spring.mvc.view.suffix=.jsp
We use JSPs as a template language, where the controller returns the view file name e.g.
#RequestMapping("/")
public String home() {
return "home";
}
This will render the JSP page /WEB-INF/view/home.jsp
The security config is e.g.
#Configuration
public class SecurityConfig {
#Bean
public SecurityFilterChain config(HttpSecurity http) throws Exception {
http.authorizeHttpRequests((auth) -> auth
.requestMatchers("/").permitAll()
.anyRequest().authenticated()
);
}
Since upgrading, visiting localhost/ redirects the browser to localhost/WEB-INF/view/home.jsp and this returns a 403 because access to that page isn't permitted.
If I allow access to this with .requestMatchers("/", "/WEB-INF/**").permitAll() it works OK (i.e. stays on / and renders the JSP page) but this seems like a bad idea, and an unnecessary step.
With debug logging on, Spring logs the following:
DEBUG [requestURL=/] o.s.security.web.FilterChainProxy : Securing GET /
DEBUG [requestURL=/] o.s.security.web.FilterChainProxy : Secured GET /
DEBUG [requestURL=/] o.s.security.web.FilterChainProxy : Securing GET /WEB-INF/view/home.jsp
DEBUG [requestURL=/] o.s.security.web.FilterChainProxy : Secured GET /WEB-INF/view/home.jsp
I cant' see anything in Spring Security migration guide about this, does anyone know what is going on?
Update
I've isolated this into a clean example:
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>3.0.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>jsptest</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>jsptest</name>
<packaging>war</packaging>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
</dependencies>
<build>
<finalName>app</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Application.java
#SpringBootApplication
#Controller
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public SecurityFilterChain config(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((auth) -> auth
.requestMatchers("/", "/WEB-INF/view/**").permitAll()
.anyRequest().authenticated()
);
return http.build();
}
#RequestMapping("/")
public String home() {
return "home";
}
}
src/main/resources/application.properties:
spring.mvc.view.prefix=/WEB-INF/view/
spring.mvc.view.suffix=.jsp
src/main/webapp/WEB-INF/view/home.jsp:
hello
Something I've overlooked first as well.
In spring security 6, forwards and includes are part of the security filter by default.
See https://docs.spring.io/spring-security/reference/5.8.0/migration/servlet/authorization.html#_permit_forward_when_using_spring_mvc
Allowing forwards globally can be done through this additional line in security configuration.
.dispatcherTypeMatchers(DispatcherType.FORWARD).permitAll()

Spring MVC Processing multipart form data

Using Craig Walls book "Spring in Action, 4th Edition", 7.2. Processing multipart form data
The code does not run no matter what path you try. I even tried
C:\something. Don't you have to create the directory first?
When I run the code, I get the error below:
root cause org.springframework.web.multipart.MultipartException:
Could not parse multipart servlet request; nested exception is
java.io.IOException: The temporary upload location
[C:\Users\jokra_000\sts-bundle\pivotal-tc-server-developer-3.1.1.RELEASE\base-instance\work\Catalina\localhost\spittr\tmp\spittr\uploads]
is not valid
Has anyone successfully uploaded an image file as outlined in Chapter 7? It seems there's far more to it than what's described, as the code Craig supplied does not run. Instead it crashes and will not upload a file.
Any suggestions on how to implement MultipartFile and the Path?
Craig's suggestion:
in AbstractAnnotationConfigDispatcherServletInitializer:
#Override
protected void customizeRegistration(Dynamic registration) {
registration.setMultipartConfig(
new MultipartConfigElement("/tmp/spittr/uploads", 2097152, 4194304, 0));
}
in Controller processRegistration:
MultipartFile profilePicture = spitterForm.getProfilePicture();
profilePicture.transferTo(new File("/tmp/spittr/" + spitter.getUsername() + ".jpg"));
It worked when I did the following:
I changed MultipartResolver:
#Bean
public MultipartResolver multipartResolver() throws IOException {
return new CommonsMultipartResolver();
}
and also added the dependency:
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.2</version>
</dependency>
and then imported to the file that extends WebMvcConfigurerAdapter:
import org.apache.commons.fileupload.FileItemFactory;

spring-boot spring-mvc apache-commons-fileupload: Required MultipartFile parameter 'file' is not present error

I'm trying to make the example of this link: https://github.com/spring-guides/gs-uploading-files work with Apcahe commons-fileuploadbut the error: Required MultipartFile parameter 'file' is not present is occurring.
The configuration that I'm making on the example is:
1 - Adding the dependency of commons-fileupload in the pom.xml:
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
2 - Config the multipartResolver() in the Application.java
#Bean
public MultipartResolver multipartResolver() {
return new CommonsMultipartResolver();
}
Without this configuration everything works fine as Spring is using org.springframework.web.multipart.support.StandardServletMultipartResolver for handle uploads, but I want use commons-fileupload.
There is something that I'm missing?
Thanks.
#avaz
You should disable MultipartAutoConfiguration like this below:
#EnableAutoConfiguration(exclude = MultipartAutoConfiguration.class)
Please see the DOC

Wrong context used to inject dependency in environment with servlet- and portlet-context

I am currently working on an application, that is accessed as both a portlet and a servlet (at least some parts are). I am also using compile-time-weaving to inject dependencies into beans in the prototype scope, so for example beans that are created via "new" or more commonly via Hibernate, as described here: http://www.chrissearle.org/node/285
This is working fine so far, however I am now using a server-api that has to be called differently in servlets and portlets. So I created an interface for a service in my application, and two implementations to the interface, one for servlets and one for portlets, so each can use the server-api differently. Each implementation is configured only in the coresponding servlet/portlet-application-context.
If the servlet is used first, this works fine, the service-implementation for the servlet is injected and used. However, once the portlet is used, only the service-implementation for the portlet will be injected and used, for both the portlet and the servlet.
I would have expected the containers for servlets and portlets to be separate. Is this an error or not supported by Spring, or is there a way to fix or work around this? The application is still using Spring 3.1.1, however the current version 4.1.1 is showing the same behaviour.
My configuration looks like this (massivly simplified):
Interface: MyService:
public interface MyService {
public String getText();
}
MyService-Implementation for Portlet:
public class MyPortletService implements MyService {
#Override
public String getText() {
return this.getClass().toString();
}
}
MyService-Implementation for Servlet:
public class MyServletService implements MyService {
#Override
public String getText() {
return this.getClass().toString();
}
}
Bean to be used by Servlet and Portlet:
#Configurable
public class MyBean {
#Autowired
private MyService myService;
public String getText() {
return myService.getText();
}
}
Call in Portlet- and Servlet-Controller:
logger.trace("new MyBean().getText(): " + new MyBean().getText());
servlet-application-context.xml:
<bean class="my.app.PortletController" />
<bean class="my.app.MyServletService"/>
portet-application-context.xml:
<bean class="my.app.ServletController" />
<bean class="my.app.MyPortletService"/>
relevant dependencies in pom.xml
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.7.4</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>3.1.1.RELEASE</version>
</dependency> -->
compile-time-weaving configuration:
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.6</version>
<configuration>
<complianceLevel>1.6</complianceLevel>
<aspectLibraries>
<aspectLibrary>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</aspectLibrary>
</aspectLibraries>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
Log output if calling servlet, portlet, servlet in that order:
new MyBean().getText(): class my.app.MyServletService
new MyBean().getText(): class my.app.MyPortletService
new MyBean().getText(): class my.app.MyPortletService
Update 2014-11-27:
I have now created a test-case outlining the problem:
https://github.com/ChrZae/servlet-portlet-spring-container-issue

The web application [] registered the JDBC driver [com.mysql.jdbc.Driver] but failed to unregister it when the web application was stopped

I've seen few similar issues on stackoverflow but i could not figure out how i can solve my problem. After adding Spring Security to my Spring MVC project i got following exception:
Jul 20, 2014 3:18:04 PM org.apache.catalina.loader.WebappClassLoader clearReferencesJdbc
SEVERE: The web application [] registered the JDBC driver [com.mysql.jdbc.Driver] but failed to unregister it when the web application was stopped. To prevent a memory leak, the JDBC Driver has been forcibly unregistered.
Here is my mysql-connecter in the pom.xml
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.31</version>
</dependency>
Here are classes that i've added:
#Component
#Transactional
public class UserDetailsServiceImpl implements UserDetailsService{
#Autowired
private UserDAO userDAO;
#Autowired
private UserAssembler userAssembler;
private static final Logger logger = LoggerFactory.getLogger(UserDetailsServiceImpl.class);
#Transactional(readOnly = true)
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
User user = userDAO.findByEmail(username);
if(null == user) throw new UsernameNotFoundException("User not found");
return userAssembler.buildUserFromUser(user);
}
}
and assembler
#Service("assembler")
public class UserAssembler {
#Autowired
private UserDAO userDAO;
#Transactional(readOnly = true)
public User buildUserFromUser(net.viralpatel.contact.model.User user) {
String role = "ROLE_USER";//userEntityDAO.getRoleFromUserEntity(userEntity);
Collection<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
authorities.add(new GrantedAuthorityImpl(role));
return new User(user.getLogin(), user.getPassword(), true, true, true, true, authorities);
}
}
Here is my spring-security.xml
<beans:bean id="webexpressionHandler" class="org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler" />
<http auto-config="true">
<intercept-url pattern="/account/*" access="ROLE_ADMIN" />
<form-login login-page="/login" default-target-url="/account/overview" authentication-failure-url="/login?error=true"/>
<remember-me/>
</http>
<beans:bean id="myUserDetailsService" class="net.viralpatel.contact.service.UserDetailsServiceImpl" />
<authentication-manager alias="authenticationManager">
<authentication-provider user-service-ref="myUserDetailsService" />
</authentication-manager>
EDITED:
INFO: The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: /opt/idea-IU-135.909/bin::/usr/java/packages/lib/amd64:/usr/lib64:/lib64:/lib:/usr/lib
Jul 20, 2014 3:58:36 PM org.apache.catalina.core.JreMemoryLeakPreventionListener lifecycleEvent
SEVERE: Failed to load class com.mysql.jdbc.NonRegisteringDriver during Tomcat start to prevent possible memory leaks.
java.lang.ClassNotFoundException: com.mysql.jdbc.NonRegisteringDriver
Your application doesn't have a flaw. It is the design of JDBC. The JDBC driver gets loaded and registered by the webapp when it creates a database connection for the first time.
That means that the driver is loaded with the web application class loader. On undeployment the driver doesn't get deregistered which in turn prevents your webapp classes from GC. That creates effectively a memory leak.
To prevent this particular memory leak you should edit your tomcat/conf/server.xml and change
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
to
<Listener
className="org.apache.catalina.core.JreMemoryLeakPreventionListener"
classesToInitialize="com.mysql.jdbc.NonRegisteringDriver" />
With mysql-connector-java-8.0.x use com.mysql.cj.jdbc.NonRegisteringDriver instead
Exclude the JDBC driver from your webapp artifact and put it into the tomcat/lib directory.
Now the JDBC driver gets loaded by Tomcat on startup and isn't linked to any webapps class loader.
Why should I modify the server.xml?
Another memory leaks manifests due to MySQL's 'Abandoned connection cleanup thread'. This thread starts with the first request and holds a reference to the webapp's classloader. With classesToInitialize you can prevent this memory leak too.
References:
org.apache.catalina.core.JreMemoryLeakPreventionListener tomcat-doc v7.0
AbandonedConnectionCleanupThread notes in v5.1.41
com.mysql.jdbc.NonRegisteringDriver source v5.1
com.mysql.cj.jdbc.NonRegisteringDriver source v8.0
mysql-connector-java changes in v8.0
What I did was just to put the mysql-connector-java-5.1.31-bin.jar in $CATALINA_HOME/lib. No modifications to server.xml.

Resources