Is Servlet 3.0 XML-less configuration possible with tapestry-spring? - java

I started writing an app using Spring MVC, then decided to go with Tapestry instead. I want to keep the XML-less configuration approach that I was using originally. I first tried this:
public class ServletConfig implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
// Spring //
AnnotationConfigWebApplicationContext rootContext = new
AnnotationConfigWebApplicationContext();
rootContext.register(PersistenceContext.class, ApplicationContext.class);
servletContext.addListener(new ContextLoaderListener(rootContext));
// Tapestry //
servletContext.setInitParameter("tapestry.app-package", "...");
FilterRegistration.Dynamic filter = servletContext.addFilter("app", TapestrySpringFilter.class);
filter.addMappingForUrlPatterns(null, false, "/*");
}
}
The problem here is that tapestry creates another ContextLoaderListener, using the empty constructor. Instead of taking in a WebApplicationContext, it looks at the contextClass and contextConfigLocation init parameters. So I tried this:
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
// Spring //
servletContext.setInitParameter("contextClass",
"org.springframework.web.context.support.AnnotationConfigWebApplicationContext");
servletContext
.setInitParameter(
"contextConfigLocation",
"...config.PersistenceContext ...config.ApplicationContext");
// Tapestry //
servletContext.setInitParameter("tapestry.app-package", "...");
FilterRegistration.Dynamic filter = servletContext.addFilter("app", TapestrySpringFilter.class);
filter.addMappingForUrlPatterns(null, false, "/*");
}
Which caused this:
java.lang.IllegalArgumentException: When using the Tapestry/Spring integration library, you must specifiy a context class that extends from org.apache.tapestry5.spring.TapestryApplicationContext. Class org.springframework.web.context.support.AnnotationConfigWebApplicationContext does not. Update the 'contextClass' servlet context init parameter.
TapestryApplicationContext inherits from org.springframework.web.context.support.XmlWebApplicationContext. So my question(s): is there a way to make annotation-based configuration work with this approach? If not, is there another approach that will allow me to use it? Or is there no way to avoid the XML?
I tried reverting to the first version of ServletConfig I put up on here, but I added
servletContext.setInitParameter("tapestry.use-external-spring-context", "true");
I'm no longer getting the error message, but the page isn't loading either. When I try to load /app/, instead of loading the index page I get this:
<html>
<head>
<title>Error</title>
</head>
<body>/app/index.jsp</body>
</html>
I'm not sure what's causing this, I can't find anything in the logs to indicate any problems. I'm thinking that there's some sort of issue with the dispatcher service. Has anybody seen this type of error before? I'm not sure if this is unrelated to my original problem or if this is a symptom of my approach not being valid. If somebody can tell me that this is a separate issue I'll take the appropriate action.

The out-of-the-box spring integration expects an XML file.
It wouldn't be too hard to extend SpringModuleDef and override locateApplicationContext to return an AnnotationConfigApplicationContext
You would then write your own TapestrySpringFilter implementation which loads your new SpringModuleDef subclass.
--edit---
I was wrong, the tapestry spring integration uses WebApplicationContextUtils.getWebApplicationContext(servletContext) to lookup the ApplicationContext. So you could initialize the servlet context with an ApplicationContext prior to loading the TapestryStringFilter and it should just work.
eg
ApplicationContext myContext = createAnnotationBasedAppContext();
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, myContext);

Related

Spring 4 WebApplicationInitializer log4j2 multiple initialization issues

I am building an application using 100% code configuration approach for a spring 4 web app. Following is my web config class.
public class WebAppInitializer extends Log4jServletContainerInitializer implements WebApplicationInitializer {
public void onStartup(ServletContext container) throws ServletException {
super.onStartup(null, container);
// Create the 'root' Spring application context
AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
rootContext.register(MyAppContext.class);
// Manage the lifecycle of the root application context
container.addListener(new ContextLoaderListener(rootContext));
// Create the dispatcher servlet's Spring application context
AnnotationConfigWebApplicationContext webContext = new AnnotationConfigWebApplicationContext();
webContext.register(MyServletContext.class);
// Register and map the dispatcher servlet
ServletRegistration.Dynamic dynamic = container.addServlet("dispatcher", new DispatcherServlet(webContext));
dynamic.setLoadOnStartup(1);
dynamic.addMapping("/api/*");
}
}
Problem -
a. My spring beans are getting initialized twice
b. Whenever I add logj2.xml in my resources (using maven), then my bean creation fails.
I am new to this, kindly help me.
Log4J - 2.5, Tomcat - 8.0.32
Thanks!
I managed to fix it. It was not the problem with WebApplInitializer but with Spring Java Configurations files. I was maintaining separate configs for ApplicationContext and ServletContext. In ApplicationContext, using
#ComponentScan(value = "com.application.module",
excludeFilters = {#ComponentScan.Filter(value = {Configuration.class})})
did the trick.
In servlet context, I used -
#ComponentScan(basePackageClasses = AppContext.class)

How to configure Spring Security with already existing WebApplicationInitializer?

I am doing a web site project with Spring/ Spring MVC I learned how to configure a spring with the java classes and annotations approach, which by far is better than the XML.
Now I want to use Spring Security with my application. However I could not understand how to configure it with already existing WebApplicationInitializer ?
The Spring documentation is not so clear.
Here is my code and what I have so far:
public class AppInitializer implements WebApplicationInitializer{
//public class AppInitializer {
private static final Class<?>[] CONFIG_CLASSES = new Class<?>[]{SiteConfigs.class, AdminConfigurations.class};
public void onStartup(ServletContext servletContext) throws ServletException {
AnnotationConfigWebApplicationContext appContext = new AnnotationConfigWebApplicationContext();
appContext.register(CONFIG_CLASSES);
DispatcherServlet servlet = new DispatcherServlet(appContext);
ServletRegistration.Dynamic dispatcher = servletContext.addServlet("dispatcher", servlet);
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/");
}
}
If I add AbstractSecurityWebApplicationInitializer to the classpath the container throws
exception.
HTTP Status 500 - No WebApplicationContext found: no ContextLoaderListener registered?
So how to config Spring Security so that springSecurityFilterChain get initialized
The exception tells you that you don't have a ContextLoaderListener which, in your case, is true. You only have a DispatcherServlet. By default Spring Security will only lookup the filters from the root application context (the one loaded by the ContextLoaderListener).
If you want to let it use a DispatcherServlets context instead you have to tell it that. You can tell it which to use by overriding the getDispatcherWebApplicationContextSuffix() method.
You aren't limited to a single WebApplicationInitializer you can have multiple in general you want to have one for your application bootstrapping and another to add security. You can then leverage the Spring convenience classes to save a couple of lines of code. See the Spring Security Reference for a sample.

Injecting an EJB into a dynamic mapped servlet

I have a filter where I am dinamically mapping servlet classes:
#Override
public void init( FilterConfig filterConfig ) throws ServletException {
servletContext = filterConfig.getServletContext();
File directory = getConventionDirectory();
FileSystemInspector fileInspector = new FileSystemInspector();
Set<ActionInfoData> actions = fileInspector.getActions( directory );
for ( ActionInfoData action : actions ) {
servletContext
.addServlet( action.getServletName(), action.getClassName() )
.addMapping( action.getServletMapping() );
}
}
Then when I access a given mapping the EJB is not injected.
#EJB
private I18nManager i18nManager;
#Override
protected void doGet( HttpServletRequest request, HttpServletResponse response )
throws ServletException, IOException {
I18nManager i18n = i18nManager; //null
}
If I manually create a mapping in the web.xml the given EJB is working in that servlet.
It makes me wonder if the fact I am registering the servlets at runtime the container does not consider those servlets as managed.
If that is the case what is the proper way to inject the EJBs into my servlets without changing the way they are dinamically registered via filter?
Is via JNDI the only way to inject my EJBs?
EDIT 1:
I have tried to implement a ServletContextListener class as suggested by "Will" using the following code in the web.xml:
<listener>
<listener-class>com.megafone.web.filter.convention.InitServlet</listener-class>
</listener>
And the relevant part of the implementation:
...
#Override
public void contextInitialized( ServletContextEvent sce ) {
ServletContext servletContext = sce.getServletContext();
FileSystemInspector fileInspector = new FileSystemInspector();
Set<ActionInfoData> actions = fileInspector.getActions( getConventionDirectory() );
for ( ActionInfoData action : actions ) {
servletContext
.addServlet( action.getServletName(), action.getClassName() )
.addMapping( action.getServletMapping() );
}
}
...
Unfortunately it does not make the container inject the EJBs and the null pointer remains. I am currently making a custom type safe JNDI lookup to the service. Obviously this is far more expensive than using the proper injection (correct me if I am wrong, have done no experiments regarding performance yet).
Using:
Java EE 6
JBoss AS 7.1
The problem seems related to this reported bug which is as yet unresolved. Resource resolution works just fine for Managed Beans as defined by the JSF specification, but not for CDI Managed Beans. Simply annotating your dynamic servlet classes with #javax.faces.bean.ManagedBean should fix the problem (yes, its quite an ugly solution):
#ManagedBean
public class DynServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
#EJB
private LoginService loginService;
public DynServlet() {
super();
}
#Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
response.getOutputStream().println(
"Request made to: " + getClass().getSimpleName());
response.getOutputStream().println("Login Service: " + loginService);
return;
}
}
#WebListener
public class DynamicServletLoadListener implements ServletContextListener {
public DynamicServletLoadListener() {
super();
}
#Override
public void contextDestroyed(final ServletContextEvent contextEvent) {
return;
}
#Override
public void contextInitialized(final ServletContextEvent contextEvent) {
contextEvent.getServletContext().addServlet("dynservlet", DynServlet.class)
.addMapping("/services/dynservlet");
}
}
Tested with JEE6 (ofc) and both JBoss 7.1.1 and 7.2.0 (EAP 6.1.0 Alpha).
Edit:
The problem with dynamic mapped servlets is actually pretty deep in the base JBoss architecture. They use JBossWeb (a forked version of Tomcat) as the servlet implementation, and in the guts of its context management code it makes a determination wether to instantiate new components via injection or regular new. Afaik as of date, your servlets would need to be annotated in some fashion in order for them to be processed via injection: I had mentioned #ManagedBean in my original answer but it looks like annotating with #WebServlet works as well.
Servlet 3.0 Spec, Sect. 4.4.3.5
Resource injection [e.g. #EJB] on all components (Servlets, Filters and Listeners) added
programmatically or created programmatically, other than the ones added via the
methods that takes an instance, will only be supported when the component is a
Managed Bean. For details about what is a Managed Bean please refer to the
Managed Bean specification defined as part of Java EE 6 and JSR 299.
Managed Bean Declaration
A Java EE 6 managed bean is annotated #javax.annotation.ManagedBean and has a no-arg constructor. A JSR 299 (CDI) managed bean merely needs a no-arg constructor or a constructor annotated #javax.inject.Inject.
Answer
To enable resource injection you need to:
place #ManagedBean annotation on dynamically added servlet
OR
enable CDI & include an empty beans.xml
Edit
Even though you're creating the servlet dynamically, it's important that the container does the creation. Don't think creation within ServletContext will support injection. Servlet doc very vague here.
With CDI try:
servletContext.addServlet("your servlet name", #Inject YourServletClass servlet)
First, in my test, this worked fine using a version of Glassfish V3.
But, second, you may well be running afoul of this clause of the Servlet 3.0 spec.
The following methods are added to ServletContext since Servlet 3.0 to
enable programmatic definition of servlets, filters and the url
pattern that they map to. These methods can only be called during the
initialization of the application either from the contexInitialized
method of a ServletContextListener implementation or from the
onStartup method of a ServletContainerInitializer implementation.
Notably, these methods can NOT be called from a Filter.init() method. I originally tried this in a Servlet.init() method, and the init method failed because the context was already initialized.
So, my experiment did not duplicate your test exactly -- I did not use a Filter.init() method for this, rather I put the code in a ServletContextListener. And when I did that, my #EJB annotation was honored.
Edit:
As un-helpful as it sounds, I would suggest this is a bug in JBoss. When I initially tried your original code with the injection from the Filter, Glassfish threw an exception, since you're not allowed to do the injection save where I mentioned earlier. Now perhaps that's an "added feature" of JBoss, but apparently the #EJB injection processing simply isn't working. According to spec, this should work as advertised.

Ordering Listeners in Spring's WebApplicationInitializer

I have a custom ServletContextListener that I am using to initialize and start up a Cron4J scheduler.
public class MainListener implements ServletContextListener {
#Value("${cron.pattern}")
private String dealHandlerPattern;
#Autowired
private DealMoqHandler dealMoqHandler;
}
I am autowiring some objects in the Listener as shown, and would like for Spring to manage the listener's instantiation. I am using programmatic web.xml configuration through WebApplicationInitializer, but so far the Listener isn't being autowired (NullPointerExceptions whenever I try to access the supposedly autowired objects).
I've already tried to add my customer listener after adding the ContextLoaderListener, shown below:
public class CouponsWebAppInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext container) throws ServletException {
AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
rootContext.register(SpringAppConfig.class);
// Manage the lifecycle of the root application context
container.addListener(new ContextLoaderListener(rootContext));
container.addListener(new MainListener()); //TODO Not working
}
I checked these past questions Spring - Injecting a dependency into a ServletContextListener and dependency inject servlet listener and tried to implement the following code inside the contextInitialized method of my listener:
WebApplicationContextUtils
.getRequiredWebApplicationContext(sce.getServletContext())
.getAutowireCapableBeanFactory()
.autowireBean(this);
However, I just get the following exception:
Exception sending context initialized event to listener instance of class com.enovax.coupons.spring.CouponsMainListener: java.lang.IllegalStateException: No WebApplicationContext found: no ContextLoaderListener registered?
at org.springframework.web.context.support.WebApplicationContextUtils.getRequiredWebApplicationContext(WebApplicationContextUtils.java:90) [spring-web-3.1.1.RELEASE.jar:3.1.1.RELEASE]
How can I make sure that Spring has already finished instantiation before adding my listener?
My mistake. It turns out the code in the original post is correct, and the listeners are being added in the correct order.
The issue was that I had annotated my custom listener with a #WebListener annotation (Servlet 3.0). This was causing the web app to disregard my addListener() code in WebApplicationInitializer, and instantiate the custom listener AHEAD of Spring's ContextLoaderListener.
The following code block illustrates the ERRONEOUS code:
#WebListener /* should not have been added */
public class CouponsMainListener implements ServletContextListener {
#Autowired
private Prop someProp;
}
You cannot use new with Spring beans - Java doesn't care about Spring and Spring has no way to modify the behavior of the new operator. If you create your objects yourself, you need to wire them yourself.
You also need to be careful what you do during initialization. When using Spring, use this (simplified) model: First, Spring creates all the beans (calls new for all of them). Then it starts to wire them.
So you must be extra careful when you start to use the autowired fields. You can't always use them right away, you need to make sure that Spring is finished initializing everything.
In some cases, you can't even use autowired fields in #PostProcess methods because Spring couldn't create the bean because of cyclic dependencies.
So my guess is that "whenever I try to access" is too early.
For the same reason, you can't use WebApplicationContextUtils in the WebApplicationInitializer: It hasn't finished setting up Spring, yet.

Spring #Autowired in Servlet

I am using Spring framework (2.5.4) in my app with Load time weaving and everything works fine everywhere (in Spring beans, in non-Spring entities), except when I try to autowire field in a servlet annotated as #Configurable, then I get a nice NullPointerException...
#Configurable(dependencyCheck=true)
public class CaptchaServlet extends HttpServlet{
#Autowired
private CaptchaServiceIface captchaService;
#Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
// ApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(config.getServletContext());
// captchaService = (CaptchaServiceIface) ctx.getBean("captchaService");
}
#Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Captcha c = captchaService.getCatpcha();
req.getSession().setAttribute("captchaAnswer", c.getAnswer());
resp.setContentType("image/png");
ImageIO.write(c.getImage(), "png", resp.getOutputStream());
}
}
<context:load-time-weaver/>
<context:spring-configured/>
<context:component-scan base-package="cz.flexibla2" />
Any suggestions about what am I doing incorrectly?
Thanks.
This is likely because the Servlet is being instantiated and initialized by the servlet container, before the Spring context is being initialized, and it's the Spring context which handles the load-time weaving.
Is your <context:load-time-weaver/> stuff being handle inside the servlet Spring context/ or at the webapp-level? The former almost certainly won't work (for the reasons specified above), but a webapp-level config might work (using ContextLoaderListener).
See also mailing list discussion and bug report at https:// bugs.eclipse.org/bugs/show_bug.cgi?id=317874 . I agree that intuitively the #Configurable annotation on the servlet should be enough to indicate to the spring framework that the servlet when instantiated will be configured by spring, when using <context:spring-configured/>. I have also observed that the desired behavior is achievable when using the -javaagent:/path/to/aspectjweaver.jar instead of spring-instrument*.jar or spring-agent.jar. Please raise an issue with Spring Jira at https:// jira.springframework.org/browse/SPR. I believe that the problem may be that the servlet class - not an instance of the servlet, but the class itself - is loaded before the spring ContextLoaderListener is called, thus the spring framework does not have a chance to instrument the servlet class before it is loaded.
The spring instrumentation for load-time-weaving appears to be based on being able to transform class bytecode before it is loaded. If the servlet container is holding onto an instance of the Class object that was obtained before it was transformed by spring, then it (the servlet container) would not be able to produce instances of the transformed class, nor would spring be able to instrument instances created using the factory methods on that Class object.

Categories