Log4j2 programmatic configuration for a webapp - java

I am trying to setup a configurable log4j configuration path for our webapp, so that by default this setting applies:
<context-param>
<param-name>log4jConfiguration</param-name>
<param-value>/WEB-INF/log4j2.xml</param-value>
</context-param>
and if, lets say, some environment variable is defined, then log4j2-test.xml is used. Can I somehow achieve this?
I've tried defining my own listener, which will take care of this, as follows:
web.xml contents:
<context-param>
<param-name>log4jConfiguration</param-name>
<param-value>/WEB-INF/log4j2.xml</param-value>
</context-param>
<context-param>
<param-name>isLog4jAutoInitializationDisabled</param-name>
<param-value>true</param-value>
</context-param>
<listener>
<listener-class>com.bla.blob.listener.Log4j2InitListener</listener-class>
</listener>
And the listener class:
public class Log4j2InitListener implements ServletContextListener {
#Override
public void contextInitialized(ServletContextEvent sce) {
ServletContext ctx = sce.getServletContext();
if (System.getProperty("log4j.configurationFile") != null) { //override what is defined in web.xml
ctx.setInitParameter("log4jConfiguration", System.getProperty("log4j.configurationFile"));
}
ctx.addListener(new Log4jServletContextListener());
final FilterRegistration.Dynamic filter = ctx.addFilter("log4jServletFilter",
new Log4jServletFilter());
if (filter != null) {
filter.setAsyncSupported(true);
filter.addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), false, "/*");
}
}
#Override
public void contextDestroyed(ServletContextEvent sce) { }
}
But for some strange reason this solution is not tomcat compatible (though works on jetty) -
java.lang.IllegalArgumentException: Once the first ServletContextListener has been called, no more ServletContextListeners may be added.
at org.apache.catalina.core.ApplicationContext.addListener(ApplicationContext.java:1386)
at org.apache.catalina.core.ApplicationContextFacade.addListener(ApplicationContextFacade.java:659)
Also, apache moved everything webapp related out of core, making everything package private, so you can't fiddle with classes by yourself anymore (yet http://logging.apache.org/log4j/2.x/manual/webapp.html still suggest you can use Log4jWebLifeCycle, for instance).
Any help will be greatly appreciated.

Related

Logging from servlet context destroyed event

Within my Servlet-based application, I would like to log events for startup and shutdown.
I've tried to implement the ServletContextListener interface to do this:
public class DiagnosticListener
implements ServletContextListener {
private static final Logger LOG = LogManager.getLogger(DiagnosticListener.class);
#Override
public void contextInitialized( final ServletContextEvent sce ) {
LOG.info("Context initialized.");
}
#Override
public void contextDestroyed( final ServletContextEvent sce ) {
LOG.info("Context destroyed.");
}
}
The initialized event is logged as expected, but the destroyed event never appears. I am assuming this is to do with how log4j2 manages its lifecycle using a similar listener, that logging infrastructure is no longer available during this event.
Is there a way to log an event for the application being shut down?
We clashed against a similar issue with Logback.
You have to write your own web.xml to fix that, because there's no alternatives to define listeners order.
We disabled the LogbackServletContextListener with:
<context-param>
<param-name>logbackDisableServletContainerInitializer</param-name>
<param-value>true</param-value>
</context-param>
then add the LogbackServletContextListener by hand as the first listener:
<listener>
<listener-class>ch.qos.logback.classic.servlet.LogbackServletContextListener</listener-class>
</listener>
and then all the other listeners.
No idea about log4j, but I think there's something similar...
edit: yes, there is:
<context-param>
<param-name>isLog4jAutoInitializationDisabled</param-name>
<param-value>true</param-value>
</context-param>
source: https://logging.apache.org/log4j/2.x/manual/webapp.html
If you configured Log4j's ServletContextListener before yours in web.xml then Log4j should be initialized before your ServletContextListener and be shutdown after yours is.

How do I configure my entity-filtering scope for security annotations in the web.xml?

Reading the jersey doc : https://jersey.java.net/documentation/latest/entity-filtering.html I was able to activate the SecurityEntityFilteringFeature by adding it to my web.xml along with other activated features.
So my web.xml's features part looks like that :
...
<init-param>
<param-name>jersey.config.server.provider.classnames</param-name>
<param-value>
org.glassfish.jersey.server.gae.GaeFeature;
org.glassfish.jersey.server.mvc.jsp.JspMvcFeature;
org.glassfish.jersey.media.multipart.MultiPartFeature;
org.glassfish.jersey.server.filter.RolesAllowedDynamicFeature;
org.glassfish.jersey.message.filtering.SecurityEntityFilteringFeature;
</param-value>
</init-param>
...
The annotations #PermitAll (which changes nothing) and #DenyAll (which always remove entity from json) work great.
The question is : to use the annotation #RolesAllowed I also need to register the roles in the entity-filtering scope as said in the documentation
EntityFilteringFeature.ENTITY_FILTERING_SCOPE - "jersey.config.entityFiltering.scope"
Defines one or more annotations that should be used as entity-filtering scope when reading/writing an entity.
But I can only configure it through my web.xml and I have nowhere to do the following :
new ResourceConfig()
// Set entity-filtering scope via configuration.
.property(EntityFilteringFeature.ENTITY_FILTERING_SCOPE, new Annotation[] {SecurityAnnotations.rolesAllowed("manager")})
// Register the SecurityEntityFilteringFeature.
.register(SecurityEntityFilteringFeature.class)
// Further configuration of ResourceConfig.
.register( ... );
Any guess ?
You can use a ResourceConfig and a web.xml together. It is not "either one or the other". For example
<servlet>
<servlet-name>MyApplication</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>org.foo.JerseyConfig</param-value>
</init-param>
</servlet>
package org.foo;
public class JerseyConfig extends ResourceConfig {
public JerseyConfig() {
register(...);
property(...);
}
}
Both the web.xml and the ResourceConfig registrations/configuration/properties, etc will be used. You can see some other deployment options, here.
If you really must stay away from the ResourceConfig (not sure why it would be such a problem), you can always create a Feature.
#Provider
public class MyFilteringFeature implements Feature {
#Override
public boolean configure(FeatureContext context) {
context.property(...);
context.register(...);
return true;
}
}
Then just register the feature (unless you are scanning packages, then it should be picked up with the #Provider annotation).

How to serve JSPs using Guice 3.0?

I'm trying to serve a JSP from Guice. I don't find any basic examples on how to do this!
My setup :
web.xml
<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>
<listener>
<listener-class>org.example.Bootstrap</listener-class>
</listener>
org.example.Bootstrap (something like...)
public class Bootstrap extends GuiceServletContextListener
{
#Override
protected Injector getInjector()
{
return Guice.createInjector(new org.example.BootstrapModule());
}
}
org.example.BootstrapModule (something like...)
public class BootstrapModule extends ServletModule
{
#Override
protected void configureServlets()
{
// serve .JSPs
bind(org.apache.jasper.servlet.JspServlet.class).in(Scopes.SINGLETON);
serveRegex("/.*\\.jsp").with(org.apache.jasper.servlet.JspServlet.class);
// serve my controllers
bind(MainServlet.class).in(Scopes.SINGLETON);
serveRegex("/.*").with(MainServlet.class);
}
}
In MainServlet, I do something like :
request.getRequestDispatcher("test.jsp").include(request, response);
or
request.getRequestDispatcher("test.jsp").forward(request, response);
or
request.getRequestDispatcher("/test.jsp").include(request, response);
or
request.getRequestDispatcher("/test.jsp").forward(request, response);
My test.jsp is in webapp/test.jsp (I use Maven).
It doesn't work! I always end up with errors like :
SEVERE: PWC6117: File XXX not found
It seems the informations Guice passes to org.apache.jasper.servlet.JspServlet are not the ones required for the JSPs to work.
What am I missing? Do I even have to specify org.apache.jasper.servlet.JspServlet manually? What is required to correctly serve JSPs from Guice?
It seems this is a known bug.
As a workaround, some say you can compile the TRUNK of Guice. I also found that setting
request.setAttribute(org.apache.jasper.Constants.JSP_FILE, "/test.jsp");
before the forwarding also works.
But I have to run more tests to see what I'll use until Guice is fixed in a public release.
You need to override Bootstrap#getModule() to return a new BootstrapModule().
#Override
protected Module getModule() {
return new BootstrapModule();
}

Java ServletContext

I have a JSP web site, not Spring MVC, and it has a config file web.xml.
There are a couple of settings within the web.xml file that I'd like to get.
However, I want to access those settings from within a class sitting in my Source Packages folder.
I know I can pass the ServletContect from the JSP to the class but I want to avoid this and just access the web.xml file from my class.
Is this possible?
EDIT
I have been looking at javax.servlet thinking what I want was in there but if it is I can't see it.
Using a javax.servlet.ServletContextListener implementation, that allows a singleton-like access to context:
package test.dummy;
import javax.servlet.ServletContextListener;
import javax.servlet.ServletContextEvent;
public class ContextConfiguration implements ServletContextListener {
private static ContextConfiguration _instance;
private ServletContext context = null;
//This method is invoked when the Web Application
//is ready to service requests
public void contextInitialized(ServletContextEvent event) {
this.context = event.getServletContext();
//initialize the static reference _instance
_instance=this;
}
/*This method is invoked when the Web Application has been removed
and is no longer able to accept requests
*/
public void contextDestroyed(ServletContextEvent event) {
this.context = null;
}
/* Provide a method to get the context values */
public String getContextParameter(String key) {
return this.context.getInitParameter(key);
}
//now, provide an static method to allow access from anywere on the code:
public static ContextConfiguration getInstance() {
return _instance;
}
}
Set it up at web.xml:
<web-app>
<listener>
<listener-class>
test.dummy.ContextConfiguration
</listener-class>
</listener>
<servlet/>
<servlet-mapping/>
</web-app>
And use it from anywhere at the code:
ContextConfiguration config=ContextConfiguration.getInstance();
String paramValue=config.getContextParameter("parameterKey");
I think this is very close to your description: link.
Basically, you want to read parameters from web.xml programatically, right?
Hmmm... I am assuming that once your web app is up then you are not going to make any change in the web.xml....
Now what you can do is a create a servlet which loads on the startup and initialize a singleton class. You can use the following setting in your web.xml.
<servlet>
<description></description>
<display-name>XMLStartUp</display-name>
<servlet-name>XMLStartUp</servlet-name>
<servlet-class>com.test.servlets.XMLStartUp</servlet-class>
<init-param>
<param-name>log4j-init-file</param-name>
<param-value>WEB-INF/classes/log4j.properties</param-value>
</init-param>
<load-on-startup>0</load-on-startup>
</servlet>
In tomcat if you set load-on-startup value 0, then it means that while loading it has got the highest priority. now in the servlets init method read all the init-parameters like this and set it in your singleton class.
String dummy= getInitParameter("log4j-init-file");
This is not easily possible and may not be an elegent solution. The only option I can suggest is to have your configuration options i a xml or properties file and put it in your WEB-INF/classes directory so you can look it up using ClassLoader.getResource or ClassLoader.getResourceAsStream
I know it may be a duplication of the configuration, but IMO its the elegent way.
I really don't like classes reading from web.xml... Why do you need that?
IMHO it would be easier, cleaner and by far much more manageable if you prepared a properties file and a manager class that reads from there.

Is there a way to run a method/class only on Tomcat/Wildfly/Glassfish startup?

I need to remove temp files on Tomcat startup, the pass to a folder which contains temp files is in applicationContext.xml.
Is there a way to run a method/class only on Tomcat startup?
You could write a ServletContextListener which calls your method from the contextInitialized() method. You attach the listener to your webapp in web.xml, e.g.
<listener>
<listener-class>my.Listener</listener-class>
</listener>
and
package my;
public class Listener implements javax.servlet.ServletContextListener {
public void contextInitialized(ServletContext context) {
MyOtherClass.callMe();
}
}
Strictly speaking, this is only run once on webapp startup, rather than Tomcat startup, but that may amount to the same thing.
You can also use (starting Servlet v3) an annotated aproach (no need to add anything to web.xml):
#WebListener
public class InitializeListner implements ServletContextListener {
#Override
public final void contextInitialized(final ServletContextEvent sce) {
}
#Override
public final void contextDestroyed(final ServletContextEvent sce) {
}
}
I'm sure there must be a better way to do it as part of the container's lifecycle (edit: Hank has the answer - I was wondering why he was suggesting a SessonListener before I answered), but you could create a Servlet which has no other purpose than to perform one-time actions when the server is started:
<servlet>
<description>Does stuff on container startup</description>
<display-name>StartupServlet</display-name>
<servlet-name>StartupServlet</servlet-name>
<servlet-class>com.foo.bar.servlets.StartupServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>

Categories