I'm trying to updating my Jersey project from 2.25 to 2.27.
Jersey is part of Java EE 8 since version 2.26, so I've also decided to update my project from Java EE 7 to Java EE 8, and from JAX-RS 2.0/CDI 1.x/Weld2.x/Apache Tomcat 8 to JAX-RS 2.1/CDI 2.0/Weld 3.0/Apache Tomcat 9.
The problem is I can't figure out the exact dependencies I need to include in my project (Maven project, pom.xml).
Things that I've tried:
I've followed intructions on this answer to install Weld on Tomcat
The latest Jersey documentation doesn't seem to cover this case. Anyway, I've tried to mimic the dependencies included in the CDI webapp example
Weld 2.4 to 3.0 migration guide
...and finally, in my despair, different combinations of the following dependencies: cdi-api, jersey-hk2, javax.inject, jersey-cdi1x, jersey-cdi1x-servlet, weld-servlet, weld-servlet-core, weld-servlet-shaded, weld-core-impl
These are my current dependencies (pom.xml):
<!-- Java EE -->
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>${java-ee.version}</version>
</dependency>
<!-- JAX-RS -->
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>javax.ws.rs-api</artifactId>
<version>${jaxrs.version}</version>
</dependency>
<!-- Jersey -->
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet</artifactId>
<version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-server</artifactId>
<version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-client</artifactId>
<version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-multipart</artifactId>
<version>${jersey.version}</version>
</dependency>
<!-- CDI (Jersey HK2 / Weld) -->
<dependency>
<groupId>org.glassfish.jersey.inject</groupId>
<artifactId>jersey-hk2</artifactId>
<version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>org.jboss.weld.servlet</groupId>
<artifactId>weld-servlet-shaded</artifactId>
<version>${weld.version}</version>
</dependency>
<dependency>
<groupId>org.jboss.weld</groupId>
<artifactId>weld-core-impl</artifactId>
<version>${weld.version}</version>
</dependency>
And this is the error I'm currently getting when trying to inject a #Singleton into my resource (#Path):
ADVERTENCIA: The following warnings have been detected: WARNING: Unknown HK2 failure detected:
...
GRAVE: El Servlet.service() para el servlet [CGERestApplication] en el contexto con ruta [/igea-cge-interfaces] lanzó la excepción [A MultiException has 3 exceptions. They are:
1. org.glassfish.hk2.api.UnsatisfiedDependencyException: There was no object available for injection at SystemInjecteeImpl(requiredType=SapClient,parent=CambioEstadoTrabajoService,qualifiers={},position=-1,optional=false,self=false,unqualified=null,363959695)
2. java.lang.IllegalArgumentException: While attempting to resolve the dependencies of es.indra.isl.igea.external.cge.web.api.services.sap.CambioEstadoTrabajoService errors were found
3. java.lang.IllegalStateException: Unable to perform operation: resolve on es.indra.isl.igea.external.cge.web.api.services.sap.CambioEstadoTrabajoService
] with root cause:
org.glassfish.hk2.api.UnsatisfiedDependencyException: There was no object available for injection at SystemInjecteeImpl(requiredType=SapClient,parent=CambioEstadoTrabajoService,qualifiers={},position=-1,optional=false,self=false,unqualified=null,363959695)
at org.jvnet.hk2.internal.ThreeThirtyResolver.resolve(ThreeThirtyResolver.java:75)
at org.jvnet.hk2.internal.ClazzCreator.resolve(ClazzCreator.java:212)
at org.jvnet.hk2.internal.ClazzCreator.resolveAllDependencies(ClazzCreator.java:235)
at org.jvnet.hk2.internal.ClazzCreator.create(ClazzCreator.java:358)
at org.jvnet.hk2.internal.SystemDescriptor.create(SystemDescriptor.java:487)
at org.glassfish.jersey.inject.hk2.RequestContext.findOrCreate(RequestContext.java:83)
at org.jvnet.hk2.internal.Utilities.createService(Utilities.java:2126)
at org.jvnet.hk2.internal.ServiceLocatorImpl.internalGetService(ServiceLocatorImpl.java:777)
at org.jvnet.hk2.internal.ServiceLocatorImpl.internalGetService(ServiceLocatorImpl.java:740)
at org.jvnet.hk2.internal.ServiceLocatorImpl.getService(ServiceLocatorImpl.java:710)
at org.glassfish.jersey.inject.hk2.AbstractHk2InjectionManager.getInstance(AbstractHk2InjectionManager.java:184)
at org.glassfish.jersey.inject.hk2.ImmediateHk2InjectionManager.getInstance(ImmediateHk2InjectionManager.java:54)
at org.glassfish.jersey.internal.inject.Injections.getOrCreate(Injections.java:129)
at org.glassfish.jersey.server.model.MethodHandler$ClassBasedMethodHandler.getInstance(MethodHandler.java:284)
at org.glassfish.jersey.server.internal.routing.PushMethodHandlerRouter.apply(PushMethodHandlerRouter.java:75)
at org.glassfish.jersey.server.internal.routing.RoutingStage._apply(RoutingStage.java:110)
And the Tomcat startup log:
jun 04, 2018 10:09:07 AM org.jboss.weld.environment.servlet.EnhancedListener onStartup
INFO: WELD-ENV-001008: Initialize Weld using ServletContainerInitializer
jun 04, 2018 10:09:08 AM org.jboss.weld.bootstrap.WeldStartup
INFO: WELD-000900: 3.0.4 (Final)
jun 04, 2018 10:09:08 AM org.jboss.weld.bootstrap.WeldStartup startContainer
INFO: WELD-000101: Transactional services not available. Injection of #Inject UserTransaction not available. Transactional observers will be invoked synchronously.
jun 04, 2018 10:09:09 AM org.jboss.weld.environment.tomcat.TomcatContainer initialize
INFO: WELD-ENV-001100: Tomcat 7+ detected, CDI injection will be available in Servlets, Filters and Listeners.
After some more research, I've found that the dependencies I listed in my question were right.
The one thing I was missing was to register an AbstractBinder in my ResourceConfig, as stated in this answer and in the Jersey user guide.
Example:
public class MyApplication extends ResourceConfig {
public MyApplication() {
super();
// ...
register(new MyInjectionBinder());
}
}
#Singleton
public class MyInjectable {
// ...
}
public class MyInjectionBinder extends AbstractBinder {
#Override
protected void configure() {
bind(MyInjectable.class).to(MyInjectable.class).in(Singleton.class);
}
}
#Path("/myresource")
public class MyResource {
#Inject
MyInjectable injectable;
// ...
}
Also note that if you want to inject other injectables into your injectable, they will not be available until the postconstruct lifecycle event:
#Singleton
public class MyInjectable {
#Inject
OtherInjectable other;
#PostConstruct
private void init() {
// ... do something with "other"
}
}
I solved this problem. I am using Jetty 9.4.14, Jersey 2.28 and Weld (servlet-shaded) 3.1.0. This is the list of dependencies for Java 11.
//Jersey:
jaxb-api-2.3.0.jar
jaxb-core-2.3.0.1.jar
jaxb-impl-2.3.0.1.jar
javax.ws.rs-api-2.1.1.jar
javax.activation-1.2.0.jar
jersey-container-jetty-http-2.28.jar
jersey-container-servlet-core-2.28.jar
jersey-server-2.28.jar
jersey-common-2.28.jar
jersey-client-2.28.jar
//Jersey + CDI:
jersey-bean-validation-2.28.jar
bean-validator-2.5.0-b06.jar
jersey-cdi1x-2.28.jar
jersey-cdi1x-servlet-2.28.jar
jersey-hk2-2.28.jar
hk2-api-2.5.0.jar
hk2-utils-2.5.0.jar
hk2-locator-2.5.0.jar
More information you can find in this Jersey issue.
Related
I've created a spring boot application, and I want to handle the Hibernate SessionFactory, so in my service class, I can just call the Hibernate SessionFactory as following :
#Autowired
private SessionFactory sessionFactory;
I found a similar question in stackoverflow where I have to add the following line in application.properties :
spring.jpa.properties.hibernate.current_session_context_class=org.springframework.orm.hibernate4.SpringSessionContext
but I'm getting this error :
Cannot resolve property 'current_session_context_class' in java.lang.String
How can I solve this ?
pom.xml dependencies :
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
Since version 2.0, JPA provides easy access to the APIs of the underlying implementations. The EntityManager and the EntityManagerFactory provide an unwrap method which returns the corresponding classes of the JPA implementation.
In the case of Hibernate, these are the Session and the SessionFactory.
SessionFactory sessionFactory = entityManagerFactory.unwrap(SessionFactory.class);
Try enabling HibernateJpaSessionFactoryBean in your Spring configuration.
#Bean
public HibernateJpaSessionFactoryBean sessionFactory() {
return new HibernateJpaSessionFactoryBean();
}
Have a look at:
https://stackoverflow.com/a/33881946/676731
By Spring configuration I mean a class annotated with #Configuration annotation or #SpringBootApplication (it is implicitly annotated with #Configuration).
I'm using spring-boot with camel and ActiveMQ.
I'm using activemq autoconfiguration via #EnableJms annotation.
But creating my own ActiveMQComponent to enable "transacted(true)" on all queues.
#Bean(name = "activemq")
#ConditionalOnClass(ActiveMQComponent.class)
public ActiveMQComponent activeMQComponent(ConnectionFactory connectionFactory) {
ActiveMQComponent activeMQComponent = new ActiveMQComponent();
activeMQComponent.setConnectionFactory(connectionFactory);
activeMQComponent.setTransacted(true);
activeMQComponent.setTransactionManager(jmsTransactionManager(connectionFactory));
return activeMQComponent;
}
It works well but when I try to gracefully shutdown the application.
The PooledConnectionFactory get destroyed before the camel graceful shutdown happens.
Leading to a tons of error and the route unable to correctly stops.
Like 20 times this error :
2017-05-04 18:21:59.748 WARN 12188 --- [er[test.queue]] o.a.activemq.jms.pool.PooledSession : Caught exception trying rollback() when putting session back into the pool, will invalidate. javax.jms.IllegalStateException: The Session is closed
Followed by:
2017-05-04 18:21:59.748 INFO 12188 --- [ Thread-18] o.a.camel.spring.SpringCamelContext : Apache Camel 2.18.3 (CamelContext: route) is shutting down
Then later :
2017-05-04 18:21:59.766 INFO 12188 --- [ - ShutdownTask] o.a.camel.impl.DefaultShutdownStrategy : Waiting as there are still 1 inflight and pending exchanges to complete, timeout in 300 seconds. Inflights per route: [test2 = 1]
Anyone can help me configuring spring-boot camel activemq all together with graceful shutdown ?
Thanks
Update :
Here is a sample of my pom.xml:
<properties>
<!-- Spring -->
<spring-boot.version>1.4.3.RELEASE</spring-boot.version>
<!-- Camel -->
<camel-spring-boot.version>2.18.3</camel-spring-boot.version>
</properties>
....
<!-- Camel BOM -->
<dependency>
<!-- Import dependency management from Spring Boot -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-spring-boot-dependencies</artifactId>
<version>${camel-spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
...
<!-- Spring -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-actuator</artifactId>
</dependency>
<!-- ActiveMQ -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-camel</artifactId>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-pool</artifactId>
</dependency>
<!-- Camel -->
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-spring-boot-starter</artifactId>
</dependency>
Update 2:
After further investigation and the creation of a new project adding every modification one by one I have isolated the problem.
The shutdown works correcly until I add a specific endpoint :
#EndpointInject(uri = "direct:aaa")
private Endpoint errorHandling;
Using :
private String errorHandling = "direct:aaa";
Doesn't produce the bug.
It seems like using #EndpointInject is making the activemq close first
Update 3 :
Found that SpringCamelContext is not implementing ApplicationListener and thus its method "onApplicationEvent" its not called handling the "shutdownEager" of camel.
Important thing is to use Camel Spring Boot Starter.
http://camel.apache.org/spring-boot.html
How to enable Camel auto-configuration in my Spring Boot application?
Just drop camel-spring-boot jar into your classpath:
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-spring-boot</artifactId>
<version>${camel.version}</version> <!-- use the same version as your Camel core version -->
</dependency>
camel-spring-boot jar comes with the spring.factories file, so as soon as you add that dependency into your classpath, Spring Boot will automatically auto-configure the Camel for you. Yay! That was fast ;) .
Auto-configured Camel context
The most important piece of functionality provided by the Camel auto-configuration is the CamelContext instance.
Camel auto-configuration creates SpringCamelContext for your and take care of the proper initialization and shutdown of that context.
Created Camel context is also registered in the Spring application context (under camelContext bean name), so you can access it just as the any other Spring bean.
#Configuration
public class MyAppConfig {
#Autowired
CamelContext camelContext;
#Bean
MyService myService() {
return new DefaultMyService(camelContext);
}
}
Apparently since https://issues.apache.org/jira/browse/CAMEL-2607
the SpringCamelContext doesn't implement ApplicationListener interface anymore.
Since I'm using spring-boot autoconfiguration, I am not using CamelContextFactoryBean which is adding the listener.
Has a temporary fix, I created a component which listen to ApplicationEvent and dispatch them to the SpringCamelContext method :
public class SpringCamelContextFix implements ApplicationListener<ApplicationEvent> {
private SpringCamelContext camelContext;
public SpringCamelContextFix(SpringCamelContext camelContext) {
this.camelContext = camelContext;
}
#Override
public void onApplicationEvent(ApplicationEvent event) {
camelContext.onApplicationEvent(event);
}
}
I had this same problem running unit/integration tests with Spring Boot, ActiveMQ or A-MQ, and Camel (version 2.18.1.redhat-000012). Apparently, when Spring Boot shuts down, the JMS thread pool is closed before the Camel context is shutdown, which is the wrong order. #John D provided a code fix in a Camel users mailing list thread which is similar to what he provided in this thread. Here is the version of John D's code that worked for me:
#Component
public class SpringCamelContextFix implements
ApplicationListener<ApplicationEvent> {
#Inject
private SpringCamelContext camelContext;
public SpringCamelContextFix(SpringCamelContext camelContext) {
this.camelContext = camelContext;
}
#Override
public void onApplicationEvent(ApplicationEvent event) {
camelContext.onApplicationEvent(event);
}
}
I'm trying do integration testing with the following stack:
App server: Embedded WildFly
CDI container: Weld
Database: In-memory H2
ORM: Hibernate/JPA
Platform: Java 8
OS: Mac OS X 10.10
I've setup basic integration testing with Arquillian (as done here) and I'm able to inject dependencies but injecting EntityManager proves to be a challenge. Dereferencing the entity manager field always results in a NullPointerException.
I've seen many articles (including this and this) but I'm still not able to get this seemingly simple thing to work.
Please see below my pom.xml
<dependencies>
<dependency>
<groupId>org.jboss.spec</groupId>
<artifactId>jboss-javaee-7.0</artifactId>
<version>1.0.0.Final</version>
<type>pom</type>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8.1</version>
<scope>test</scope>
</dependency>
<!-- JUnit Container Implementation for the Arquillian Project -->
<dependency>
<groupId>org.jboss.arquillian.junit</groupId>
<artifactId>arquillian-junit-container</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jboss.arquillian.protocol</groupId>
<artifactId>arquillian-protocol-servlet</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jboss.arquillian.container</groupId>
<artifactId>arquillian-weld-ee-embedded-1.1</artifactId>
<version>1.0.0.CR3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jboss.arquillian.extension</groupId>
<artifactId>arquillian-persistence-dbunit</artifactId>
<version>1.0.0.Alpha7</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jboss.weld</groupId>
<artifactId>weld-core</artifactId>
<version>1.1.5.Final</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.6.4</version>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.jboss.arquillian</groupId>
<artifactId>arquillian-bom</artifactId>
<version>1.1.8.Final</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
test-persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="test" transaction-type="JTA">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<class>com.xyz.hellomaven.DummyEntity</class>
<jta-data-source>java:jboss/datasources/ExampleDS</jta-data-source>
<!--<jta-data-source>java:/DefaultDS</jta-data-source>-->
<!--<jta-data-source>jdbc/arquillian</jta-data-source>-->
<properties>
<property name="hibernate.show_sql" value="true" />
<property name="hibernate.hbm2ddl.auto" value="update"/>
<!--<property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect" />-->
</properties>
</persistence-unit>
</persistence>
Test case
#RunWith(Arquillian.class)
public class GreeterTest {
#Inject
private Greeter instance; // Injection works!
#PersistenceContext
private EntityManager em; // Null pointer.
public GreeterTest() {
}
#Deployment
public static WebArchive createDeployment() {
return ShrinkWrap.create(WebArchive.class)
.addClasses(Greeter.class, PhraseBuilder.class, DummyInterceptor.class)
.addAsResource("logging.properties", "META-INF/logging.properties")
.addAsResource("test-persistence.xml", "META-INF/persistence.xml")
.addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml");
}
#Test
public void testCreateGreeting() {
System.out.println("createGreeting");
assertEquals("Hello, Steve!", instance.createGreeting("Steve"));
}
#Test
public void testPersistence() {
DummyEntity de = new DummyEntity();
de.setId(1l);
de.setName("Petr Cech");
de.setAge(10);
em.persist(de);
Query q = em.createQuery("SELECT d.age FROM DummyEntity d");
assertEquals(10, q.getResultList().get(0));
}
}
Complete Maven project available on GitHub.
Please what am I doing wrong?
just don't use weld, sins data-sources is out of things that could be covered by CI and DI. probably you may mock it with Mokito and stay with light Weld,
<dependency>
<groupId>org.jboss.arquillian.container</groupId>
<artifactId>arquillian-weld-ee-embedded-1.1</artifactId>
<version>1.0.0.CR3</version>
<scope>test</scope>
</dependency>
But if you want to deal with real DB use managed jboss (ExampleDS is a demo jboss h2 datasource) or managed glassfish instead.
<dependency>
<groupId>org.jboss.as</groupId>
<artifactId>jboss-as-arquillian-container-managed</artifactId>
<version>7.1.1.Final</version>
<scope>test</scope>
</dependency>
ref. https://github.com/arquillian/arquillian-examples/blob/master/arquillian-persistence-tutorial/pom.xml
As stated by #Soloviev Dmitry, you use a CDI container for your integration test, which only enables CDI.
There are two options I see:
First one is to use a wildfly-embedded container configured in your maven project, so during maven phase running your integration-tests, wildfly will be downloaded and your test package will be deployed to it. So with ExampleDS it would work fine, as it comes with Wildfly out of the box.
See this post for details
Second one would consist in not using Arquillian for your integration test. So if your integration test only covers managed beans, (not session beans, Wildfly specific resources, ...), you could just instantiate a CDI container prior to your test execution (in #Before or #BeforeClass annotated method using Junit for example) and then instantiate your EntityManager by using the EntityManagerFactory class, referencing your persistence unit used for this integration test. With this method, you could also create CDI producers to inject other resources for your integration test, mocks, depending on the scope of your test.
maven dependency
<dependency>
<groupId>org.jboss.weld.se</groupId>
<artifactId>weld-se</artifactId>
<version>2.1.2.Final</version>
<scope>test</scope>
</dependency>
The test class
import org.jboss.weld.environment.se.Weld;
import org.jboss.weld.environment.se.WeldContainer;
import org.junit.*;
public class ExampleIT {
private EntityManager em;
protected static Weld weld;
protected static WeldContainer container;
#BeforeClass
public static void init() {
weld = new Weld();
container = weld.initialize();
}
#AfterClass
public static void close() {
weld.shutdown();
}
#Before
private void before(){
em = Persistence.createEntityManagerFactory("MyPersistenceUnit").createEntityManager();
}
#Test
public void testToto(){
// Do something with entity manager ...
}
}
I usually opt for second solution for Integration tests, because it's easier to setup than Arquillian tests, and faster to execute.
Use the application managed entitymanager.
#PersistenceUnit
EntityManagerFactory emf;
and create entityManager using
EntityManager em = emf.createEntityManager();
a container managed entitymanager is created and injected by container itself. If you are not under a server environment, then you need to use application managed persistence context.
I guess the transaction manager + Entity Manger Factory are missing in your context file. Configure both in test-persistence.xml, then make the entity manager factory a property of the transaction manager.
Just taking Spring-Boot for a spin and decided to mix in Camel because I need some arcane Headers work in the rest client I am working on. Setting up the application was fine until I added the camel-http component to my POM, then I get this on init:
Exception in thread "main" org.springframework.context.ApplicationContextException: Unable to start embedded container; nested exception is org.springframework.context.ApplicationContextException: Unable to start EmbeddedWebApplicationContext due to missing EmbeddedServletContainerFactory bean.
I've havent got the first idea of where to start to look for the problem. I gather Spring Boot will look up the classpath and try to wire stuff up, so is there a way for the to block the Camel packages from being acted on or something of the sort?
Complete log of the start up in this Gist
Here's my main aplication code:
#ComponentScan
#EnableAutoConfiguration
public class Application {
private static ApplicationContext ctx;
public static void main(String[] args) throws Exception {
ctx = SpringApplication.run(Application.class, args);
//Right outta Spring 4 docs
System.out.println("Let's inspect the beans provided by Spring Boot:");
String[] beanNames = ctx.getBeanDefinitionNames();
Arrays.sort(beanNames);
for (String beanName : beanNames) {
System.out.println(beanName);
}
//---
// FIXME: ugly hack to allow some POC woek while wait for proper Camel/Spring4 unit tests fix.
Application app = new Application();
app.executeTests();
}
/**
* Dev QOL - unit tests are broken for now, see:
* https://issues.apache.org/jira/browse/CAMEL-7074
* <p/>
* Waiting for fix (Too lay to checkout and build my own Camel)
*/
private void executeTests() throws Exception {
testAuth();
}
#Bean
DefaultCamelContext camelCtx() throws Exception {
DefaultCamelContext camel = new DefaultCamelContext();
camel.addRoutes(cryptsyRouteBuilder());
camel.start();
return camel;
}
#Bean
public CryptsyRouteBuilder cryptsyRouteBuilder() throws Exception{
CryptsyRouteBuilder bean = new CryptsyRouteBuilder();
bean.setCryptsy(cryptsy());
return bean;
}
#Bean
public Cryptsy cryptsy() throws IOException {
return new Cryptsy();
}
protected void testAuth() throws Exception {
ProducerTemplate producer = camelCtx().createProducerTemplate();
producer.requestBody("direct:start", "Why, hullo there", String.class);
}
}
And my POM dependencies:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>0.5.0.BUILD-SNAPSHOT</version>
</parent>
<dependencies>
<!-- Spring -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--<dependency>-->
<!--<groupId>org.springframework.boot</groupId>-->
<!--<artifactId>spring-boot-starter-actuator</artifactId>-->
<!--</dependency>-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<!-- Camel -->
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-core</artifactId>
<version>${camel.version}</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-spring</artifactId>
<version>${camel.version}</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-spring-javaconfig</artifactId>
<version>${camel.version}</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-quartz</artifactId>
<version>${camel.version}</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-test-spring</artifactId>
<version>${camel.version}</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-http</artifactId>
<version>${camel.version}</version>
</dependency>
<!-- Assorted -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.9</version>
</dependency>
</dependencies>
<properties>
<start-class>xxx.xxxx.Application</start-class>
<camel.version>2.12.2</camel.version>
</properties>
The exception is telling you that Spring Boot thinks you want to build a web server, but can't find the right dependencies on the classpath. The most obvious reason for that in your case would be that the HTTP dependencies you added included Servlet APIs. I see no reason why you need that for a client app, but only you would know whether you need it or not. Maybe you can exclude it?
If you do need the Servlet dependencies and just want to explicitly tell Boot that you aren't creating a web application you can set the property spring.main.web_environment=false, or use the SpringApplication (or SpringApplicationBuilder) API directly to set the same flag. See docs here for background information.
I first want to point out that this is by and large the biggest problem in terms of time wasted that I have ever dealt with in my career. (Over two days straight now with essentially 0 progress.) Every single "work-around" or "solution" I have attempted hasn't worked, so I am blocked and pretty desperate for some assistance.
The problem in a nutshell is that Jersey/HK2 seems to always instantiate my Spring-managed beans AFTER they have already been instantiated by Spring, which tells me that jersey-spring3 is not doing its job, or at least not with my current setup (or any of the ~50 permutations of setups I have tried thus far.)
Note that when I use an empty constructor, those resource fields are null at run-time.
I do not understand why my current setup doesn't work as I am essentially copying this online example
Any help is more than appreciated!!
Configuration
- - - - - pom.xml - - - - -
<!-- ... -->
<dependencies>
<!-- Spring Dependencies -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-instrument</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-instrument-tomcat</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-ldap</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-parent</artifactId>
<version>${spring.version}</version>
<classifier>tests</classifier>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>${spring.version}</version>
<classifier>tests</classifier>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-acl</artifactId>
<version>${spring.version}</version>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-support</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-dao</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
</dependency>
<!-- / Spring Dependencies -->
<!-- API dependencies -->
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-client</artifactId>
<version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet</artifactId>
<version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-processing</artifactId>
<version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.test-framework.providers</groupId>
<artifactId>jersey-test-framework-provider-grizzly2</artifactId>
<version>2.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.ext</groupId>
<artifactId>jersey-spring3</artifactId>
<version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>${gson.version}</version>
</dependency>
<!-- / API dependencies -->
<!-- ... -->
</dependencies>
<!-- ... -->
<properties>
<!-- ... -->
<spring.version>3.0.5.RELEASE</spring.version>
<jersey.version>2.4.1</jersey.version>
<gson.version>2.2.4</gson.version>
<!-- ... -->
</properties>
<!-- ... -->
- - - - - web.xml - - - - -
<web-app>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:/beans.xml</param-value>
</context-param>
<!-- ... -->
<servlet>
<servlet-name>Jersey REST Service</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>fubar.rest.FubarJerseyApplication</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Jersey REST Service</servlet-name>
<url-pattern>/api/*</url-pattern>
</servlet-mapping>
<!-- ... -->
</web-app>
- - - - - beans.xml (Context Configuration) - - - - -
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<!-- ... -->
<!-- beans-outbound-api has configuration for spring-jersey3 to work properly -->
<import resource="beans-api.xml" />
</beans>
- - - - - beans-api.xml - - - - -
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- Services -->
<bean id="locationServiceV1" class="fubar.rest.v1.services.location.impl.LocationServiceV1" />
<bean id="locationServiceV2" class="fubar.rest.v2.services.location.impl.LocationServiceV2" />
<bean id="viewServiceV1" class="fubar.rest.v1.services.view.impl.ViewServiceV1" />
<bean id="viewServiceV2" class="fubar.rest.v2.services.view.impl.ViewServiceV2" />
<!-- Resources -->
<bean class="fubar.rest.resources.location.impl.LocationResource">
<constructor-arg index="0" ref="locationServiceV1" />
<constructor-arg index="1" ref="locationServiceV2" />
</bean>
<bean class="fubar.rest.resources.view.impl.ViewResource">
<constructor-arg index="0" ref="viewServiceV1" />
<constructor-arg index="1" ref="viewServiceV2" />
</bean>
</beans>
Code
- - - - - Resource (JAX-RS) - - - - -
#Path(RESTLocationResourceV1.PathFields.PATH_ROOT)
#Produces({V1_JSON, APPLICATION_JSON})
public class LocationResource
extends ResourceBase<LocationResource, ILocationServiceV1, ILocationServiceV2> {
private static final Logger logger = Logger.getLogger(LocationResource.class);
#Inject
public LocationResource(final LocationServiceV1 v1Loc, final LocationServiceV2 v2Loc) {
super(v1Loc, v2Loc);
logger.info(format(Messages.INF_INSTANTIATED, "LocationResource"));
}
#GET
#Path(PathFields.SUBPATH_LIST)
public LocationListV1 listV1(#HeaderParam(HEADER_API_KEY) String apiKey)
throws ApplicationException {
// Implementation
}
#GET
#Path(PathFields.SUBPATH_SEARCH)
public LocationListV1 searchV1(#HeaderParam(HEADER_API_KEY) String apiKey,
#QueryParam(QueryFields.QUERY) String likeText) throws ApplicationException {
// Implementation
}
}
- - - - - Service (Spring Bean) - - - - -
public class LocationServiceV1 extends ServiceBaseV1<LocationBean, LocationV1, LocationListV1>
implements
ILocationServiceV1 {
#Autowired
private LocationDao daoLoc;
public LocationServiceV1() {
super(new LocationBeanToJsonTranslatorV1());
}
#Override
public LocationListV1 listV1() throws ApplicationException {
// Implementation
}
#Override
public LocationListV1 searchV1(String likeText) throws ApplicationException {
// Implementation
}
}
(Essentially the same for version 2)
- - - - - Application (Jersey) - - - - -
public class FubarJerseyApplication extends ResourceConfig {
private static final class Messages {
static final String INF_STARTING_APPLICATION = "Starting %s!";
}
private static final Logger logger = Logger.getLogger(FubarJerseyApplication.class);
public FubarJerseyApplication() {
packages("fubar.rest");
logger.info(format(Messages.INF_STARTING_APPLICATION, this.getClass().getName()));
}
}
Invocation (Client)
curl http://my-ip-address/fubar/api/location/list
(500 Internal Server Error)
Error (Server)
org.glassfish.hk2.api.UnsatisfiedDependencyException: There was no object
available for injection at Injectee(requiredType=LocationServiceV1,parent=
LocationResource,qualifiers={}),position=0,optional=false,self=false,
unqualified=null,344016971)
at org.jvnet.hk2.internal.ThreeThirtyResolver.resolve(ThreeThirtyResolver.java:74)
at org.jvnet.hk2.internal.ClazzCreator.resolve(ClazzCreator.java:208)
at org.jvnet.hk2.internal.ClazzCreator.resolveAllDependencies(ClazzCreator.java:225)
at org.jvnet.hk2.internal.ClazzCreator.create(ClazzCreator.java:329)
at org.jvnet.hk2.internal.SystemDescriptor.create(SystemDescriptor.java:456)
at org.glassfish.jersey.process.internal.RequestScope.findOrCreate(RequestScope.java:158)
at org.jvnet.hk2.internal.Utilities.createService(Utilities.java:2350)
at org.jvnet.hk2.internal.ServiceLocatorImpl.getService(ServiceLocatorImpl.java:612)
at org.jvnet.hk2.internal.ServiceLocatorImpl.getService(ServiceLocatorImpl.java:597)
at org.glassfish.jersey.internal.inject.Injections.getOrCreate(Injections.java:173)
at org.glassfish.jersey.server.model.MethodHandler$ClassBasedMethodHandler.getInstance(MethodHandler.java:185)
at org.glassfish.jersey.server.internal.routing.PushMethodHandlerRouter.apply(PushMethodHandlerRouter.java:103)
at org.glassfish.jersey.server.internal.routing.RoutingStage._apply(RoutingStage.java:128)
at org.glassfish.jersey.server.internal.routing.RoutingStage._apply(RoutingStage.java:131)
at org.glassfish.jersey.server.internal.routing.RoutingStage._apply(RoutingStage.java:131)
at org.glassfish.jersey.server.internal.routing.RoutingStage._apply(RoutingStage.java:131)
at org.glassfish.jersey.server.internal.routing.RoutingStage._apply(RoutingStage.java:131)
at org.glassfish.jersey.server.internal.routing.RoutingStage.apply(RoutingStage.java:110)
at org.glassfish.jersey.server.internal.routing.RoutingStage.apply(RoutingStage.java:65)
at org.glassfish.jersey.process.internal.Stages.process(Stages.java:197)
at org.glassfish.jersey.server.ServerRuntime$1.run(ServerRuntime.java:250)
at org.glassfish.jersey.internal.Errors$1.call(Errors.java:271)
at org.glassfish.jersey.internal.Errors$1.call(Errors.java:267)
at org.glassfish.jersey.internal.Errors.process(Errors.java:315)
at org.glassfish.jersey.internal.Errors.process(Errors.java:297)
at org.glassfish.jersey.internal.Errors.process(Errors.java:267)
at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:318)
at org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:236)
at org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:983)
at org.glassfish.jersey.servlet.WebComponent.service(WebComponent.java:361)
at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:372)
at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:335)
at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:218)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at fubar.server.springframework.SessionFilter.doFilter(SessionFilter.java:44)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at fubar.server.springframework.loader.ContextLoaderHttpInterceptor$LoaderState.filter(ContextLoaderHttpInterceptor.java:75)
at fubar.server.springframework.loader.ContextLoaderHttpInterceptor$StartedState.filter(ContextLoaderHttpInterceptor.java:120)
at fubar.server.springframework.loader.ContextLoaderHttpInterceptor.doFilter(ContextLoaderHttpInterceptor.java:62)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:293)
at org.apache.jk.server.JkCoyoteHandler.invoke(JkCoyoteHandler.java:190)
at org.apache.jk.common.HandlerRequest.invoke(HandlerRequest.java:311)
at org.apache.jk.common.ChannelSocket.invoke(ChannelSocket.java:776)
at org.apache.jk.common.ChannelSocket.processConnection(ChannelSocket.java:705)
at org.apache.jk.common.ChannelSocket$SocketConnection.runIt(ChannelSocket.java:898)
at org.apache.tomcat.util.threads.ThreadPool$ControlRunnable.run(ThreadPool.java:690)
at java.lang.Thread.run(Thread.java:662)
API Log
Dec 10, 2013 13:36:28 INFO [main] fubar.rest.FubarJerseyApplication
- Starting fubar.rest.FubarJerseyApplication!
Dec 10, 2013 13:38:06 INFO [pool-1-thread-1] resources.location.impl.LocationResource
- LocationResource has been instantiated
Dec 10, 2013 13:38:06 INFO [pool-1-thread-1] resources.view.impl.ViewResource
- ViewResource has been instantiated
Update -- found this:
Catalina Log
Dec 10, 2013 1:36:42 PM org.glassfish.jersey.server.ApplicationHandler initialize
INFO: Initiating Jersey application, version Jersey: 2.4.1 2013-11-08 12:08:47...
Dec 10, 2013 1:36:43 PM org.glassfish.jersey.server.spring.SpringComponentProvider initialize
SEVERE: Spring context lookup failed, skipping spring component provider initialization.
Dec 10, 2013 1:38:00 PM com.sun.xml.bind.v2.runtime.reflect.opt.Injector inject
... so the ApplicationContext isn't found in SpringComponentProvider#initialize.
What's loading first? Spring or Jersey? It could be that your Spring context isn't initialized when SpringComponentProvider calls WebApplicationContextUtils.getWebApplicationContext(sc);. Try using Spring's ContextLoaderListener so that Spring does its initialization right after the app is deployed.
I ran into a lot of the same issues that you're experiencing with the jersey-spring3 library. It had problems finding my Spring ApplicationContext (looks like this is where you're stuck) and it blew up injecting setters that took a generic parameter as an argument.
If you get past the app context issue, I don't think what you have will work anyway. You defined the ViewResource and LocationResource beans in XML. From what I can tell, Jersey will only get the resource instance from Spring if the the resource class is annotated with #Component. Take a look at org.glassfish.jersey.server.spring.SpringComponentProvider, specifically component.isAnnotationPresent(Component.class):
// detect JAX-RS classes that are also Spring #Components.
// register these with HK2 ServiceLocator to manage their lifecycle using Spring.
#Override
public boolean bind(Class<?> component, Set<Class<?>> providerContracts) {
if (ctx == null) {
return false;
}
if(component.isAnnotationPresent(Component.class)) {
DynamicConfiguration c = Injections.getConfiguration(locator);
String[] beanNames = ctx.getBeanNamesForType(component);
if(beanNames == null || beanNames.length != 1) {
LOGGER.severe(LocalizationMessages.NONE_OR_MULTIPLE_BEANS_AVAILABLE(component));
return false;
}
String beanName = beanNames[0];
ServiceBindingBuilder bb = Injections.newFactoryBinder(new SpringComponentProvider.SpringManagedBeanFactory(ctx, locator, beanName));
bb.to(component);
Injections.addBinding(bb, c);
c.commit();
LOGGER.config(LocalizationMessages.BEAN_REGISTERED(beanName));
return true;
}
return false;
}
An unrelated issue was that we also wanted to move all of our JAX-RS annotations to interfaces. Whenever I tried it, I got "Could not find a suitable constructor for com.foo.ResourceInterface".
In the end, I solved all of my issues by not using jersey-spring3 and rolling my own Jersey to Spring connector. Here's what I did:
Configured all of my resources as regular Spring beans. You can use XML if you want.
In my Application, I added bindings to the HK2 container to use a factory whenever it needs an instance of one of the resources. My factory class simply returns the Spring managed instance of the resource.
Before the factory returns the Spring-managed bean, I use the Jersey/HK2 ServiceLocator to inject things that Jersey provides. For example, anything annotated with #Context.
My javax.ws.rs.Application looks like this:
public class RestConfig extends ResourceConfig {
private static final Log log = LogFactory.getLog(RestConfig.class);
#Inject
public RestConfig(ServiceLocator locator) {
super();
// specific to my app. get your spring beans however you like
Collection<Object> beans = BeanLocator.beansByAnnotation(RestResource.class);
DynamicConfiguration c = Injections.getConfiguration(locator);
for (Object bean : beans)
{
// tell jersey to use a factory for any interface that the bean implements. since your resources don't implement interfaces,
// you'll want to do something a bit different here.
for (Class<?> currentInterface : bean.getClass().getInterfaces())
{
if (log.isTraceEnabled())
log.trace("binding " + currentInterface.getSimpleName() + " to Spring managed bean");
ServiceBindingBuilder<Object> bb = Injections.newFactoryBinder(new StaticLookupFactory(locator, bean));
bb.to(currentInterface);
Injections.addBinding(bb, c);
}
}
// commit the changes to the HK2 container (don't skip this step!)
c.commit();
property("jersey.config.disableMoxyJson.server", true);
packages("com.foo.web.rest");
register(MoxyXmlFeature.class);
}
// a "factory" where the provide() method returns the spring managed bean
// that was passed to the constructor.
private static class StaticLookupFactory implements Factory<Object> {
private ServiceLocator locator;
private Object bean;
StaticLookupFactory(ServiceLocator locator, Object bean)
{
this.locator = locator;
this.bean = bean;
}
#Override
public Object provide() {
// inject this annotated with #Context, #Inject, etc
locator.inject(bean);
return bean;
}
#Override
public void dispose(Object instance) {
}
}
}
BeanLocator is a utility class that I wrote that makes it easy to grab bean instances using static methods when autowiring isn't available. For example, when working outside of Spring managed beans. Not too much going on there:
public static Collection<Object> beansByAnnotation(Class<? extends Annotation> annotation)
{
return applicationContext.getBeansWithAnnotation(annotation).values();
}
RestResource is also specific to our app. It's a custom stereotype that works like #Component, #Service, etc:
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Component
public #interface RestResource {
String value() default "";
}
Note that Jersey allows you to register custom implementations of org.glassfish.jersey.server.spring.ComponentProvider to manage the lifecycle of resources on your own. I tried it but couldn't get it to recognize my implementation no matter what I did.
One other note... the locator.inject(bean) call that activates the Jersey dependency injection mechanism will processes anything marked with #Inject. Use #Autowired within your classes or configure your beans with XML to avoid having both Spring and Jersey attempt to resolve values for things annotated with #Inject.
We have a custom, asynchronous ContextLoader, so the interim solution required placing a total hack in the Jersey-Spring3 source to wait for the application to initialize before the custom component provider initializes.
P.S. For any poor soul who finds themselves having to do something like this, make sure META-INF/settings contains the SpringComponentProvider configuration.
(2014-04-18) Elaborating for #Scott
Note that this is a terrible hack and I would only attempt such a thing as a last resort when all other attempts have failed, like in my case. Also I would consult the Jersey mailing group about your problem before attempting anything like this.
That said... this is what I did to solve my problem:
Literally copied the source code of spring-jersey3 into my application/server, modifying the header of every file with the appropriate tags as per the license;
Created the following class --
===>
/**
* Hack class for RN-8979.
*
* #author ryan
*
*/
public class ContextLoadWaiter {
private static final Logger logger = Logger.getLogger(ContextLoadWaiter.class);
public void doWait() {
try {
while (ContextLoaderHttpInterceptor.isNotStarted()) {
logger.info("Waiting for ContextLoader to start...");
Thread.sleep(1000);
}
} catch (InterruptedException e) {
logger.error("SpringComponentProvider was interrupted!");
}
}
}
Note that this is specific to *our* code-base as ContextLoaderHttpInterceptor is an http servlet where isNotStarted returns true if our custom ContextLoader (which happens to be asynchronous) is not yet loaded.
The custom asynchronous ContextLoader was put in place sometime by somebody for some reason along the lines of allowing the UI to display a "loading" page while the server boots up. (Probably not the correct way to add this UI "feature", but the code was there and the UI depended on it, so I had to deal with it...)
Since this part will not apply directly to you, the key thing is to debug through SpringComponentProvider (from here) and look at the value of the ClassPathXmlApplicationContext. If it is null, as it is in our case, then you need to figure out why it is null and wait on whatever ContextLoader you use to load before you initialize this component.
Placed this hacky line in SpringComponentProvider --
==>
...
private final ContextLoadWaiter waiter = new ContextLoadWaiter();
...
#Override
public void initialize(ServiceLocator locator) {
waiter.doWait(); // Wait on our asynchronous context loader.
this.locator = locator;
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine("Context lookup started");
}
...
Created this file: META-INF/services/org.glassfish.jersey.server.spi.ComponentProvider with the contents being the fully qualified classpath to the SpringComponentProvider, e.g. com.company.server.nbi.rest.internal.jspring.SpringComponentProvider
Added the custom Jersey-spring3 package as a package to scan in the application; see below...
==>
/**
* Application configuration.
*
* #author ryan
*
*/
public class MyJerseyApplication extends ResourceConfig {
private static final class Messages {
static final String INF_STARTING_APPLICATION = "Starting %s!";
}
private static final Logger logger = Logger.getLogger(MyJerseyApplication.class);
public MyJerseyApplication() {
packages(
/* Internal providers */
"com.company.server.nbi.rest.providers",
/* Internal filters */
"com.company.server.nbi.rest.filters",
/* Spring injection support */
"com.company.server.nbi.rest.internal.jspring", // HERE!!!
/* Json providers */
"com.fasterxml.jackson.jaxrs.json",
/* Jackson exception mappers */
"com.fasterxml.jackson.jaxrs.base");
/* Resources */
register(ResourceA.class);
register(ResourceB.class);
register(ResourceC.class);
/* Miscellaneous features */
register(MultiPartFeature.class);
register(LoggingFilter.class);
logger.info(format(Messages.INF_STARTING_APPLICATION, this.getClass().getName()));
}
}
That's "it". Definitely not a solution to be proud of, but if you are in desperation mode like I was, it probably doesn't hurt to give it a shot.
This is the message that is key to understanding the issue. It indicates that Spring is failing to initialise correctly:
SEVERE: Spring context lookup failed, skipping spring component provider initialization.
(On a side note: because Spring is failing to initialise, the only JSR-330 implementation to try and resolve the #Inject is HK2 - which is why you're seeing the other issue).
Anyway, the problem is likely that your container isn't performing a scan for the annotations that make all the jersey-spring3 magic happen.
This behaviour is part of the Servlet 3.0 Specification (JSR-33, Section 1.6.2), so you should double check that your container supports this.
In the case of Tomcat - unless you're running Tomcat 7.0.29 or newer, you'll actually need to make sure that the Servlet version is specified in your web.xml.
http://tomcat.apache.org/tomcat-7.0-doc/changelog.html#Tomcat_7.0.29_(markt)
I hit this problem recently and it drove me nuts, and fixing the web.xml was easier than upgrading from Ubuntu/Precise!
Hope this helps!