I have a web application that uses a library which resides in TOMCAT_HOME/common/lib. This library looks for a properties file at the root of the classpath (in a class called ApplicationConfig):
ApplicationConfig.class.getResourceAsStream("/hv-application.properties");
My Tomcat web application contains this properties file. It is in WEB-INF/classes, which is the root of the classpath right? However, at runtime, when it tries to load the properties file, it throws an exception because it can't find it (getResourceAsStream returns null).
Everything works fine if my application is a simple, standalone Java application. Does Tomcat cause the getResourceAsStream method to act differently? I know there's a lot of similar questions out there, but none of them have helped unfortunately. Thanks.
Try Thread.currentThread().getContextClassLoader().getResourceAsStream("/hv-application.properties") instead.
This looks like it might be related to how Tomcat classloaders work. If you have something in one class loader (your config file in the webapp classloader) that's used by something in another (the jar in common/lib), the result could be a big headache.
This document explains how Tomcat delegates to class loaders. If possible, could you try one of the following:
Move the jar file in common/lib into your web application (WEB-INF/lib). This is not always possible I know, but sometimes jars (e.g. log4j) can coexist peacefully across classloaders (*).
Move your configuration file into common/classes. This is effectively the same thing (puts the configuration item into the same classloader as the jar that needs it). Again, this is not ideal, but if you have control over your environment, it would work.
Either way, having resources in different classloaders can be a pain. I hope this helps.
(*) log4j has the -log4j.ignoreTCL option which makes this possible though
The Tomcat security manager generally won't let you access webapp classes and resources from libraries in the Tomcat root libraries. This is meant to give separation between the web applications running in a container.
You should be able to get around this by updating the security policy, but it's generally better to not put your libs into the Tomcat container, which I assume you are doing.
I'm expanding Olivier comment as a response (thank you for the lead).
The problem seems to be the leading slash (/) in the resource path.
In Tomcat 8 the WebAppClassloader correctly resolves the path with and without the leading slash. Both .getResourceAsStream("/org/pakopa/app/config.properties"); and .getResourceAsStream("org/pakopa/app/config.properties"); returns an InputStream.
In Tomcat 7 (And I assume previous versions too) .getResourceAsStream("/org/pakopa/app/config.properties"); is not resolved and returns null but .getResourceAsStream("org/pakopa/app/config.properties"); is correctly resolved.
Related
Let's say we have two web applications and a Tomcat instance loading shared JARs (depedencies for both applications) from an external directory (via the means of shared.loader defined in catalina.properties). Therefore, these dependencies are not packaged into the WAR files.
Let's also say that:
Both web applications depend on a particular shared JAR file, which uses a logging framework (log4j2 at the moment but that's not required).
Both web applications use a logging framework of their own (we don't care whether they are identical or not, as long as things work as expected), and different logging configurations.
What we would like to achieve is for the shared JAR to reliably log to the same file, regardless of which web application its methods are called. To our understanding, both web applications have different logging contexts and having two such contexts log to the same file is either not possible or at least dangerous. If that's not true or doesn't have to be true, please elaborate.
The question: is it possible to achieve the above scenario with a single logging context? If so, could you please provide an example to make it working (the crucial bits will perfectly suffice), using lo4j2 or logback? Are there any catches?
Please note that we would like to avoid setting up a special servlet in one of the web applications for this (so the other web application would call it instead of logging directly to a file). Using (e.g.) syslog instead might be a solution perhaps but still, let's keep this question focused on the described scenario please.
After some research, trial and error, we managed to satisfy our requirements:
Each web application logs to its own files.
The shared JAR always logs to its own file too.
At least with Log4j2, the problem appears to revolve around class loaders. In our case, classes from the shared JAR file, and all of its dependencies, were always loaded using a class loader dedicated to (shared.loader). This means that Log4j2's JAR files need to be there beside the shared JAR file and if we tried to remove them, we would see ClassNotFoundExceptions.
Now, we can move to the web applications:
If a web application DOESN'T package Log4j2 for its own use, its loggers are also loaded using the class loader for shared.loader (as a fallback), and the web application's logging configuration overwrites the configuration applied previously (in our case, we had to call explicit initialization or reconfiguration, so that's why). It wouldn't work as expected.
If a web application DOES package Log4j2 for its own use, its loggers are loaded using the class loader for the web application (since dependencies packaged within WEB-INF take precedence), which is a completely different 'context' (class loader) than in approach #1, and the web application's logging configuration does NOT overwrite previously applied configurations (since the shared JAR and all web applications have their own 'context').
That is the behaviour we observed. During approach #1, each web application overwrote logging configuration for the applications initialized/started previously, because the Log4j2 'context' (class loader) was shared. During approach #2, the shared JAR's Log4j2 context was initialized by the web application that called its initialization (only one of them), and the configuration was not touched ever since. In our case, the configuration file was provided by the web application (it was not packaged within the shared JAR). Note that in practice, one of the web applications will always have to initialize the shared JAR, either implicitly or explicitly.
For absolute certainty, we listed open file descriptors to the shared JAR's log files and with approach #2, there was indeed only one. With approach #2, there can still be multiple descriptors open if some of the web applications' configuration files also reference the shared JAR's log files (configuration duplicity). That is precisely the sort of situation you'd normally want to avoid.
Pitfalls that we discovered:
Notice that the shared JAR's Log4j2 context was initialized by the web application that called its initialization (only one of them) remark is a bit tedious. Unless we copy or include the shared JAR's configuration within configuration of each web application (and for the above stated reasons, we want to avoid that), some of the logging messages may end up elsewhere or get lost during servlet container's start. In general, it may not be possible to guarantee the order in which web applications are started. I may be wrong but from what I saw in Tomcat logs, Tomcat starts the web applications (WAR files) in alphabetic order.
If the shared JAR and any of the web applications happen to share some more dependencies (beside the logging framework), these dependencies must, also, be placed within shared.loader. But, if the shared JAR's logging configuration redirects logging messages of any of those dependencies into its own logs, they can not end up in the web application's logs, unless that dependency is also packaged within the web application (same principle as approach #2 above). But in practice, separation of logging API and backing implementations makes this difficult. For example, you can read here on StackOverflow that selection of SLF4J bindings is rather "random" (JVM-dependent). If you put different bindings to shared.loader and into one of the web application's WEB-INF folder, you may not have certainly about which binding is going to get selected.
I certainly can not recommend the scenario we are trying to achieve but the described "solution" definitely works as expected (despite the pitfalls).
Inside web application I use some Spring scheduled-tasks (so managed standalone by Spring container). These tasks execute some business logic and require access to StringTemplate resources, which after deployment are located in WEB-INF/classes. I provide their directory as String (i.e. "some/templates") which works fine when working in exploded mode, but after switching to WAR-packaging, these resources cannot be found by ST. Project's page suggest using URL/URI (the "quagmire"), but that's a bit unclear to me. Other resources work properly, the only problem is the STGroupDir constructor.
How should I construct URL/URI/Paths arguments so that these resources could be accessed by WAR-packaged Spring-managed scheduled-tasks?
It appeared to be an issue with Weblogic 9.x creating an internal _wl_cls_gen.jar within the packaged WAR, containing all the classpath (i.e. WEB-INF/classes) resources. This results in problems with many frameworks that rely on getResource(path) methods (such as the ClassLoader one). Because of that StringTemplate could not access the group files. I fixed the problem by moving the templates out of the classpath and to WEB-INF location, then injecting the path as URL for StringTemplate to use.
I found some further information regarding this issue here and here.
A few days back, I ran into Jersey deployment issues which I posted here
Jersey Resource .class loading
We were able to use an alternative deployment mechanism by extending javax.ws.rs.core.Application and put in a temporary fix. But on researching more, I came across Jersey Scanners which can be implemented within our code. This can be made to lookup specific JARs within our project deploy structure. I was looking up the web, but could not find any specific examples of how the URISchemeScanner needs to be integrated within our code. (web.xml configuration, etc ...) Appreciate if I could be pointed in the right direction
from PackagesNamesScanner javadoc:
"Further schemes may be registered by registering an implementation of UriSchemeScanner in the META-INF/services file whose name is the the fully qualified class name of UriSchemeScanner."
See: http://jersey.java.net/nonav/apidocs/1.12/jersey/com/sun/jersey/core/spi/scanning/PackageNamesScanner.html
I am also having the problem with WAS 7. What we have done is remove the scanning Init Param itself from the web.xml and copy the Jar which contains resources in the Web-Inf Lib folder of the Web project. Then it will work. Adding J2EE dependency in the ear alone wont work.
I am using jersey 1.12 but the default scanner still have issue
Can you share the custom scanner modification you have done so that I can also try with that?
I have a question regarding Spring Web Flow with JSF: How can I teach Spring Web Flow to be able to load relative views like view="pages/view.xhtml" from a jar in the classpath of a tomcat webapp? After some research via google I think, that Web Flow does not support this constellation out of the box.
Maybe some context, to help understanding my question:
- Flows are registered in multiple FlowRegistries (I solved this problem by implementing a custom implementation, which finds all flowRegistries in the Spring Context)
- Flows can reside either as file resource outside the classpath or within a jar in the classpath, i.e. file ressource flows are located somewhere in WEB-INF/conf and they are at the same position within the jar files.
- Views in the flow definitions are adressed relatively to the flow-definition-file
Now you might ask the question why we have both constellations, where the flows can reside. At the moment we are trying to extract from a big bunch of a webapp modules that contain all functionality belonging to a certain domain. The approach is to bundle all artifacts relevant there within a single project that can be built as jar and added to the webapp then.
While it is no problem to load the Spring beans for each jar without knowing where our configuration files are located, the Web Flow causes some problems.
The first problem was, that the flowRegistry is a monolith that cannot be split without doing something before hand. This problem is solved by a custom flow registry.
But now I came to a second problem: Within view states we reference the pages relatively to the flow definition, like described in the documentation:
<view-state id="some-id" view="pages/somepage.xhtml"> ... </view-state>
Now, when I enter such a view state, web flow throws an exception, which tells me that this way is not supported:
A ContextResource is required to get relative view paths within this context;
the resource was ...
Googling around brought up this possible solution:
workaround for webflows in jars
But this workaround is not working as it has a problem with my multiple flow registries.
Another option might be to not put everything into the jar, but I am not sure if that is a better idea. Likely have everything that can be loaded from classpath in the jar and the rest as pure files in a defined structure.
Any ideas? Thank you very much for your efforts and hints.
I found a slight different solution by myself after several hours of trying and debugging my application on how to accomplish the goal of the question.
first thing to change was to advance from Tomcat 6 to Tomcat 7 because of a change in the servlet API spec, that enabled me to solve my problem with slight modifications
I switched from relative referencing in view states to absolute addressing
I changed the directory structure of my jar file to fit to the newer servlet API: all file resources needed for JSF or Spring Webflow needed to be placed in META-INF/resources (see Javadoc of ServletContext look for the method getResource, it specifies what I needed)
These three steps enabled me to completely pack webflows and their resources in jar-files.
I got two classes with the same package in different JARs. Until the previous version, both classes were identical, so i had no issues in loading them. Now, one of them has a new method added and if I want to access it, not only should I import the class with that package, i also need to make sure the jar with the correct class comes first in the classpath.
i.e. javac -classpath "%classpath%;a.jar;b.jar" MyClasses..
where a.jar has the class with my new method.
Now, how do i ensure this when my app goes to production, where it's deployed as an EAR file, with all the libraries under WEB-INF/lib?
How do I know which jar gets the preference over the other? Is it the alphabetical order like a.jar is given the first preference over b.jar?
I've read this safe-class-imports-from-jar-files thread and got to know about writing a custom classloader, but is there a better simpler solution that? Cos I'm just going to access this method in that whole JAR in this current project and writing a classloader seems a bit overkill.
And please don't ask me "Why the hell same class with same package in different JARs?" It's absolutely out of my control and it'll take some time to get this corrected.
Environment details: IBM WAS 6.1 on their 1.5 Java.
Please ask me more questions, if I don't make much sense. Thanks in advance!
You can try to change the startup script of your server and specify the jar with the correct class in the bootclasspath by using java -Xbootclasspath .... Otherwise there is no guarantee which one of the 2 jars will load up first.
As far as I know, the order of jars being loaded from WEB-INF/lib is arbitrary - I asked a similar question about JBOSS and got the reply ( from RedHat ) that it depends on the order that java.io.File.listFiles() returns them in ( and that is not a guaranteed order ).
A custom classloader would be an option, but have you considered repackaging the jars - removing the duplicated classes?
Websphere allows you to specify the order in which classloaders of a particular application are inquired when searching for a class (the classloaders are hierarchically structured, from the topmost that loads JRE classes, down to classloader loading classes in your WAR).
During deployment of an app, you can specify if the order of inquiring the classloaders when searching for a class. There are two modes - Parent first (i.e. query the topmost classloader first) and parent last (query the app classloader first). This can be specified on both EAR and WAR level.
Packaging the duplicated jars to different locations in the app (e.g. one to EAR's classpath, the other to WAR's WEB-INF/lib) and setting the classloader orderING apropriately may solve your problem. However, if both your JARs have to be on the same level (e.g. WEB-INF/lib), then there's no way to specify which one will be used when loading the duplicated class.
The order of the JARs in one application is likely to be alphabetical but the order of applications might not. Additionally, it depends on how the server handles classloading, i.e. whether it replaces existing classes or skips the new ones.
Although you already stated that, I'd still like to give that advice: Having the same class in multiple JARs deployed in one application (which could happen with versioned jars, for example) is always a bad idea. Your better off to invest the time to fix that instead of trying to mess with class loading.
This might come out to be pretty vague but I do remember resolving this issue a long time back by messing around with the WAS admin console for that given application and rearranging the relevant JAR files using their web UI. Not sure if this is an acceptable step in your case but worth a try in case everything else fails.
assuming you have some control over the deployment, fix the classloading yourself. combine the problematic jars yourself by unzipping them in reverse loading order into the same directory and then re-zipping into a new jar. then deploy the app with the new combo jar. no duplicate classes, problem solved.
or, just delete the dupe classes from the jars before deploying.