Dynamically compile and include JSP(X)s outside Web Context Root - java

I have a web application heavily using jspx-files and jsp:include-includes which is deployed from the same jar to several hundred contexts in tomcat. Currently we can only modifying the layout of each individual instance by changing CSS or overwriting certain files in other file directories:
Let's say the webapp.jar contains a file named /css/forms.css then the webapp ensures that GETting http://mydomain/cust1/res/css/forms.css is mapped to that file. If the webapp is configured to look in the "override directory" /data/cust1/, then if there's a file named /data/cust1/css/forms.css this file is served instead from the one in the application archive. This is ensured by the webapp itself.
During the last years this has been very successful but recently I feel the pain of the restriction of the more or less static jspxs that cannot be "overridden" ;) Basically I'd like to be able to "override" some jspx files for each deployed context without compiling and deplyoing a custom webapp-jar for each context. (Having a custom something:includeJsp-tag wouldn't be a problem).
Basically the webapp should be able to provide an override to the jsp(x) compiler for individual jspx-files, e.g. take a look at the following example sutrcture:
<!-- webapp.jar:/jspx/view.jspx -->
<jsp:root ...>
<ns:customInclude src="inc/include.jspx" />
</jsp:root>
<!-- webapp.jar:/jspx/inc/include.jspx -->
<jsp:root ...>
Default-Markup from the webapp.jar
</jsp:root>
<!-- /data/cust1/jspx/inc/include.jspx -->
<jsp:root ...>
Custom markup for "cust1"
</jsp:root>
Now, when http://.../cust1/jspx/view.jspx is requested I want /data/cust1/jspx/inc/include.jspx to be compiled and executed by Tomcat.
Basically I know that actually everything necessary is already possible somewhere in Tomcat (compiling from a jspx to Java-Bytecode, including the file, ...), but I also know that Tomcat/Jasper adhere to the jsp-spec. I figured by looking at the code that this is not that easy...
So basically, does anyone know how to get a setup like this to work? Did perhaps someone already solve this? Or are there alternatives to my current approach?

I ended up implementing my own FileDirContext that is included via a <Resources>-Tag in context.xml. Actually I implemented two - an extension of org.apache.naming.resources.WARDirContext and an extension of org.apache.naming.resources.FileDirContext because I wanted to enable the same mechanisms during development with eclipse (which deploys to a file structure) and on our servers which deploy from non-exploded .war files.
Both of them can be configured via the context descriptor and can use several "virtual overlays". An overlay can either be a file system path or an external .war file (actually more like a .zip renamed to .war) or a .jar file packed into the deployed .war file.
The steps to a tuned down solution with one virtual overlay are:
Create a class MagicWARContext (magic is like the solution to every problem ;) extending from aforementioned WARDirContext.
In this class create a static inner class ProtectedMethodVisibleMaker extends FileDirContext. This allows to access certain protected methods of Tomcat's original implementation. Override doGetRealPath and doLookup.
In your own WAR context create getters and setters for a field private String overlay and a field private ProtectedMethodVisibleMaker overlayContext and instantiate a new and empty instance to this field.
In allocate() of your WAR context call overlayContext.setDocBase(overlay). Be sure to call overlayContext.release() from release() just to be sure.
Now you have to override 5 more methods of WARDirContext
doGetRealPath(String)
doLookup(String)
getAttributes(Name, String[])
list(String)
list(Name)
The resulting enumerations of both lists should of course contain entries of a virtual overlay as well as the original context. So I ended up writing my own NamingEnumeration that iterates over a list of delegate naming enumerations and ensures that each named entry is output only once.
Instantiate your Magic Context from the context descriptor:
<Resources
className="package.name.tomcatextensions.MagicWARContext"
overlay="/data/some/dir"/>
Pack your classes in a jar and copy this e.g. to tomcat/lib
Caveats
We're overriding Tomcat's internal classes. This naturally exposes this code to changes. So someone has to ensure that everything works as expected after version upgrades. Instead of some of the protected methods one can override the actual public ones, but some of them are final so you're basically out of luck.
Plus it is possible to expose non-magic subcontexts to other application layers via the lookup methods meaning they don't have that overlay anymore or don't contain the fallback resources of your deployed war. I ended up adding some warnings to the log when subcontexts are requested. During startup this happens two times, once for /WEB-INF/classes and once for /WEB-INF/lib.
In my usecase I prohibit adding virtual overlays that contain either on of those paths because I don't want to have foreign class files appearing in my web context, so this is not a problem. However some other client might be calling those methods and doing something unexpected with them. During my tests I did not find any problem, but there might occure strange effects with caching enabled or when enumerating resources and there will be problems when sub contexts are used... Of course one could create virtualized wrappers around a subcontext returned by a lookup, but I didn't do this yet as I hope this is unnecessary.
And of course this method enables you to override certain resources of the fallback webapp in un-exploding war deployments. If you just need to add external resources to the web context you can use Tomcat's aliases (see How do I add aliases to a Servlet Context in java?).
Use at your own risk and have fun.

Related

Access to META-INF/weblogic-application.xml

I need to refactor an existing jar to work under weblogic 11c and 12c. The code requires full details of the java role to LDAP group mapping, as found in an ears META-INF/weblogic-application.xm. I seek a good mechanism to retrieve this information.
Some specifics:
The jar is not an EJB jar, and as the interface can't change, and I have no control over how it will be used in applications (it is a adrop-in replacement for an other library).
This means I can't inject the EJB context.
This also means I have no connection to the JMX mbean tree available (which would need configuration info)
I want something that works for multiple applications, all deployed as ears.
I want something that survives fastswap and exploded directory layout
I want something as future proof as possible
It needs to work both when called from WAR files as from EJB jar files
It needs to run under WebLogic 11c,12c, and be as future proof as reasonably possible
For now, i use this strategy:
Use getClass().getResource(myself) to retrieve an URL to my code
Drop the jar: prefix and associated ! markers
Scan upwards trough the directory tree, until we find a folder containing META-INF/weblogic-application.xml
If found -> read and parse./ Otherwise -> complain and crash
This seems to work, but I hope to discover a less hackish approach, either by accessing the xml file, or by querying the managed server which executes the jar (but without means to configure connection parameters)

Is it possible to keep referenced files inside the service itself?

I'm working on a jax-ws service in Eclipse. At some point, this service opens and uses a couple of XSLT stylesheets.
My question is, can you somehow import and keep these 2 files in the project itself, as you can with a library? For convenience' sake. I basically want my service to work as is, without having to go through the trouble of shipping the xslts along with the service but having to place them in different locations on the server, having to explain to people how and where they must go etc..
On a related note, how come when I make new File("D:\x.xslt");, the service looks for it in "C:\Users\Tudor\Desktop\eclipseJ2EE\eclipse\D:\x.xslt"? As in, *eclipse_path*/*fileName*. I would have understood, if it looked for the file in the root of the apache tomcat server; but not the installDir of eclipse... Anyway, how do I change that behaviour?
You can store the xslt file within your source classpath and load it via the Classloader.
If you are using Spring you can also use the ResourceLoader to load resources.
Its rarely a good idea to use File instances with relative paths directly, since within different server environments the base directory often differs.
Hope this helps.

How does class loading work when the same class exists in different applications on the same server?

I have multiple web-apps running on an app server and each web-app WAR file contains a copy of the same jar file.
Does this mean that a class in that jar file will be loaded multiple times in the JVM, once for each WAR file it exists in? Following on from that, if I have a static synchronized method in such a class, is it only synchronized among threads within the web-app it exists in but not synchronized against the same method in the same class in a different jar file in a different WAR file? (Hope the question makes sense, will clarify if necessary).
If this is the case I presume the best solution is to remove the jar file from each WAR file and deploy it to a shared classpath folder on the server?
A Java classloader typically works by looking for classes in one or more places in a fixed sequence. For instance, the classloader that loads your application when you run it from the command line looks first in the rt.jar file (and others on the bootclasspath), and then in the directories and JAR files specified by your classpath.
A webapp classloading is similar in principle, but a bit more complicated in practice. For a particular webapp, a webapp's classloader looks for classes in the following order. For example Tomcat 6 looks for classes in this order:
Bootstrap classes of your JVM
System class loader classes (described here)
/WEB-INF/classes of the webapp
/WEB-INF/lib/*.jar of the webapp
$CATALINA_HOME/lib
$CATALINA_HOME/lib/*.jar
Of course, once the classloader has found the class it is looking for, it looks no further. So classes with the same name later in the order won't get loaded.
The complication is that the web container has one classloader for each webapp, and these classloaders delegate to other classloaders that manage the common classes. In practice, this means that some classes will only ever be loaded once for the entire container (e.g. 1. and 2.) and others may get loaded multiple times by different classloaders.
(When a class is loaded more than once, it results in distinct Class objects and distinct class statics. The versions of the class are different types as far as the JVM is concerned and you cannot typecast from one version to the other.)
Finally, Tomcat can be configure to allow individual webapps to be "hot loaded". This entails stopping a webapp, creating a new classloader for it, and restarting it.
FOLLOWUP
So ... synchronizing a static method will not protect access to a shared resource where the class has been loaded multiple times?
It depends on the details, but it probably won't. (Or to look at if another way, if a class has actually been loaded multiple times, then a static method of each "load" of the class will access a different set of static fields.)
If you really want a singleton application class instance to be shared by multiple webapps in the same container, it is simplest if you put the class into $CATALINA_HOME/lib or the equivalent. But you also should ask yourself if this is good system design. Consider combining the webapps, or to using request forwarding etc instead of a shared data structure. The singleton pattern tends to be troublesome in webapps, and this flavor is even more so.
Java EE application servers typically use multiple classloaders to isolate applications from each other, and allow new versions of one application to be deployed without affecting other apps.
You get patterns such as several WAR files and one EJB file in an EAR with a hierarchy of classloaders, each WAR having it's own.
This does lead to duplication as you describe, but this is not necesserily a bad thing. It means that you can even have different versions of the same JARs deployed a the same time, and that may actually be benficial, allowing incremental migration to new versions.
Some application servers (WebSphere for exmaple) have explicit support for a shared library concept, and I do use that.
Be wary of just popping JARs into arbitrary classpaths, you run the risk of destabilising the app server itself.
Most application server use most specific along a path takes precedence policy.
If you have multiple library that do the same thing, You should consider to put them inside application server lib (f.e: TOMCAT_HOME/lib)

java.lang.LinkageError on jBoss while running app with log4j custom layout class

I am trying to configure a custom layout class to Log4J as described in my previous post. The class uses java.util.regex.Matcher to identify potential credit card numbers in log messages. It works perfectly in unit tests, also in a minimal web app containing a single servlet. However when I try to deploy it with our app in JBoss, I get the following error:
--- MBEANS THAT ARE THE ROOT CAUSE OF THE PROBLEM ---
ObjectName: jboss.web.deployment:war=MyWebApp-2010_02-SNAPSHOT.war,id=476602902
State: FAILED
Reason: java.lang.LinkageError: java/util/regex/Matcher
I couldn't even find any info on this form of the error - typically LinkageError seems to show up with a "loader constrain violation" message, like in here.
Technical details: we use JBoss 4.2, Java 5, Log4J 1.2.12. We deploy our app in an .ear, which contains (among others) the above mentioned .war file, and the custom layout class in a separate jar file (let's call it Commons). We override the default settings in jboss-log4j.xml with our own log4j.properties located in a different folder, which is added to the classpath at startup, and is provided via Sapient's Carbon framework.
Update to #skaffman's answer:
The reason we have a separate log4j.properties file is the scheme propagated by Sapient Carbon. This basically decouples the configuration and data files from the application server environment, so that they are accessible via Carbon's lookup functionality and they can be stored in a directory external to the app server. We inherited this setup, and we hate it because it causes us lots of trouble with deployment, classpath issues etc. since it does not adhere to the JEE conventions. We aim to get rid of it in the long run, but it's gonna take time :-(
Even though the separate log4j.properties file is not best practice, it certainly works. It has been functioning in our app for years, and I could also make it work with a minimalist web app containing a single servlet (not using Sapient Carbon). If log4j.properties is put into the classpath, Log4J reads it properly when the web app is launched, and reconfigures logging accordingly.
Update#2: An interesting finding is that Matcher is not even used in MyWebApp, only in the Commons module (and another module, in a separate jar). In Commons, it has been used before, in a class called StringHelper, which is used indirectly by MyWebApp, via other modules.
I guess this rules out the possibility of two different Matcher class versions loaded by different classloaders. So my only remaining guess is that Matcher is loaded by two different classloaders when it is used from the jar and the war, and then attempted to pass from one to the other. This is explained by Frank Kieviet's excellent article. However, I believe that such a setup would cause a "loader constraint violation" rather than this form of the error.
Update#3: If I add this appender (example 3.8) to jboss-log4j.xml, the error disappears, and the server runs perfectly :-o This obviously has to do something with loading log4j.jar, because this setup requires the jar to be present in the server lib directory. It works also if I change the appender type to org.jboss.logging.appender.FileAppender, and set log level to WARN, which results in an empty ucl.log file. This may suit as a temporary workaround, but I am still eager to fully understand what's going on here.
What does this error message mean, and how can I fix it properly?
Epilogue
After a long wait, I finally got to eliminate Carbon from the logging process and migrate our logging config into server/conf/jboss-log4j.xml. This required that I publish our custom log filter class in a separate jar in the server/lib directory. After this, the class loading works again, without the workaround described in Update#3 above :-)
My first reaction is that in JBoss it's not possible to override the log4j configuration like that. JBoss isn't allowing log4j to locate its own configuration, as it normally would, the location of conf/jboss-log4j.xml is specified in conf/jboss-service.xml.
To my knowledge, all log4j configuration in a given JBoss server must be centralised in to a single file, usually conf/jboss-log4j.xml.
Have you tried, as a test, moving the contents of your log4j.properties into the existing conf/jboss-log4j.xml file? If that works fine, then the problem is almost certainly caused by your attempt to override log4j. Having said that, I'd be surprised if jboss/log4j is that fragile, but perhaps in certain cases, it rejects this.
you either have two classes of different signatures but the same path in your environment or you compiled against another signature of j.u.r.Matcher. Since this is standard Java API, I think you should check your source and compilation targets and the JVM runtime version of your JBoss installation.
Edit:
After that is ruled out, I'm sure, the classloader (the server's) that manages the appenders and tries to load the appender that's using your custom layout can't see the custom layout class instance. So you have two options:
Deploy your custom layout JAR to the server's lib-directory along with log4j.
Deploy log4j along with your application and isolate the application with your own classloader (jboss-app.xml):
<jboss-app>
<loader-repository>
com.myapplication:loader=MyClassLoader
<loader-repository-config>java2ParentDelegation=false</loader-repository-config
</loader-repository>
</jboss-app>
I hope, the problem will go away then.

Tomcat parent webapp shared by configurable children webapps

Currently, we support many clients using the same web app, but each client has a different configuration for accessing their database, setting files etc. As the client list grows, updating the web apps is becoming increasingly arduous, and the duplication of resources is a waste of memory, file space, etc..
What we'd like to do is have a parent web app which is shared by all children web apps. Then have each child web app carry only files specific to them. When the child web app starts up, Tomcat loads the web app from the parent web app and then overrides any files defined in the child web app following an identical package structure.
We've been googling around and haven't found a ready or complete solution. Solutions we've looked at:
Tomcat common/share - could handle class and JAR files, but we don't see a way to handle static and JSP resources residing above the WEB-INF dir.
CATALINA_BASE appears to be more suited for running multiple instances of Tomcat which we'd rather avoid
A Maven possible solution, but we are not big fans of Maven, so would rather avoid it also.
Anybody have suggestions or ideas on how to solve this? If Tomcat configuration is not possible, what about a different application server (such as Glassfish) or a tool for doing dynamic file updated (such as OSGi, rsync). Would like to remove the resource duplication if possible.
Thank you.
There is no such thing as "parent" or "child" webapps. It's not part of J2EE spec and AFAIK it's not supported by any application server.
That said, your problem is twofold:
1) Having shared resources. This part is pretty easy assuming "resources" means static resources (images / CSS / javascript / etc...).
If they are truly shared (e.g. you don't need to have a separate version in some of your webapps), host them elsewhere (separate "common" webapp or put Apache in front of your Tomcat and host them there.
If you do need to have "local" versions of some of those resources you may be able to do some clever conditional URL rewriting or simply write a servlet that would check whether particular resource exists locally and, if not, take it from "common" location.
Precompile your JSPs so you only have to deal with JARs.
If your Tomcat instance only hosts your apps, you can indeed put your JARs in shared (or lib in the latest version); otherwise you can deploy them with each application .
2) Simplifying deployment. I'm not really sure what the big problem is here... It's rather trivial to write an Ant (batch, shell, what have you) script that would assemble and deploy WARs based on "common" and "per-app" directory structures.
Alternatively, you may want to take a look at using JNDI to severely reduce the number of files that have to be deployed (in theory, to a single context.xml for each application).
You can build parent-child hierarchy if you use Spring at your web-apps - Using a shared parent application context in a multi-war Spring application.
I.e. you can define all shared stuff at the 'parent' context and have 'child' contexts just to use it.
If all you had was setting file and configuration changes you could manage these through the context.xml and then you can point the docBase of each application context at a common directory for all the applications to share the same source.
the drawback to this is changes to the application will require a tomcat restart.
This does not however solve your problem if you want to override logic.
A option that I am exploring for a similar scenario is to move the client custom portion into ajax widgets / gadgets. Then have it be part of the configuration files to tell the application which version of the gadget to pull for which client.
you can review documentation for having applications share a docbase here http://tomcat.apache.org/tomcat-5.5-doc/config/context.html

Categories

Resources