Maven - deploying large war files - java

This question is somewhat similar to this one Best way to deploy large *.war to tomcat so it's a good read first, but keep on reading my q, it's different at the end...
Using maven 2 my war files are awfully large (60M). I deploy them to a set of tomcat servers and just copying the files takes too long (it's about 1m per war).
On top of that I added an RPM layer that'll package the war in an RPM file (using maven's rpm plugin). When the RPM is executed on the target machine it'll cleanup, "install" the war (just copy it), stop and start the tomcat (that's how we do things here, no hot deploys) and set up a proper context file in place. This all works fine.
The problem, however, is that the RPM files are too large and slow to copy. What take almost eh entire space is naturally the war file.
I haven't seen any off-the-shelf solution so I'm thinking of implementing one myself so I'll describe it below and this description will hopefully help explain the problem domain. I'll be happy to hear your thought on the planned solution, and better yet point me at other existing solutions and random tips.
The war files contain:
Application jars
3rd party jars
resources (property files and other resources)
WEB-INF files such as JSPs, web.xml, struts.xml etc
Most of the space is taken by #2, the 3rd party jars.
The 3rd party jars are also installed on an internal nexus server we have in the company so I can take advantage of that.
You probably guessed that by now, so the plan is to create thin wars that'll include only the application jars (the ones authored by my company), resources and WEB-INF stuff and add smartness to the RPM install script that'll copy the 3rd party jars when needed.
RPM allows you to run arbitrary scripts before or after installation so the plan is to use mvn write a list of 3rd party dependencies when building the war and add it as a resource to the RPM and then when installing an RPM the RPM installation script will run over the list of required 3rd party jars and download the new jars from nexus only if they don't exist yet.
The RPM will have to delete jars if they are not used.
The RPM will also have to either rebuild the war for tomcat to explode it or add the 3rd party jars to common/lib or something like that although we have a few web-apps per tomcat so it'll make things complicated in that sense. Maybe explode the jar by itself and then copy the 3rd party jars to WEB-INF/lib
Your input is appreciated :)

We have a directory on the target machines with all third party jars we're using (about 110Mb). The jars are using a naming coding convention that includes their version number (asm-3.2.jar, asm-2.2.3.jar ...). When adding a new version of a third party we don't delete the older version.
When deploying, our jar files contains only business logic classes and resources we compile in the build (no third party). The classpath is defined in the jar manifest where we cherry pick which third party it should be using at runtime. We're doing that with ant, no maven involved and we have more then 25 types of services in our system (very "soa" though I dislike this over buzzed word). That business logic jar is the only jar in the jvm classpath when starting the process and it is also versioned by our code repo revision number. If you go back to older revision (rollback) of our code that might be using an older third party jar its still going to work as we don't remove old jars. New third party jars should be propagated to production machines before the business code that uses them does. But once they're there they're not going to be re-pushed on each deployment.
Overall we lean towards simplicity (i.e. not OSGi) and we don't use Maven.

I would advise against your proposed plan. It sounds like a lot of moving pieces that are likely hard to test and/or diagnose problem when they arise.
We don't have the problem of "large" WARs but we do have the problem that most of our WARs all need the exact same 3-rd party libraries on their classpath. The solution we went forth with (that has worked very well) was to utilize OSGi to build our application modularly. We use Felix as our OSGi container which runs inside of Tomcat. We then deploy all of our dependencies/libraries to Felix once. Then we deploy "thin" WARs which just reference OSGi dependencies by Importing the packages it needs from the bundles it cares about.
This has a few other advantages:
Deploying new versions of OSGi bundles while the old ones are running is not an issue which allows for no downtime (similar to hot deploy).
If you need to upgrade one of your dependencies (e.g. Spring 2.5 -> 3.0), you only need to upgrade the Spring bundle running in OSGi; no need to deliver (or package) new WARs if the APIs did not change. This can all (once again) be done on a live running OSGi container, no need to turn anything off.
OSGI guarantees your bundles do not share classpaths. This helps keep your code cleaner because each WAR only needs knowledge of what it cares about.
Setting up your WARs to be "OSGi ready" is not trivial but it is well documented. Try checking out How to get started with OSGi or just Google for 3rd party tutorials. Trust me, the initial investment will save you much time and many headaches in the future.
It is probably best not to re-invent the modularity wheel if possible.

Related

Exclude lib/ext From Jetty Classpath For Specific Web Apps

I'm currently in the process of creating a web service to be deployed onto an existing Jetty 9 server. I decided to use maven to handle the dependency management for this application and it is creating a bit of a problem. When I try to start the app, it is failing due to library conflicts. I figured out that the source of the problem is that the classpath is including both my maven dependencies and the jars in $JETTY_HOME/lib/ext/. I can't simply rename or delete lib/ext since there are existing applications on the server that depend on those libraries.
Is there a way to configure jetty to include or exclude lib/ext from the classpath on a per-application basis? If not, is there some other way I can work around this problem?
TL;DR - I don't think there is a simple answer.
According to the documentation, the jars in the lib/ext directory are added to the classpath by the "ext" module. So, you could in theory disable the "ext" module. But that will remove the jars from the classpath for all of the web services in the container. So that won't work ... except for testing purposes.
So that leaves you with a couple of more drastic solutions:
Set up a separate Jetty server to run the new webapp. (For example, using Docker containers.)
Analyze the other webapps to determine which of them use the JARs in the lib/ext directory, and rebuild their WARs so that (at least) the conflicting dependencies are in the WARs that need them rather than in lib/ext.
This is probably what you should do.
Go on a Jetty code hacking spree and modify the way that Jetty builds the "server" classpath. Anything is possible, if you are prepared to put in enough coding effort. But you will be creating "technical dept" for yourself by (in effect) creating a private fork of Jetty. So this is a really bad idea.

Is it possible to store all jars in Tomcat lib folder?

We have a few projects and we store libs in project  folders (each project has it’s own libs)  and we store shared libs in tomcat lib folder:
·Project1\lib
·Project2\lib
·…
·Tomcat\lib
Our manager proposed to combine all libs and put all jars in Tomcat lib folder.
Advantages of this approach are the following:
It will decrease compile time to .war file
It will decrease size of .war file
I feel that this decision is not the best one the but I can’t prove it. Is there any disadvantages of this approach?
The resultant .war application file is less portable. Additional installation steps since the webapp is no longer self-contained.
Or to argue against your boss' logic.
Compiling should be automated in continuous integration, so time spent is not real person time
Space is cheap
Don't consolidate. Consolidation will prevent different projects from using different versions of a particular 3rd party library. This is why different webapps are isolated from each other.
Some libraries also store things statically (sigh), which can cause cross-webapp pollution.

How do I incrementally patch a war file

I am having war file deployment at customer site. War file contains lib folder which contains dependent jars e.g.
/lib/app-01.jar
/lib/spring-2.5.1.jar
/lib/somefile-1.2.jar
...
...
If we need to update lets say app-01.jar to app-02.jar, is there any elegant solution? how does these dependent jars are packaged into WAR file as industry standard?
Is it good idea to package those dependent jars without version number?
e.g.
/lib/app.jar
/lib/spring.jar
/lib/somefile.jar
...
...
EDIT NOTE:
Actually, War is deployed to Webshpere, WebLogic, Tomcat on Windows or Linux platform. And Customer's IT department is involved for deployment
Probably the most elegant solution is just to generate a new war, and deploy it.
Here are the reasons:
If you are worried about uptime, some application servers supports side by side deployment. It means that you can deploy a new version and have it up at the same time of the old one. And stop the old when no one is using it. (I've used that on WebLogic, like 5yrs ago, so I suppose that is a common feature now). But that kind of feature only works if you deploy a new .WAR version.
Probably the WAR was generated using Maven, Ant or Gradle, so changing the dependency version and do a mvn package is usually faster and less error prone than unzipping the WAR, change it, and zip it again.
All the application servers provides a "hot replace" feature, that works by refreshing the class loader. Its fine for development, but in production it can be problematic (class loader leaks can be common, and problems caused by incorrect initialization or bad programming practices can give you unexpected bugs like having two versions of a class)
About JAR file names: I recommend to keep the versions on the file name.
Most of the JARs contains version information inside META-INF/Manifest.mf. But if for some reason you have to know which versions are using your app... opening each JAR file to see the version in the manifest is a lot of work.
As a final advice. If you don't use any automatic build tool... adopt one (take a look to Gradle, which is nice). Updating a library version, usually consist on changing the version number on the build file and execute something like gradle deploy. Even if you are not the developer, but the one in charge of devops, having an automated build is going to help you with deployments and updates.
In Tomcat I don't think the war is relevant after the files have been uncompressed. You could just ignore the war and extract the new/changed files into the correct webapp's directory (The one with the same name as the war.)

Application server-agnostic build script

I have a J2EE application that needs to be distributed to customers, who will have application servers of their own choice. What is the recommended way of writing a build script that would compile the J2EE application sources (at customer sites) so that they will eventually be able to deploy the application on their servers ?
Moreover, what JARs should one actually use during the compilation process ? Since this is meant to an application server agnostic script, is it recommended to use the JARs (say servlet.jar, jms.jar, ejb.jar etc.) from the application server, rather than having my customers download these JARs from a particular repository ? Is there any such repository that is recommended for such JARs (JARs of J2EE APIs) ?
PS: The EAR file cannot be built locally and shipped to customers, since additional development will be done at each installation to integrate with other systems, perform customizations on the application etc.
Build the application locally, and distribute the WAR/EAR files to the customer.
Edit:
The situation you describe is basically that you need to provide a code base for further development by the customer. In that case I would suggest that you look into creating maven modules of your work which are pushed to a private repository accessible by the customer. You then let them know that they need to depend on module X version Y in their pom.xml, and module X then pulls in all the libraries needed in the correct versions.
Note that if they don't use Maven, they might use Ant Ivy for the dependency resolving.
Some generic J2EE API jars can be found at Sun's site, however you might have to dig a lot to find them.
I would just use any JAR you have handy, bundle it with the source, but away from /WEB-INF/lib
Basically, don't include the container jars (servlet.jar, jms.jar, ejb.jar etc.) in your EAR or WAR file.
If you are just talking about servlet.jar, the web container provides that - you shouldn't include it in your war file.
If you are creating an EAR file, they you must be using an enterprise container and it will have a JMS.jar and EJB.jar.
JMS on Tomcat would be a little trickier - you'd need people to install something like activemq on their servers.

How to use common libraries for multiple Java web project

I am having four different project, and i am using Weblogic to deploy my projects. There are several libraries ( jar files ) which are common for all projects. Currently each of my project are having lib directory and have almost same set of libraries. Now, is it possible to have this lib directory outside WAR files and access them.
Resist the temptation of putting the jar files in the "shared" folder of your container. It is better to keep the jar files where they are now. It may sound a good idea to use a shared folder now, but in the future you may need to deploy an application that requires a shared library, but a different version.
That being said, I have no experience with WebLogic. In Tomcat there is a shared folder with libraries common for all deployed applications. It is not a good idea to use this. If WebLogic can be configured to use a shared folder per a set of applications (and not for all deployed applications) you could go for it.
Do you want to do this ? Unless you're stuck for deployment space, I would (perhaps) advise against it.
Why ? At the moment you have 4 solutions running off these libs. If you have to upgrade one of the libs (say, if you discover a bug, or if you require a new feature), then you're going to have to test compatibility and functionality for all 4 solutions. If each solution has its own set of libs, then they're sandboxed and you don't have to move all 4 in step.
Note that all this hinges on how easy it is to regression-test your solutions. You may find it easy, in which case using the same set of libs is feasible.
Don't do that.
The whole idea of WAR files is that they are self-contained units. This makes deployment so much easier.
In addition to the possible version conflicts that others have pointed out, putting jar files in /shared can have very nested consequences for class visibility. They will be on a separate classloader, and be unable to see the classes in the WAR file. If you use libraries that rely on Class.forName() to work (and there are plenty), this could get very painful.
If you really, really cannot afford the extra disk space and memory look at OSGi or Spring DM. They have solved this problem, but at the price of increased complexity.
Put all the shared jar files under common\lib folder of weblogic. common\lib is accessible by all the deployed apps.
Well first of all you can put your libs all on the same place and have your build process import the ones needed.
Has for on deploy the new Weblogic 10 has a lib folder in each domain where you can put shared libs. i dont think that is possible before Weblogic 10
You can put the jars in their own ear file and deploy it as a shared library.
You can also put the wars in an ear and add the shared jars to APP-INF/lib. This is a Weblogic extension of J2EE, so it won't work on other servers.
I'm currently using another approach.
Create a central repository folder and put all common libraries in there.
In each project you can create a reference to all needed libraries. In Subversion it works with externals
Everytime, the local working copy is updated, the externals are updated to, so you just need to commit to the central folder and it's automatically distributed to all projects.

Categories