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.
Related
This is the code:
public class ContextManagerImpl implements ContextManager {
private static final ThreadLocal<Context> ctx = new ThreadLocal<Context>();
#Override
public Context getContext() {
if(ctx.get() == null) {
ctx.set(new Context("", "")); // Dummy context. This should never happen
}
return ctx.get();
}
#Override
public void begin(Context context){
ctx.set(context); // Verified context passed is never null or blank
}
#Override
public void end() {
if(ctx!= null) {
ctx.remove();
}
}
}
public final class Context implements Serializable {
private String s1;
private String s2;
}
There are 2 threads which are using this class with different threadlocal context. It works fine most of the time, however sometimes even when begin() sets the value in ctx properly, getContext() returns dummy context.
I suspect there is a race condition somewhere which is causing this but given that threadlocal set() and get() are threadsafe and initialisation of ctx is done during declaration this should never happen.
Note: I have upgraded spring boot to 3.4.3 but still using JDK 8.
ThreadLocal get() returning null sometimes after spring boot upgrade from 2.1.4 to 3.4.3
If Spring guarantees that begin(...) is called before getContext(), then it sounds to me like a one thread is doing the call to begin(...) method and another thread is calling getContext(). If this is the case the getContext() will return null.
ThreadLocal is designed to provide a different context for each thread. Is that what you want? I don't fully understand your wiring but I'm suspicious of your use of ThreadLocal. What are the spring boot guarantees around threads?
I suspect that you should instead just make it a volatile field:
private static volatile Context ctx; // maybe set to new Context("", "");
// this should be called by spring boot
#Override
public Context begin(Context ctx) {
this.ctx = ctx;
}
public Context getContext() {
return ctx;
}
If you are worried about some race conditions if multiple instances of your ContextManagerImpl are being initialized with the same context then you could use an AtomicReference instead:
// may want to initialize with a dummy context
private static AtomicReference <Context> ctxRef = new AtomicReference();
// this should be called by spring boot
#Override
public Context begin(Context ctx) {
ctxRef.compareAndSet(null, ctx);
}
public Context getContext() {
return ctxRef.get();
}
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!
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.
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
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()