shared classloader with embedded Tomcat 8 - java

I have upgraded tomcat from version 7.0.34 to version 8.0.33, and since then I have been facing a problem to share the web application context and Junit context.
I have a web application with singleton class that gathers statistic data about the web application. I also have Junit that runs the web application in embedded tomcat. the Junit queries the web application and then checks the statistic data.
I try to make a simple example:
the singleton:
public class Counter {
private static Counter instance;
private AtomicLong counter;
private Counter(){}
public static Counter getInstance(){
if(instance == null){
synchronized (Counter.class) {
if(instance == null){
instance = new Counter();
}
}
}
return instance;
}
public long incrementAndGet(){
return counter.incrementAndGet();
}
public long getValue(){
return counter.get();
}
}
the servlet:
#WebServlet(name="servlet",loadOnStartup=1, urlPatterns="/servletTest")
public class Servlet extends HttpServlet{
#Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("Hi, you are the #" + Counter.getInstance().incrementAndGet() + " visitor");
}
}
contextListener:
public class MyContextListener implements ServletContextListener{
#Override
public void contextDestroyed(ServletContextEvent arg0) {}
#Override
public void contextInitialized(ServletContextEvent arg0) {
Counter.getInstance().incrementAndGet();
}
}
Test unit:
public void mainTest() throws ServletException, LifecycleException{
Tomcat tomcat = new Tomcat();
tomcat.setPort(50000);
StandardContext ctx = (StandardContext) tomcat.addWebapp("/fe", System.getProperty("FEBaseDir")); //The FEBaseDir property is supposed to be taken from Maven build using 'test' profile
tomcat.start();
Counter.getInstance().getValue();
}
when I used Tomcat 7, everything worked fine. but since I upgraded tomcat to tomcat 8.0.33, It hasn't been working. the singleton class with the static data loads twice. first by the tomcat and then by the Junit itself.
I have tried to pass tomcat a classloader but it doesn't work.
public void mainTest() throws ServletException, LifecycleException{
Tomcat tomcat = new Tomcat();
tomcat.setPort(50000);
StandardContext ctx = (StandardContext) tomcat.addWebapp("/fe", System.getProperty("FEBaseDir")); //The FEBaseDir property is supposed to be taken from Maven build using 'test' profile
ctx.setCrossContext(true);
ctx.setLoader((Loader) new WebappLoader(Thread.currentThread().getContextClassLoader()));
ctx.setParentClassLoader(Thread.currentThread().getContextClassLoader());
tomcat.getEngine().setParentClassLoader(Thread.currentThread().getContextClassLoader());
tomcat.getHost().setParentClassLoader(Thread.currentThread().getContextClassLoader());
tomcat.getService().setParentClassLoader(Thread.currentThread().getContextClassLoader());
tomcat.getServer().setParentClassLoader(Thread.currentThread().getContextClassLoader());
tomcat.start();
Counter.getInstance().getValue();
}
What am I doing wrong?

You could try using the setDelegate method in StandardContext to prevent the web-app classloader from reloading the Counter class, but this impacts security in a bad manner so I advice against that.
The usual way to expose statistics is to use JMX (MBeans). You enable this by calling the setUseNaming method in StandardContext with value true.
You can register a mbean like this (copied from here):
MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
ObjectName beanPoolName = new ObjectName("com.zaxxer.hikari:type=Pool (" + poolName + ")");
mBeanServer.registerMBean(hikariPool, beanPoolName);
And you can retrieve a value like this (copied from here):
MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
ObjectName poolName = new ObjectName("com.zaxxer.hikari:type=Pool (foo)");
HikariPoolMXBean poolProxy = JMX.newMXBeanProxy(mBeanServer, poolName, HikariPoolMXBean.class);
int idleConnections = poolProxy.getIdleConnections();
See also this SO question and you'll probably have to read some more documentation (in my experience, it takes a while to understand the whole JMX thing and get it to work). I have not tried this in combination with unit-tests though, so YMMV.

Related

SpringBoot Tomcat Embedded Global JNDI Resource

I know there are a lot of questions about JNDI Resources in tomcat embedded, but I tried all the solutions I found without success.
I have an application that expose Rest API for my clients. Inside this app, we have a async solution using JMS and Amazon SQS. The App uses third-part libs, that uses JNDI to get sql.Datasource, because of that, I need use JNDI Datasource.
The problem is, when the App does a call to this libs in the same thread of Rest Controller, the JNDI Lookup works, and the datasource is getted.
When my #JmsListener calls this libs, I get a NamingNotFoungException.
I've used context.list("java") in 2 points of my code and confirmed that, inside JmsListener, there is no JNDI Context.
My tomcat factory class:
Configuration
public class CustomTomcatEmbeddedServletContainerFactory {
#Value("${spring.log.datasource.jndiName}")
private String logJndiName;
#Value("${spring.log.datasource.password}")
private String logPassword;
#Value("${spring.log.datasource.url}")
private String logUrl;
#Value("${spring.log.datasource.username}")
private String logUsername;
#Bean
public TomcatEmbeddedServletContainerFactory tomcatFactory() {
return new TomcatEmbeddedServletContainerFactory() {
#Override
protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(Tomcat tomcat) {
tomcat.enableNaming();
return super.getTomcatEmbeddedServletContainer(tomcat);
}
#Override
protected void postProcessContext(Context context) {
// LogDS
context.getNamingResources()
.addResource(
getContextResource(logJndiName, logUrl, logUsername, logPassword)
);
ContextResourceLink contextResourceLink = new
ContextResourceLink();
contextResourceLink.setGlobal(logJndiName);
contextResourceLink.setName(logJndiName);
contextResourceLink.setType("javax.sql.DataSource");
context.getNamingResources().addResourceLink(contextResourceLink);
}
private ContextResource getContextResource(
final String name
, final String url
, final String username
, final String password
) {
ContextResource resource = new ContextResource();
resource.setName(name);
resource.setType(DataSource.class.getName());
resource.setProperty("factory", "com.zaxxer.hikari.HikariJNDIFactory");
resource.setProperty("jdbcUrl", url);
resource.setProperty("dataSource.user", username);
resource.setProperty("dataSource.password", AESCrypto.decrypt(password));
resource.setScope("Sharable");
return resource;
}
};
}
}
Any idea of this problem?
-------Update---------
When I use the code below, the context in JMSListener solve but my RestController doesn't answer anymore, a 404 http status happen.
protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(Tomcat tomcat) {
tomcat.enableNaming();
TomcatEmbeddedServletContainer container = super.getTomcatEmbeddedServletContainer(tomcat);
for (Container child : container.getTomcat().getHost().findChildren()) {
if (child instanceof Context) {
ClassLoader contextClassLoader = ((Context) child).getLoader().getClassLoader();
Thread.currentThread().setContextClassLoader(contextClassLoader);
break;
}
}
return container;
}
-------Update2---------
My problem is fixed. Instead of returning "container", like I said above, I was returning super.getTomcatEmbeddedServletContainer(tomcat); The manual GlobalContext in my first update works well!
My problem is fixed. Instead of returning "container", like I said above, I was returning super.getTomcatEmbeddedServletContainer(tomcat); The manual GlobalContext in my first update works well!

Tomcat Multithreaded deployment using ManagerServlet

I have 30 WARs in tomcat and there is a dependency between them. So we have a servlet to deploy them sequentially. Now I want to deploy the required apps first sequentially and then rest of them in parallel.
My code is something like below.
public class MyDeployerServlet extends ManagerServlet {
...
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
...
if(count < serialContexts){
super.deploy(writer, context, contextName, null, false, sm);
count++;
} else {
MyAsyncDeployer deployer = new MyAsyncDeployer(writer, context, contextName, null, false, sm);
Thread deployerThread = new Thread(deployer);
deployerThread.start();
}
}
MyAsyncDeployer runnable code is:
public class MyAsyncDeployer extends MyDeployerServlet implements Runnable{
private PrintWriter writer;
private String config;
private ContextName context;
private String war;
private boolean update;
private StringManager sm;
public MyAsyncDeployer(PrintWriter writer, String config, ContextName context, String war, boolean update,
StringManager sm) {
this.writer = writer;
this.config = config;
this.context = context;
this.war = war;
this.update = update;
this.sm = sm;
}
public void run() {
super.deploy(writer, config, context, null, false, sm);
}
When I call this, serial deployment goes fine but the multithreaded deployments throw below exception.
Exception in thread "Thread-9" java.lang.NullPointerException
at javax.servlet.GenericServlet.getServletContext(GenericServlet.java:123)
at javax.servlet.GenericServlet.log(GenericServlet.java:188)
at org.apache.catalina.manager.ManagerServlet.deploy(ManagerServlet.java:834)
at com.example.servlet.MyAsyncDeployer.run(MyAsyncDeployer.java:30)
at java.lang.Thread.run(Thread.java:745)
Exception in thread "Thread-10" java.lang.NullPointerException
at javax.servlet.GenericServlet.getServletContext(GenericServlet.java:123)
at javax.servlet.GenericServlet.log(GenericServlet.java:188)
at org.apache.catalina.manager.ManagerServlet.deploy(ManagerServlet.java:834)
at com.example.servlet.MyAsyncDeployer.run(MyAsyncDeployer.java:30)
at java.lang.Thread.run(Thread.java:745)
I am clueless what is missing here, I am using the same object references in my thread. If this is possible at all to deploy in multithreaded way?
The problem here is that you forgot to initialize your MyAsyncDeployer servlet.
What you need is to call MyAsyncDeployer#init(ServletConfig config) method right after construction (before starting a thread).
For serial case it works because Tomcat initialized your servlet (MyDeployerServlet) itself before deploying as javadoc for init states:
Called by the servlet container to indicate to a servlet that the servlet
is being placed into service. See Servlet#init.
This implementation stores the ServletConfig object it receives
from the servlet container for later use. When overriding this form of
the method, call super.init(config).
But as long as you don't need to deploy you async servlet into container and only need to use its deploy ability, no one is instantiating it for you.
Fixed version of your code:
if(count < serialContexts){
super.deploy(writer, context, contextName, null, false, sm);
count++;
} else {
MyAsyncDeployer deployer = new MyAsyncDeployer(writer, context, contextName, null, false, sm);
delpoyer.setWrapper(getWrapper());
deployer.init(getServletConfig());
Thread deployerThread = new Thread(deployer);
deployerThread.start();
}
}
UPD:
Note, that you can't avoid creating servlet (aka having this.deploy call in Runnable#run) because thread-safety of ManagerServlet#deploy method is guaranteed via total synchronization (the whole method is synchronized), so in fact such approach will be serial as well.

How to call EJB 3.0 by external Servlet

I am learning EJB 3 and trying some simple stuff on it.
I am using eclipse -> Luna Service Release 1 (4.4.1) with JBOSS AS 7.1.1 (final)
I created a simple EJB project in eclipse with name -> SalutationEJBProj. Under this project created a simple session, stateless Bean.
code:
package com.vipin.bean.session.stateless;
import javax.ejb.Stateless;
#Stateless(mappedName="SalutationBean")
public class SalutationBean implements SalutationBeanRemote {
public SalutationBean() {
}
#Override
public String getFormalSalutation(String name) {
return "Dear" + name;
}
#Override
public String getInfomalSalutation(String name) {
return "Hi" + name;
}
}
The Remote interface is:
package com.vipin.bean.session.stateless;
import javax.ejb.Remote;
#Remote
public interface SalutationBeanRemote {
public String getFormalSalutation(String name);
public String getInfomalSalutation(String name);
}
I am able to deploy it in Jboss AS 7.1.1 and here is the JNDI bindings output when i start the jboss:
java:global/SalutationEJBProj/SalutationBean!com.vipin.bean.session.stateless.SalutationBeanRemote
java:app/SalutationEJBProj/SalutationBean!com.vipin.bean.session.stateless.SalutationBeanRemote
java:module/SalutationBean!com.vipin.bean.session.stateless.SalutationBeanRemot
java:jboss/exported/SalutationEJBProj/SalutationBean!com.vipin.bean.session.stateless.SalutationBeanRemote
java:global/SalutationEJBProj/SalutationBean
java:app/SalutationEJBProj/SalutationBean
java:module/SalutationBean
Now, i want this ejb to be accessible by an external Servlet, for this I created a dynamic web project with name --> SalutationServletProj
Here is the code of the simple Servlet:
package com.vipin.servlet;
#WebServlet(urlPatterns = { "/SalutationServlet" })
public class SalutationServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
#EJB
private SalutationBean salutationBean; // <-- How will this be accessible, this is in a different project, how do we import this?
public SalutationServlet() {
super();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}
My intention is to use #EJB annotation (or any other method) to get the instance of the EJB in the Servlet.
As these two are in different projects, how can i achieve this? I can't use like this in servlet:
import com.vipin.bean.session.stateless.SalutationBean
1) What are the files from EJB project that i have to copy into servlet project.
2) Will it work as #EJB annotation or do i need to do JNDI lookup? (Because the servlet is remote).
3) Where can i find the classes/libraries generated in jboss?
Any help highly appreciated.
it should be sufficient to only add the SalutationBeanRemote into WEB-INF/classes of your web project.
Then it should work out if you add the following member to your Servlet:
#EJB(lookup="java:global/SalutationEJBProj/SalutationBean")
private SalutationBeanRemote salutationBean;
Only the java:global... names will work because your servlet is running in another module and application as the EJB.
I am unsure if the java:jboss/exported... lookup name works or if it is only for remote clients.
Best regards,
Robert

How to deliver Realm with the war with Tomcat?

we have a simple web application running on Tomcat 7.0.56. Now we want to use our
own realm for authentication.
public class SpecialAuth extends DataSourceRealm{
#Override
public Principal authenticate(String username, String credentials){
....
}
}
This is defined in the /META-INF/context.xml inside the war
<Context>
<Realm className="some.package.SpecialAuth" dataSourceName="jdbc/MySQL" />
</Context>
Where to put the SpecialAuth.class?
What we expected was simply to have the SpecialAuth.class inside our war but then we're getting folling exception on startup
Caused by: java.lang.ClassNotFoundException: some.package.BackOfficeAuth
at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
....
If we make a jar, putting it into $TOMCAT/lib everything works fine.
But this CAN'T be the solution! That would mean every time I work on this class(es) I have to touch my tomcat server and can't use the normal deployment.
How can I use the build-in authentication mechanism without touching the tomcat all the timeß
As you said I don't like your answers :) So what I did (and I'm 100% sure that you don't like it) was to set the realm on the dirties possible way BUT now I can run it on a ontouched tomcat. After 163 acceptance tests nothing seems to break.
public final class ContextListener implements ServletContextListener {
#Override
public void contextInitialized(ServletContextEvent event) {
ServletContext servletContext = event.getServletContext();
TomcatContextManipulator tomcat = new TomcatContextManipulator(servletContext);
tomcat.applyRealm(new MyOwnRealm());
}
}
.
public class TomcatContextManipulator {
private static final String EXPEXCTED_TOMCAT_VERSION = "7.0.52.0";
private ApplicationContextFacade servletContext;
/**
* #param servletContext must be of type {#link ApplicationContextFacade}
*/
public TomcatContextManipulator(ServletContext servletContext) {
checkTomcatVersion();
ensureEquals(servletContext.getClass(), ApplicationContextFacade.class, "class of servletContext");
this.servletContext = (ApplicationContextFacade) servletContext;
}
/**
* checks if the correct version of tomcat is in use, throws {#link IllegalStateException} if not
*/
private void checkTomcatVersion() {
// we use several internal parts of tomcat (for example with reflection)
// by doing this we bind ourself hardly to a explicit version
ensureEquals(EXPEXCTED_TOMCAT_VERSION, ServerInfo.getServerNumber(), "Tomcat-Server-Version");
}
/**
* overrides the existing realm with the given on
*/
public void applyRealm(Realm realm) {
ensureNotNull(realm, "realm");
ApplicationContext applicationContext = (ApplicationContext) ReflectionUtil.get(servletContext, "context");
StandardContext standardContext = (StandardContext) ReflectionUtil.get(applicationContext, "context");
standardContext.setRealm(realm);
}
}
Note:
Reflection.get() returns the value of the (private) instance variable of the given object
ensure...() is like assert... but it throws Exception

Getting server context path on application startup

I am trying to get the server URL (eg. http://www.mywebapp.com/myapp) from the ServletContext when the application starts up, I am doing this by invoking a bean method on startup (using #Startup) and getting the servlet context,
#Startup
#Name("startupActions")
#Scope(ScopeType.APPLICATION)
public class StartupActionsBean implements StartupActions,
Serializable {
#Logger private Log log;
/**
*
*/
private static final long serialVersionUID = 1L;
#Create
#Override
public void create(){
ServletContext sc = org.jboss.seam.contexts.ServletLifecycle.getServletContext();
String context = sc.getContextPath();
String serverInfo = sc.getServerInfo();
log.debug("__________________START__________________");
log.debug("Context Path: "+context);
log.debug("Server Info: "+serverInfo);
}
// Cleanup methods
#Remove
#BypassInterceptors
#Override
public void cleanUp(){}
}
This work ok, however the ServletContext path is blank, see console output below..
18:52:54,165 DEBUG [uk.co.app.actions.startup.StartupActionsBean] __________________START__________________
18:52:54,165 DEBUG [uk.co.app.actions.startup.StartupActionsBean] Context Path:
18:52:54,165 DEBUG [uk.co.app.actions.startup.StartupActionsBean] Server Info: JBoss Web/3.0.0-CR1
Does anyone know how to get the contextpath through this, or other means?
ps. using SEAM 2.2.2, Jboss AS6 Final, Richfaces 3.3.3
Don't use #Startup, your component startup code gets called before contexts are fully setup and some other factories could not be yet initialized.
Observe the org.jboss.seam.postInitialization event and use the same ServletLifecycle.getCurrentServletContext() to get hold of the data needed. A quick example:
#Name("contextPath")
public class ContextPathInit implements Serializable {
private String contextPath;
#Observer("org.jboss.seam.postInitialization")
public void init() {
contextPath = ServletLifecycle.getCurrentServletContext().getContextPath();
}
}
Have you tried getting it from ExternalContext using the getContextName method?
FacesContext.getCurrentInstance().getExternalContext().getContextName()

Categories