On base of the guide from this blog, Roll your own Auto Discovery with Jersey and HK2, I have the follow resource POJO:
#Path("Test")
public class TestResource {
#Inject
private TestService service;
#GET
#Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public Set<Test> getTests() {
return service.getAllTests();
}
}
The TestService:
#Contract
public interface TestService {
public Set<Test> getAllTests();
}
The TestServiceImpl
#Service
public class TestServiceImpl implements TestService {
#Override
public Set<Test> getAllTests() {
Set<Test> tests = new HashSet<>();
Test c = new Test();
c.setName("test");
tests.add(c);
return tests;
}
}
The Jersey dependency in pom.xml is of version 2.25.1
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.glassfish.jersey</groupId>
<artifactId>jersey-bom</artifactId>
<version>${jersey.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>javax.ws.rs-api</artifactId>
<version>2.0</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet-core</artifactId>
<!-- use the following artifactId if you don't need servlet 2.x compatibility -->
<!-- artifactId>jersey-container-servlet</artifactId -->
</dependency>
<dependency>
<groupId>org.glassfish.jersey.bundles</groupId>
<artifactId>jaxrs-ri</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.hk2</groupId>
<artifactId>hk2</artifactId>
<version>2.5.0-b36</version>
</dependency>
In order to make Jersey scan the #Service and #Contract classes automatically, I used the inhabitant-generator plugin with version 2.5.0-b36:
<plugin>
<groupId>org.glassfish.hk2</groupId>
<artifactId>hk2-inhabitant-generator</artifactId>
<version>2.5.0-b36</version>
<executions>
<execution>
<goals>
<goal>generate-inhabitants</goal>
</goals>
</execution>
</executions>
</plugin>
There is the corresponding Feature implementation:
public class AutoServiceDiscovery implements Feature {
#Override
public boolean configure(FeatureContext context) {
ServiceLocator locator = ServiceLocatorProvider.getServiceLocator(context);
DynamicConfigurationService dcs = locator.getService(DynamicConfigurationService.class);
Populator populator = dcs.getPopulator();
try {
populator.populate(new ClasspathDescriptorFileFinder(this.getClass().getClassLoader()),
new DuplicatePostProcessor());
} catch (IOException | MultiException ex) {
Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, null, ex);
}
return true;
}
}
And it is indeeded registered through my ResourceConfig class:
#ApplicationPath("/*")
public class ApplicationConfig extends ResourceConfig {
public ApplicationConfig() {
packages("resources");
register(new AutoServiceDiscovery());
}
}
However, I send request to the /test, got the following error:
MultiException has 3 exceptions. They are:
1. org.glassfish.hk2.api.UnsatisfiedDependencyException: There was no object available for
injection at SystemInjecteeImpl(requiredType=TestService,parent=TestResource,qualifiers=
{},position=-1,optional=false,self=false,unqualified=null,1947073589)
2. java.lang.IllegalArgumentException: While attempting to resolve the dependencies of
rx.practice.ee.jaxrs.resources.TestResource errors were found
3. java.lang.IllegalStateException: Unable to perform operation: resolve on
rx.practice.ee.jaxrs.resources.TestResource
org.jvnet.hk2.internal.Collector.throwIfErrors(Collector.java:89)
org.jvnet.hk2.internal.ClazzCreator.resolveAllDependencies(ClazzCreator.java:250)
org.jvnet.hk2.internal.ClazzCreator.create(ClazzCreator.java:358)
org.jvnet.hk2.internal.SystemDescriptor.create(SystemDescriptor.java:487)
org.glassfish.jersey.process.internal.RequestScope.findOrCreate(RequestScope.java:162)
...
Question: Anyone knows why the #Service class cannot be injected? I am using Tomcat server
After a couple of days research on the source code of inhabitat-generator, I figured out that in case of web application package,war, the locator file is not generated in META-INF/hk2-locator as demonstracted in the HK2 Inhabitant Generator office site in case of using jar as deployment package. The source code of AbstractInhabitantsGeneratorMojo.java told that in case of war, locator files are generated in hk2-locator, and this is not mentioned in the HK2 Inhabitant Generator office site.
However, when constructing the ClasspathDescriptorFileFinder without the directory names argument in the bootstrap class, AutoServiceDiscovery, it is only compatible with jar as deployment package, meaning it is only finding files in META-INF/hk2-locator.
So the better solution would be not to use inhabitant-generator plugin but the metadata-generator dependency, which is an annotation processor at compile time and, it is proved out-of-the-box.
If someone is persistent to using this plugin, he/she could create his/her own ClasspathDescriptorFileFinder so that it is able to find locator files from hk2-locator
Last but not least, I also tried to use the inhabitants-generator plugin's options to generate the locator files in hk2-locator, but this seems to be next to impossible as well
Related
I have a code that uses AspectJ. I use the compile-time weaving mode. During context initialization, I get an error. Although everything worked before that.
annotation
#Retention(RUNTIME)
#Target(METHOD)
#Documented
public #interface AuditAnnotation {
public String value() default "";;
}
LoggingInterceptorAspect
#Aspect
public class LoggingInterceptorAspect {
private LoggingService loggingService;
#Autowired
public LoggingInterceptorAspect(LoggingService loggingService) {
this.loggingService = loggingService;
}
#Pointcut("execution(private * *(..))")
public void privateMethod() {}
#Pointcut("#annotation(com.aspectj.in.spring.boot.aop.aspect.auditlog.annotation.AuditAnnotation)")
public void annotatedMethodCustom() {}
#Before("annotatedMethodCustom() && privateMethod()")
public void addCommandDetailsToMessage() throws Throwable {
ZonedDateTime dateTime = ZonedDateTime.now(ZoneOffset.UTC);
String message = String.format("User controller getUsers method called at %s", dateTime);
System.out.println("+++++++++++++++++++++++++");
loggingService.log(message);
}
}
LoggingInterceptorConfig (It is the error here.)
#Configuration
public class LoggingInterceptorConfig {
#Bean
public LoggingInterceptorAspect getAutowireCapableLoggingInterceptor() {
return Aspects.aspectOf(LoggingInterceptorAspect.class);
}
}
Here is an error in this line:
return Aspects.aspectOf(LoggingInterceptorAspect.class);
exception
ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'getAutowireCapableLoggingInterceptor' defined in class path resource [com/aspectj/in/spring/boot/aop/aspect/auditlog/interceptor/config/LoggingInterceptorConfig.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.aspectj.in.spring.boot.aop.aspect.auditlog.interceptor.LoggingInterceptorAspect]: Factory method 'getAutowireCapableLoggingInterceptor' threw exception; nested exception is org.aspectj.lang.NoAspectBoundException: Exception while initializing com.aspectj.in.spring.boot.aop.aspect.auditlog.interceptor.LoggingInterceptorAspect: java.lang.NoSuchMethodException: com.aspectj.in.spring.boot.aop.aspect.auditlog.interceptor.LoggingInterceptorAspect.aspectOf()
pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.aspectj.in.spring.boot</groupId>
<artifactId>aspectj-in-spring-boot</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>aspectj-in-spring-boot</name>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<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</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>3.0.3</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.9</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</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>
<plugin>
<groupId>com.nickwongdev</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.12.6</version>
<configuration>
<complianceLevel>11</complianceLevel>
<source>11</source>
<target>11</target>
<showWeaveInfo>true</showWeaveInfo>
<verbose>true</verbose>
<Xlint>ignore</Xlint>
<encoding>UTF-8</encoding>
<aspectLibraries>
<aspectLibrary>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</aspectLibrary>
</aspectLibraries>
<showWeaveInfo>true</showWeaveInfo>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
With the help of reflection, all public methods defined in LoggingInterceptorAspect.class. But why is null returned?
Maybe someone has some ideas why initialization is not happening LoggingInterceptorAspect.class
#Aspect
#Component
public class LoggingInterceptorAspect {
With #Component you register a bean of type LoggingInterceptorAspect in the application context.
#Bean
public LoggingInterceptorAspect getAutowireCapableLoggingInterceptor() {
Here with #Bean you register again another bean of type LoggingInterceptorAspect in the application context
Why register 2 beans of the same type when both are singletons?
Thanks for the MCVE on GitHub. Having access to it, helped me to easily identify your problems as follows:
Your dynamic #annotation() pointcut is quite broad, targeting all packages. I recommend to additionally add within() in order to limit the aspect scope.
In order to auto-inject the logger into the aspect, you want to use a setter instead of the constructor, because AspectJ aspects are expected to have default constructors.
Pointcut expression privateMethod() && publicMethod() will never match, because a method cannot be public and private at the same time. You want to use || instead. Or you can simply omit both pointcuts if you want to match both anyway. Also be careful, because in addition to public and private methods there are protected and package-scoped methods too, which you will be excluding if you are only targeting public and private ones.
Your aspect should look as follows:
package com.aspectj.in.spring.boot.aop.aspect.auditlog.interceptor;
import com.aspectj.in.spring.boot.service.LoggingService;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
#Aspect
public class LoggingInterceptorAspect {
private LoggingService loggingService;
#Autowired
public void setLoggingService(LoggingService loggingService) {
this.loggingService = loggingService;
}
#Pointcut("execution(private * *(..))")
public void privateMethod() {}
#Pointcut("execution(public * *(..))")
public void publicMethod() {}
#Pointcut("#annotation(com.aspectj.in.spring.boot.aop.aspect.auditlog.annotation.AuditAnnotation)")
public void annotatedMethodCustom() {}
#Pointcut("within(com.aspectj.in.spring.boot..*)")
public void applicationScoped() {}
#Before("annotatedMethodCustom() && applicationScoped()")
//#Before("annotatedMethodCustom() && applicationScoped() && (privateMethod() || publicMethod())")
public void addCommandDetailsToMessage(JoinPoint joinPoint) throws Throwable {
ZonedDateTime dateTime = ZonedDateTime.now(ZoneOffset.UTC);
String message = String.format("User controller getUsers method called at %s", dateTime);
System.out.println("+++ " + joinPoint);
loggingService.log(message);
}
}
Update: I forgot to mention one possible problem in aspects which do not explicitly use execution() because they want to match all methods: When using #annotation() only, you are targetting both call() and execution() joinpoints, as you can see in the compiler log:
[INFO] Join point 'method-call(java.util.List com.aspectj.in.spring.boot.controller.UserController.getUsersInternal())' in Type 'com.aspectj.in.spring.boot.controller.UserController' (UserController.java:26) advised by before advice from 'com.aspectj.in.spring.boot.aop.aspect.auditlog.interceptor.LoggingInterceptorAspect' (LoggingInterceptorAspect.java:37)
[INFO] Join point 'method-execution(java.util.List com.aspectj.in.spring.boot.controller.UserController.getUsersInternal())' in Type 'com.aspectj.in.spring.boot.controller.UserController' (UserController.java:32) advised by before advice from 'com.aspectj.in.spring.boot.aop.aspect.auditlog.interceptor.LoggingInterceptorAspect' (LoggingInterceptorAspect.java:37)
CLASSPATH component C:\Program Files\JetBrains\IntelliJ IDEA 2018.3\plugins\maven\lib\maven3\boot\plexus-classworlds.license: java.util.zip.ZipException: zip END header not found
[INFO] Join point 'method-execution(java.util.List com.aspectj.in.spring.boot.service.impl.DefaultUserService.getMockUsers())' in Type 'com.aspectj.in.spring.boot.service.impl.DefaultUserService' (DefaultUserService.java:34) advised by around advice from 'org.springframework.cache.aspectj.AnnotationCacheAspect' (spring-aspects-5.3.9.jar!AbstractCacheAspect.class:64(from AbstractCacheAspect.aj))
This also leads to duplicate runtime logging output when omitting the && (privateMethod() || publicMethod()) condition:
+++ call(List com.aspectj.in.spring.boot.controller.UserController.getUsersInternal())
2021-09-04 16:11:41.213 INFO 17948 --- [o-auto-1-exec-1] sample-spring-aspectj : User controller getUsers method called at 2021-09-04T14:11:41.210203700Z
+++ execution(List com.aspectj.in.spring.boot.controller.UserController.getUsersInternal())
In order to avoid that, you should add a generic execution pointcut:
#Before("annotatedMethodCustom() && applicationScoped() && execution(* *(..))")
When I add custom revision entity, I start getting error:
2020-12-13 00:22:29.418 ERROR 80983 --- [ost-startStop-1] o.s.b.web.embedded.tomcat.TomcatStarter : Error starting Tomcat context. Exception: org.springframework.beans.factory.UnsatisfiedDependencyException. Message: Error creating bean with name 'webSecurityConfig': Unsatisfied dependency expressed through field 'userDetailsService'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userDetailsServiceImpl': Unsatisfied dependency expressed through field 'userRepository'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userRepository': Cannot create inner bean '(inner bean)#4384acd' of type [org.springframework.orm.jpa.SharedEntityManagerCreator] while setting bean property 'entityManager'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name '(inner bean)#4384acd': Cannot resolve reference to bean 'entityManagerFactory' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Invocation of init method failed; nested exception is java.lang.NoClassDefFoundError: org/hibernate/resource/beans/spi/ManagedBeanRegistry
MyRevision:
package ...;
import org.hibernate.envers.DefaultRevisionEntity;
import org.hibernate.envers.RevisionEntity;
import javax.persistence.Entity;
#Entity
#RevisionEntity(MyRevisionListener.class)
public class MyRevision extends DefaultRevisionEntity {
private String username;
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
}
MyRevisionListener:
package ...;
// import de.xxxxx.carorderprocess.models.User;
import org.hibernate.envers.RevisionListener;
// import org.springframework.security.core.Authentication;
// import org.springframework.security.core.context.SecurityContext;
// import org.springframework.security.core.context.SecurityContextHolder;
// import java.util.Optional;
public class MyRevisionListener implements RevisionListener {
#Override
public void newRevision(Object revisionEntity) {
/* String currentUser = Optional.ofNullable(SecurityContextHolder.getContext())
.map(SecurityContext::getAuthentication)
.filter(Authentication::isAuthenticated)
.map(Authentication::getPrincipal)
.map(User.class::cast)
.map(User::getUsername)
.orElse("Unknown-User"); */
MyRevision audit = (MyRevision) revisionEntity;
audit.setUsername("dd");
}
}
WebSecurityConfig:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
UserDetailsServiceImpl userDetailsService;
UserDetailsServiceImpl:
#Service
public class UserDetailsServiceImpl implements UserDetailsService {
#Autowired
UserRepository userRepository;
#Override
#Transactional
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User Not Found with username: " + username));
return UserDetailsImpl.build(user);
}
}
UserRepository:
#Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
Boolean existsByUsername(String username);
}
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.0.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>de.xxxxxxx</groupId>
<artifactId>carorderprocess</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>carorderprocess</name>
<description>Demo project for Spring Boot</description>
<dependencyManagement>
<dependencies>
</dependencies>
</dependencyManagement>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</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-mail</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</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-jdbc</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.17</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.16</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>persistence-api</artifactId>
<version>1.0.2</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.5.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-envers</artifactId>
<version>2.4.1</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-envers</artifactId>
<version>5.4.25.Final</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>
<executions>
<execution>
<id>compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
I think your problem could be related with the different dependencies in your pom.xml.
Please, first, remove the spring-data-envers dependency, unless you are querying your audit tables you do not need it. Even in that case, you can use Envers on its own to obtain that information if required.
Be aware that, as indicated in the comments of the answer from Sunit, you will need to remove the attribute repositoryFactoryBeanClass, it could not longer take the value EnversRevisionRepositoryFactoryBean. But you probably still need to include the #EnableJpaRepositories annotation.
Although I initially indicated that you can let Spring Boot manage your versions, due to the one of spring-boot-starter-parent, the framework is providing you versions of hibernate-xxx similar to 5.2.17.Final.
But, as you indicated, you need to use the method forRevisionsOfEntityWithChanges for querying your audit entities. As you can see in the java docs, that method was introduced in AuditQueryCreator in version 5.3.
As a consequence, you need to provide the following dependency:
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-envers</artifactId>
<version>5.3.20.Final</version>
</dependency>
But in addition you also need to provide a compatible version of both hibernate-entitymanager and hibernate-core:
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>5.3.20.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.3.20.Final</version>
</dependency>
From what I understood from all the comments above, your requirement is
to use Envers Auditing
and use method forRevisionsOfEntityWithChanges to get list of all revisions with what changed in them
Please start by doing these
Remove dependency of spring-data-envers library.
Just keep library hibernate-envers - version 5.4.23.Final also worked for me
Remove repositoryFactoryBeanClass = EnversRevisionRepositoryFactoryBean.class from #EnableJpaRepositories annotation
All Repository classes should only extend from JpaRespository and NOT from RevisionRepository. You dont need RevisionRepository
You should be able to get your application up and running now.
Now coming back to the question, how to get all revisions with changes using forRevisionsOfEntityWithChanges method.
Create an AuditConfiguration class like this, to create the AuditReader bean
#Configuration
public class AuditConfiguration {
private final EntityManagerFactory entityManagerFactory;
AuditConfiguration(EntityManagerFactory entityManagerFactory) {
this.entityManagerFactory = entityManagerFactory;
}
#Bean
AuditReader auditReader() {
return AuditReaderFactory.get(entityManagerFactory.createEntityManager());
}
}
In your AuditRevisionEntity class, add following annotation. Without this the serialization of this class wont work. e.g
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class AuditRevisionEntity extends DefaultRevisionEntity {
In your entity class add option withModifiedFlag = true to #Audited annotation. Without this you cannot get entity revisions with all changes. e.g
#Audited(withModifiedFlag = true)
public class Customer {
Modify your database table for this entity audit table and fields *_mod. e.g if you have a customer table with fields name, age, address columns, then add columns name_mod, age_mod, address_mod to the customer_audit table
Last, add following code in your service method to get audit revisions with changes
#Autowired
private AuditReader auditReader;
public List<?> getRevisions(Long id) {
AuditQuery auditQuery = auditReader.createQuery()
.forRevisionsOfEntityWithChanges(Customer.class, true)
.add(AuditEntity.id().eq(id));
return auditQuery.getResultList();
}
I will try to post the same code in Github sometime today, so that you can take a look at working code.
Your code looks fine. But it may not be sufficient to identify the root cause.
Looking at the exception it is clear that application is failing since it is not able to find bean dependency
Could you try following
Check your library imports first in your build.gradle or pom.xml. Generally you should not require any other Hibernate library other than Spring Boot Data JPA and Hibernate Envers
Try removing/disabling the Hibernate Envers audit code and library dependencies and see if can you get your application up and running. This will help you identify if error is due to Hibernate Envers or if your application code has other issues.
If above does not works, then please provide more information
Which version of Spring Boot are you on
What libraries have you imported (build.gradle or maven pom file)
What other Configurations you have in your project - do you have any other JPA configuration file or any other custom configuration related to Hibernate or JPA
What annotations are on the main application class
Directory structure of your Repository class, and the directory on which you do component scan (in case you have overridden it)
First of all is it a viable thing to embed Activiti into an API type application for use within that application or should Activiti be run standalone?
The error below is due to bean definition but I'm not sure where the beans should be defined and how - if thats correct approach for version 6. Our standards with Springhboot 2 is to annotate beans in java rather than xml context
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2019-04-10 21:17:43.924 ERROR 19516 --- [ restartedMain] o.s.b.d.LoggingFailureAnalysisReporter :
***************************
APPLICATION FAILED TO START
***************************
Description:
Field runtimeService in ma.cvmeeting.workflow.WorkflowApplication$MyrestController required a bean of type 'org.activiti.engine.RuntimeService' that could not be found.
The injection point has the following annotations:
- #org.springframework.beans.factory.annotation.Autowired(required=true)
Action:
Consider defining a bean of type 'org.activiti.engine.RuntimeService' in your configuration.
Process finished with exit code 0
code:
import org.activiti.engine.RuntimeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
#SpringBootApplication
public class WorkflowApplication {
public static void main(String[] args) {
SpringApplication.run(WorkflowApplication.class, args);
}
#RestController
public static class MyrestController{
#Autowired
private RuntimeService runtimeService;
#GetMapping("/start-process")
public String startProcess() {
runtimeService.startProcessInstanceByKey("Potulerauneoffre");
return "Process started. Number of currently running"
+ "process instances = "
+ runtimeService.createProcessInstanceQuery().count();
}
}
pom.xml:
<project>
<groupId>ma.cvmeeting</groupId>
<artifactId>workflow</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>workflow</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-engine</artifactId>
<version>7-201802-EA</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf</artifactId>
<version>3.0.11.RELEASE</version>
</dependency>
<dependency>
<groupId>org.h2database</groupId>
<artifactId>h2database</artifactId>
<version>1.0.20061217</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>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
There are two ways to initialize the engine when you embed it in your spring based application:
1.) let spring initialize it for you so you can use all the engine services right away without need of any configuration. this requires activiti-spring-boot-starter as dependency.
2.) You initialize engine by your self and provide the services beans from #Configuration class. for this you will require only activiti-engine core as dependency
The reason your application cannot find the RuntimeService because you are trying the second approach add the below dependency in your pom.xml and remove the engine one
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter</artifactId>
</dependency>
you should follow documentation for more help.
We recommend activiti 7 core if you are planning to use spring boot 2.x and the use of the new APIs. This is great time if you want to get involved with the new APIs and project initiatives
You could write a #Configuration class and define Activiti services, like this :
#Configuration
public class ActivityConfig {
#Autowired
DataSource dataSource;
#Bean
public DataSourceTransactionManager getTransactionManager() {
return new DataSourceTransactionManager(dataSource);
}
#Bean
public ProcessEngineConfigurationImpl getProcessEngineConfiguration() {
SpringProcessEngineConfiguration res = new SpringProcessEngineConfiguration();
res.setDataSource(dataSource);
res.setTransactionManager(getTransactionManager());
return res;
}
#Bean
public ProcessEngineFactoryBean getProcessEngine() {
ProcessEngineFactoryBean res = new ProcessEngineFactoryBean();
res.setProcessEngineConfiguration(getProcessEngineConfiguration());
return res;
}
#Bean
public RepositoryService getRepositoryService() throws Exception {
return getProcessEngine().getObject().getRepositoryService();
}
#Bean
public FormService getFormService() throws Exception {
return getProcessEngine().getObject().getFormService();
}
#Bean
public TaskService getTaskService() throws Exception {
return getProcessEngine().getObject().getTaskService();
}
#Bean
public RuntimeService getRuntimeService() throws Exception {
return getProcessEngine().getObject().getRuntimeService();
}
#Bean
public HistoryService getHistoryService() throws Exception {
return getProcessEngine().getObject().getHistoryService();
}
#Bean
public IdentityService getIdentityService() throws Exception {
return getProcessEngine().getObject().getIdentityService();
}
}
I'm attempting to set up a simple REST web application that uses Jersey. In the documentation, it seems that I should be able to create my application without using a web.xml file. From the site:
JAX-RS provides a deployment agnostic abstract class Application for declaring root resource and provider classes, and root resource and provider singleton instances. A Web service may extend this class to declare root resource and provider classes.
The example that follows shows this code:
public class MyApplication extends Application {
#Override
public Set<Class<?>> getClasses() {
Set<Class<?>> s = new HashSet<Class<?>>();
s.add(HelloWorldResource.class);
return s;
}
}
To me, this says that I can use an Application class to do all of my servlet setup. This seems to be the configuration that reads my resource class's annotations and sets up the correct URL handling mechanisms. Is that correct? I don't have to do any other setup?
I ask because I created the following and it didn't work (I get a 404 from localhost:8080/{context}/test):
pom.xml:
<dependencies>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-core</artifactId>
<version>1.12</version>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-server</artifactId>
<version>1.12</version>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-json</artifactId>
<version>1.12</version>
</dependency>
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>jsr311-api</artifactId>
<version>1.1.1</version>
</dependency>
</dependencies>
Application class:
#ApplicationPath("/")
public class JerseyTestApp extends Application
{
#Override
public Set<Class<?>> getClasses()
{
final Set<Class<?>> classes = new HashSet<>();
classes.add(JerseyTestController.class);
return classes;
}
}
Resource class:
#Path("/test")
public class JerseyTestController
{
#GET
#Produces(MediaType.TEXT_PLAIN)
public String getTestMsg()
{
return "It works";
}
}
Dumb. All I had to do was include the jersey-servlet jar, as prescribed by this answer.
I have a Standalone Jersey server running at the beginning of my JunitTest. I'm testing if my JaxRS controller works, as well as my custom HttpClient. Please note that I've always been able to use this JaxRsResourceController embedded in glassfish.
Here is the JaxRsController (light version)
#Path("root")
public class JaxRsResourceController implements
ResourceController<HttpServletRequest> {
#Context
private UriInfo context;
#Context
HttpServletRequest request;
#Context
HttpServletResponse response;
#GET
public String hello(){
System.out.println("Uri is "+this.context.getBaseUri().toString());
return "Hello "+peoples;
}
}
I have no problem with the client, but when I start the server, I have :
GRAVE: The following errors and warnings have been detected with resource and/or provider classes:
SEVERE: Missing dependency for field: javax.servlet.http.HttpServletRequest com.robustaweb.library.rest.controller.implementation.JaxRsResourceController.request
SEVERE: Missing dependency for field: javax.servlet.http.HttpServletResponse com.robustaweb.library.rest.controller.implementation.JaxRsResourceController.response
at com.sun.jersey.api.container.httpserver.HttpServerFactory.create(HttpServerFactory.java:172)
at com.robustaweb.library.rest.server.JerseyServer.startServer(JerseyServer.java:44)
Basically it says that at the #Context injection time, there is no dependency on the HttpServletRequest.
However if I remove the #Context annotations on request and response, but keep it for UriInfo context, it's ok, and I can read the Uri.
I changed a few times the Maven pom wich is now to force the libs in:
<dependencies>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-server</artifactId>
<version>1.14</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>jsr311-api</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.1</version>
</dependency>
</dependencies>
Any idea ?
servlet dependencies were separated to another module, try adding
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-servlet</artifactId>
<version>1.14</version>
</dependency>
to your pom.
It was not easy, but I found out. The thing is that in my JUnit test, I was creating the server like this :
HttpServer server = HttpServerFactory.create(url);
But that way, you create a lightweight container that does not have servlet containers, and so is the failure reason. So in order to have it all, I used the jersey-test-framework that allow to use the Grizzly web container (or even Embedded glassfish).
Here is the maven :
<dependencies>
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>jsr311-api</artifactId>
<version>1.1.1</version>
</dependency>
<!-- Unit test are using jersey server directly -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.sun.jersey.test.framework</groupId>
<artifactId>jersey-test-framework</artifactId>
<version>1.0.3</version>
<scope>test</scope>
</dependency>
</dependencies>
Here is the JerseyServerTest : note that it extends JerseyTest
public class JerseyServerTest extends JerseyTest {
protected String baseUri = "http://localhost:" + TestConstants.JERSEY_HTTP_PORT + "/";
public JerseyServerTest() throws Exception {
super("com.robustaweb.library.rest.controller");
/*
It's possible to NOT call the super() but to manually do :
1) ApplicationDescriptor appDescriptor = new ApplicationDescriptor()
.setRootResourcePackageName(resourcePackageName) // resource packages
.setContextPath(contextPath) //context of app
.setServletPath(servletPath); // context of spi servlet
2)setupTestEnvironment(appDescriptor);
*/
}
#Test
public void testHelloWorldRequest() {
SunRestClient client = new SunRestClient(baseUri + "root");
String result = client.GET("", null);
System.out.println(result);
}
#Test
public void testDeleteRequest() {
SunRestClient client = new SunRestClient(baseUri + "root");
String result = client.DELETE("john", null);
System.out.println(result);
}
}
And finally the Resource file, that contains #GET and #DELETE
#Path("root")
public class JaxRsController extends JaxRsResourceController{
List<String> peoples = new ArrayList<String>();
#GET
public String hello(){
System.out.println("Uri is "+getUri());
return "Hello "+peoples;
}
#DELETE
#Path("{name}")
public String deletePeople(#PathParam("name") String name){
System.out.println("deleting "+name);
this.peoples.remove(name);
return String.valueOf(peoples.size());
}
}
And now it works !
I had some help in this article, and there is a small chapter on the documentation. Beeing able to attach the source code of the Jersey framework really helped, so thantks to IntelliJ also.