There are REST web service based on Jersey 2.23.2 & Guice 3.0. To use Guice it is necessary to adjust the hk2-guice bridge (I'm using 2.5.0-b07). Everything works fine until I have tried to test the service using Jersey Test Framework. Can't configure hk2-guice bridge for tests.
My test:
public class SomeTest extends JerseyTestNg.ContainerPerClassTest {
#Override
protected TestContainerFactory getTestContainerFactory() throws TestContainerException {
return new GrizzlyWebTestContainerFactory();
}
#Override
protected Application configure() {
return super.configure(); // cant't configure
}
#Test
public void test() {
Assert.assertEquals(1, 1);
}
}
I can't configure test in SomeTest.configure() just returning new JerseyConfiguration() (see below) because JerseyConfiguration's constructor expected ServiceLocator's object.
Even if it were possible to return an object of class `JerseyConfiguration` - I'm not sure that my test would work because some filters and listener are defined in the web.xml file.
How to configure the test taking into account all filters, listener and hk2-guice bridge?
Web service details
Dependencies section from build.gradle:
def jerseyVersion = '2.23.2'
def hk2Version = '2.5.0-b07'
def giuceVersion = '3.0'
dependencies {
compile "javax.servlet:javax.servlet-api:3.1.0"
//jersey
compile "org.glassfish.jersey.core:jersey-server:${jerseyVersion}"
compile "org.glassfish.jersey.containers:jersey-container-servlet:${jerseyVersion}"
//hk2
compile "org.glassfish.hk2:guice-bridge:${hk2Version}"
//guice
compile "com.google.inject:guice:${giuceVersion}"
compile "com.google.inject.extensions:guice-servlet:${giuceVersion}"
}
File web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<display-name>REST API App</display-name>
<listener>
<listener-class>com.example.core.JerseyGuiceServletContextListener</listener-class>
</listener>
<filter>
<filter-name>guiceFilter</filter-name>
<filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>guiceFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>jerseyFilter</filter-name>
<filter-class>org.glassfish.jersey.servlet.ServletContainer</filter-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>com.example.core.JerseyConfiguration</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>jerseyFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
Class JerseyGuiceServletContextListener:
public class JerseyGuiceServletContextListener extends GuiceServletContextListener {
static Injector injector;
#Override
protected Injector getInjector() {
injector = Guice.createInjector(new JerseyServletModuleConfig());
return injector;
}
}
Class JerseyServletModuleConfig:
class JerseyServletModuleConfig extends ServletModule {
#Override
protected void configureServlets() {
bind(HeyResource.class).in(Scopes.SINGLETON);
}
}
Class JerseyConfiguration:
package com.example.core;
import com.google.inject.Injector;
import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.jersey.server.ResourceConfig;
import org.jvnet.hk2.guice.bridge.api.GuiceBridge;
import org.jvnet.hk2.guice.bridge.api.GuiceIntoHK2Bridge;
import javax.inject.Inject;
import javax.servlet.ServletContext;
class JerseyConfiguration extends ResourceConfig {
#Inject
public JerseyConfiguration(ServiceLocator serviceLocator, ServletContext servletContext) {
packages("com.example.ws");
GuiceBridge.getGuiceBridge().initializeGuiceBridge(serviceLocator);
GuiceIntoHK2Bridge guiceBridge = serviceLocator.getService(GuiceIntoHK2Bridge.class);
guiceBridge.bridgeGuiceInjector(JerseyGuiceServletContextListener.injector);
}
}
just overrode configureDeployment() & modified configure() methods in test
public class SomeTest extends JerseyTestNg.ContainerPerClassTest {
#Override
protected TestContainerFactory getTestContainerFactory() throws TestContainerException {
return new GrizzlyWebTestContainerFactory();
}
#Override
protected Application configure() {
return new ResourceConfig()
.packages("com.example.ws")
.register(GensonCustomResolver.class);
}
#Override
protected DeploymentContext configureDeployment() {
Application application = configure();
return ServletDeploymentContext.builder(application)
.addListener(JerseyGuiceServletContextListener.class)
.addFilter(GuiceFilter.class, "guiceFilter")
.addFilter(ServletContainer.class, "jerseyFilter", new HashMap<String, String>(){{
put("javax.ws.rs.Application", JerseyConfiguration.class.getCanonicalName());
}})
.build();
}
#Test
public void test() {
Assert.assertEquals(1, 1);
}
}
& add web.xml to test classpath.
Related
How can I make my Spring Rest HelloWorld app work without using Spring Boot?
When running this project in tomcat 8.5 within eclipse, I expect the url "localhost:8080/hello" to show "HelloWorld", but instead it shows 404
src/main/java/com.package/HelloController.java
#RestController
public class HelloController {
#RequestMapping("/hello")
public String helloWorld() {
return "Hello World";
}
}
src/main/java/com.package/HelloConfig.java
public class HelloConfig {
#Bean
public HelloController helloController() {
return new HelloController();
}
public static void main(String[] args) {
ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(HelloConfig.class);
context.getBean(HelloController.class);
}
}
build.gradle
plugins {
id 'java'
id 'war'
id 'eclipse-wtp'
}
dependencies {
compile 'org.springframework:spring-context:5.0.3.RELEASE'
compile 'org.springframework:spring-web:5.0.3.RELEASE'
testCompile 'junit:junit:4.12'
}
repositories {
mavenCentral()
}
Answering my own question: the missing bit was the DispatcherServlet, the logic responsible for delegating http requests to Controllers, like HelloController in my example.
Based on Spring docs (https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-servlet), there are 3 ways to configure DispatcherServlet:
in web.xml
overriding WebApplicationInitializer
extending AbstractAnnotationConfigDispatcherServletInitializer (recommended for apps with Java based configurations like mine)
src/main/java/com.package/ServletInitializer:
public class ServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
return null;
}
#Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] { HelloConfig.class };
}
#Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
}
note: why the downvotes?
You may need to WebApplicationInitializer and AnnotationConfigWebApplicationContext on start up.In onStartup Method you can mention the root context of your application and access the controller mapping thereon.
public class CustomWebAppInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext container) {
AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
rootContext.register(RootConfiguration.class);
ContextLoaderListener contextLoaderListener = new ContextLoaderListener(rootContext);
container.addListener(contextLoaderListener);
AnnotationConfigWebApplicationContext webContext = new AnnotationConfigWebApplicationContext();
webContext.register(MvcConfiguration.class);
DispatcherServlet dispatcherServlet = new DispatcherServlet(webContext);
ServletRegistration.Dynamic dispatcher = container.addServlet("dispatcher", dispatcherServlet);
dispatcher.addMapping("/");
}
}
I am not sure if you have checked your web.xml for web application and you have correctly set the below configuration
<servlet>
<servlet-name>springDispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:pathToYourSpringBeanConfig/channel-application-context.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- Map all requests to the DispatcherServlet for handling -->
<servlet-mapping>
<servlet-name>springDispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- needed for ContextLoaderListener -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath: pathToYourSpringBeanConfig/channel-application-context.xml</param-value>
</context-param>
<!-- Bootstraps the root web application context before servlet initialization -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
Also you need to provide location of your context xml application where you need to tell your context like following details:
<mvc:annotation-driven></mvc:annotation-driven>
<context:component-scan base-package="group.*"></context:component-scan>
PS : if you want to access URL without war name you might wanna check this
Deploy war on Tomcat without the war name in the URL
I have a main Java application running in a tomcat environment.
Now i have written a java class, put it into a JAR file and in the TCs lib folder. I can access that class now in the main app by importing the class and calling the constructer.
is there a way to create that class once at TCs startup. so i can access the classes variables?
Thanks!
e.
//EDIT 1
here is my example:
Beach.java
public class Beach {
public static void main(String []args) {
System.out.println("***********************");
}
}
MyAppServletContextListener.java
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
public class MyAppServletContextListener implements ServletContextListener{
#Override
public void contextInitialized(ServletContextEvent arg0) {
System.out.println("**************** ServletContextListener started");
Beach x = new Beach();
}
#Override
public void contextDestroyed(ServletContextEvent arg0) {
}
}
all this goes into a jar file and into :
…/WEB-INF/lib/beach.jar
and this is my addition to Web.xml:
<web-app>
<listener>
<listener-class>
MyAppServletContextListener
</listener-class>
</listener>
</web-app>
and this is the server.log error:
10:42:26,440 | ERROR | [[/APP]] | Error configuring application listener of class MyAppServletContextListener
java.lang.ClassNotFoundException: MyAppServletContextListener
You can create a class implementing ServletContextListener
Once registered this class will allow you to invoke the desired constructor.
public class MyAppServletContextListener implements ServletContextListener{
#Override
public void contextInitialized(ServletContextEvent arg0) {
YourClass x = new YourClass();
}
#Override
public void contextDestroyed(ServletContextEvent arg0) {
}
}
You need to register this class into the web.xml:
<web-app ...>
<listener>
<listener-class>
com.yourpackage.MyAppServletContextListener
</listener-class>
</listener>
</web-app>
Could you possibly explain how I can get the ServletContext instance in my Application's sub-class? Is it possible? I have tried to do it like in the following snippet but it does not seem to work - the ctx is not set:
import javax.ws.rs.core.Application;
import javax.ws.rs.core.Context;
//...
#ApplicationPath("/")
public class MainApplication extends Application {
#Context ServletContext ctx;
#Override
public Set<Class<?>> getClasses() {
Set<Class<?>> classes = new HashSet<Class<?>>();
//...
return classes;
}
}
web.xml:
<web-app ...>
<context-param>
<param-name>environment</param-name>
<param-value>development</param-value>
</context-param>
<filter>
<filter-name>jersey-filter</filter-name>
<filter-class>org.glassfish.jersey.servlet.ServletContainer</filter-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>my.MainApplication</param-value>
</init-param>
</filter>
...
</web-app>
The problem is that I need to get context parameters from it. If there is another way, I would be grateful if somebody gave a hint.
I understand that Context annotation might not be purposed for this. Actually, I do not need ServletContext itself. If only I could get context params from web.xml, I would be absolutely happy.
Here is an example of what I really need:
import java.util.HashSet;
import java.util.Set;
import javax.servlet.ServletContext;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.Context;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
public class MainApplication extends Application {
#Context ServletContext ctx;
#Override
public Set<Object> getSingletons() {
Set<Object> set = new HashSet<Object>();
final String environment = ctx.getInitParameter("environment");
//final String environment = ... get context parameter from web xml
set.add(new AbstractBinder() {
#Override
protected void configure() {
bind(new BaseDataAccess(environment)).to(DataAccess.class);
}
});
//...
return set;
}
}
Thanks.
Since Jersey 2.5, ServletContext can be injected directly in constructor:
https://java.net/jira/browse/JERSEY-2184
public class MyApplication extends ResourceConfig {
public MyApplication(#Context ServletContext servletContext) {
// TODO
}
}
#Context can be made available on ResoureConfig by injecting it as a constructor parameter using #Context. Another way to access it is through an event handler.
Try the below code.
#ApplicationPath("...")
public class MyApplication extends ResourceConfig {
public MyApplication() {
register(StartupHandler.class);
}
private static class StartupHandler extends AbstractContainerLifecycleListener {
#Context
ServletContext ctx;
#Override
public void onStartup(Container container) {
// You can put code here for initialization.
}
}
// ...
Injection happens when you enter service method. Check if this is a problem.
There is interesting statement in documentation for Jersey version 1.18 for class
com.sun.jersey.spi.container.servlet.ServletContainer
The servlet or filter may be configured to have an initialization
parameter "com.sun.jersey.config.property.resourceConfigClass" or
"javax.ws.rs.Application" and whose value is a fully qualified name of
a class that implements ResourceConfig or Application. If the concrete
class has a constructor that takes a single parameter of the type Map
then the class is instantiated with that constructor and an instance
of Map that contains all the initialization parameters is passed as
the parameter.
If my understanding is correct the following constructor must be invoced with "an instance of Map that contains all the initialization parameters"
public class ExampleApplication extends Application {
public ExampleApplication(Map initParams) {
}
...
}
Here is appropriate part of web.xml:
<servlet>
<servlet-name>Jersey Web Application</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>experiment.service.ExampleApplication</param-value>
</init-param>
</servlet>
But somehow it failed for me with the following message:
SEVERE: Missing dependency for constructor public
experiment.service.ExampleApplication(java.util.Map) at parameter
index 0
And for current version of Jersey (2.5.1) there are no such statement in documentstion:
https://jersey.java.net/apidocs/latest/jersey/org/glassfish/jersey/servlet/ServletContainer.html
You can use the ApplicationEventListener interface to get the ServletContext. After initialization has finished, you can 'catch' an ApplicationEvent and use the injected ServletContext to work with.
Works fine with: org.glassfish.jersey : 2.12
For additional versions, pls use comments - i dont know, sry.
Jersey Docs - 20.1.2. Event Listeners
Your MainApplication:
#ApplicationPath("/")
public class MainApplication extends Application {
#Override
public Set<Class<?>> getClasses() {
Set<Class<?>> set = new HashSet<Class<?>>();
set.add(MainApplicationListener.class);
return classes;
}
}
... or alternative MainResourceConfig (I prefer to use this one):
public class MainResourceConfig extends ResourceConfig {
public MainResourceConfig() {
register(MainApplicationListener.class);
}
}
And the ApplicationEventListener:
public class MainApplicationListener implements ApplicationEventListener {
#Context
private ServletContext ctx; //not null anymore :)
#Override
public void onEvent(ApplicationEvent event) {
switch (event.getType()) {
case INITIALIZATION_FINISHED:
// do whatever you want with your ServletContext ctx
break;
}
#Override
public RequestEventListener onRequest(RequestEvent requestEvent) {
return null;
}
}
Don't use #Context in your Application but in a Resource class.
#Path("/foos")
public class FooResource {
#Context
ServletContext ctx;
#GET
public Response getFoos() {
return Response.ok().build();
}
}
Is it possible to use Guice AOP to intercept an annotated method on a Jersey resource?
I have a successfully configured Guice integration working with Jersey with respect to Dependency Injection without any problems, however my configured Interceptor is not intercepting my annotated method at all.
web.xml
<listener>
<listener-class>my.package.GuiceConfig</listener-class>
</listener>
<filter>
<filter-name>guiceFilter</filter-name>
<filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>guiceFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
GuiceConfig configuration module
public class GuiceConfig extends GuiceServletContextListener {
#Override
protected Injector getInjector() {
return Guice.createInjector(new JerseyServletModule() {
#Override
protected void configureServlets() {
bindInterceptor(Matchers.any(),
Matchers.annotatedWith(RequiredAuthority.class),
new AuthorisationInterceptor());
Map<String, String> params = new HashMap<String, String>();
params.put(JSP_TEMPLATES_BASE_PATH, "/WEB-INF/jsp");
params.put(FEATURE_FILTER_FORWARD_ON_404, "true");
params.put(PROPERTY_PACKAGES, "my.service.package");
filter("/*").through(GuiceContainer.class, params);
}
});
}
}
RequiredAuthority annotation
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface RequiredAuthority {
String value();
}
AuthorisationInterceptor aspect
public class AuthorisationInterceptor implements MethodInterceptor {
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
// Allow invocation to process or throw an appropriate exception
}
}
TempResource JAX-RS resource class
#Path("/temp")
public class TempResource {
#GET
#Produces(MediaType.APPLICATION_JSON)
#RequiredAuthority("PERMISSION")
public String getTemp() {
// Return resource normally
}
}
Looks like configureServlets() isn't calling:
bind(TempResource.class);
I'm new to Guice and already stuck :)
I pretty much copied classes GuiceConfig, OfyFactory and slightly modified Ofy from Motomapia project (which you can browse) using it as s sample.
I created GuiceServletContextListener which looks like this
public class GuiceConfig extends GuiceServletContextListener
{
static class CourierServletModule extends ServletModule
{
#Override
protected void configureServlets()
{
filter("/*").through(AsyncCacheFilter.class);
}
}
public static class CourierModule extends AbstractModule
{
#Override
protected void configure()
{
// External things that don't have Guice annotations
bind(AsyncCacheFilter.class).in(Singleton.class);
}
#Provides
#RequestScoped
Ofy provideOfy(OfyFactory fact)
{
return fact.begin();
}
}
#Override
public void contextInitialized(ServletContextEvent servletContextEvent)
{
super.contextInitialized(servletContextEvent);
}
#Override
protected Injector getInjector()
{
return Guice.createInjector(new CourierServletModule(), new CourierModule());
}
}
I added this listener into my web.xml
<web-app>
<listener>
<listener-class>com.mine.courierApp.server.GuiceConfig</listener-class>
</listener>
<!-- GUICE -->
<filter>
<filter-name>GuiceFilter</filter-name>
<filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>GuiceFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
<dispatcher>INCLUDE</dispatcher>
</filter-mapping>
<!-- My test servlet -->
<servlet>
<servlet-name>TestServlet</servlet-name>
<servlet-class>com.mine.courierApp.server.TestServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>TestServlet</servlet-name>
<url-pattern>/test</url-pattern>
</servlet-mapping>
</web-app>
OfyFactory looks like this
#Singleton
public class OfyFactory extends ObjectifyFactory
{
Injector injector;
#Inject
public OfyFactory(Injector injector)
{
this.injector = injector;
register(Pizza.class);
register(Ingredient.class);
}
#Override
public <T> T construct(Class<T> type)
{
return injector.getInstance(type);
}
#Override
public Ofy begin()
{
return new Ofy(super.begin());
}
}
Ofy doesn't have any Guice annotations at all...
public class Ofy extends ObjectifyWrapper<Ofy, OfyFactory>
{
// bunch of helper methods here
}
And finally test servlet where I'm trying to use injected field looks like this
public class TestServlet extends HttpServlet
{
#Inject Ofy ofy;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
ofy.save(new Pizza());
}
}
Ofy ofy is always null. It's never injected. And it's not injected because OfyFactory is never instantiated, its constructor is never called.
Could you please point what I'm doing wrong? Why my singleton is never created?
Thanks a lot.
Instead of defining TestServlet in the web.xml file, try deleting its mapping from web.xml and adding this line in the configureServlets() method:
serve("/test").with(TestServlet.class);
You may also need to bind TestServlet as a Singleton either by annotating the class with #Singleton or by adding a
bind(TestServlet.class).in(Singleton.class);
line to one of the modules.
What's happening is that Guice is not actually creating your servlet so it isn't able to inject the Ofy object. Guice will only create servlets if it is instructed to do so using a serve(...).with(...) binding. Any servlets defined in the web.xml are outside of Guice's control.