Built with Seam

You can find the full source code for this website in the Seam package in the directory /examples/wiki. It is licensed under the LGPL.

To clear some of the mystery behind Seam apps deploying in JBoss5, we'll descibe what's going on during the deploy. We'll also describe a couple of easy workarounds to stop these issues occuring.

Most of the issues we're seeing is due new functionality we introduced in JBoss5 and its new kernel - Microcontainer (MC); unfortunately we still haven't fully explored and tested them. There are just too many details to consider!

The quick fixes

So you just want to make JBoss 5 redeployment work well? Then just make these changes to JBoss AS (they will be included in 5.2 and later).

You'll need to open up $JBOSS_HOME/server/default/conf/bootstrap/profile.xml. First, remove line 100 (<value>WEB-INF/dev</value>); this causes the hot-deploy classes to be properly read by Seam.

Next, find line 82 (<property name="filter"><bean class="org.jboss.system.server.profile.basic.XmlIncludeVirtualFileFilter" /></property>), and replace it with

<property name="filter">
	<!-- A pattern matching filter. We can configure this to our custom pattern.
		Here we configure it to include only application.xml and web.xml 
		for modification check (and any subsequent redeployments) -->
	<bean class="org.jboss.system.server.profile.basic.PatternIncludeVirtualFileFilter">
			<!-- Remember, the parameter is a regex so use the correct syntax
				as below -->
			<parameter class="java.lang.String">application.xml|web.xml</parameter>

Finally, if you are seeing a continuous redeployment loop, you need to make sure there are no files like *.jsfdia or *.spdia (or other IDE generated temp files) in your WEB-INF/ or META-INF/. This actually requires a fix to the JBoss deployers code, and we are testing whether you can make a drop in replacement for the deployers jar.

The gory details!

One of the issues that actually caused the most changes to overall deployment lifecycle is JBAS-6117 - some thread in the background still does some work on the resources that were just deleted at undeployment. Hence you need a copy of those resources to be actually able to find them, even though they are deleted! We only remove the temp copy after undeploy completes.

We think this was already an issue in JBoss4, but nobody ever complained about it, since it was usually some error/warning at undeploy, which didn't look very important. But conceptually, there was still a problem which dealt with in JBoss5.

With introduction of JBoss VFS, we didn't see the need to do copy/temp by default, as the VFS can handle nested archives easily. However many frameworks, such as Wicket - as the example shows - assume resources are around at undeploy. You never really know when you're gonna need a copy to make your app properly cleanup.

There is a declarative mechanism in the MC Deployers, jboss-structure.xml, where you can define that your app should be temped. Of course, this would mean that every Seam application would have to place this file in its app to make the temping take place. Not good enough! We decided to make the temping optional via programmatic check - after MC Deployers finished recognition of structure, we actually check if the app is a Seam app and declare it to be temped while building Deployer's DeploymentContext instance. And this is what SeamModificationTypeMatcher does.

A deployer's StructureBuilder uses StructureProcessor to pre- and post-process Deployments and DeploymentContexts. The current impl of StructureProcessor is ModificationTypeStructureProcessor which takes a set of ModificationTypeMatchers and runs them over Deployment in progress to see if we need to change modification type.

   <bean name="ModificationTypeStructureProcessor" class="org.jboss.deployers.vfs.plugins.structure.modify.ModificationTypeStructureProcessor">
     <incallback method="addMatcher"/>
     <uncallback method="removeMatcher"/>

SeamTempModificationTypeMatcher is one of those matchers, checking if there is some Seam deployment descriptor, and then changing modification type to Temp. Removing this bean, means the Deployment will stay the way it was after structure recognition.

Now have a temped app, hence we can properly handle resources lookup on undeploy. But what about checking if something needs to be redeployed or if the original files have been updated?

This proves to be a much harder task than it looked at the beginning. The problem now is strict separation between user/client and server view of the deployment. The client view is pretty much limited, where the real information is held on the sever side; e.g. metadata locations, classpath ... But in our case this is not one and the same thing, remember we temped our app. So, the client sees the original, where the server sees the temp. But for all the resource loading mechanism used should be transparent.

In JBoss5 it's the Profile Service that only knows if something changed, needs to be re-deployed, etc. At the beginning deployment modification check was part of AS code, but the issue actually proved to be a Deployers issue. So, we moved the actual impl into Deployers project under StructureModificationChecker interface.

The first impl was MetaDataStructureModificationChecker, which only checks metadata locations for potential updates / changes. The idea was use the server side information (DeploymentContext) on the client side (Deployment), to do proper checks. This way we actually don't care if the app is temped, as we only care about client changes, temping is impl detail, not known to user.

  <bean name="MetaDataStructureModificationChecker" class="org.jboss.deployers.vfs.spi.structure.modified.MetaDataStructureModificationChecker">
      <parameter><inject bean="MainDeployer" /></parameter>
    <property name="cache"><inject bean="StructureModCache" /></property>
    <property name="filter"><bean class="org.jboss.system.server.profile.basic.XmlIncludeVirtualFileFilter" /></property>

But this still doesn't know about changes outside metadata locations; e.g. the ones that don't need re-deployment. For this purpose SynchWrapperModificationChecker was added, which handles the updates, only when its delegate StructureModificationChecker doesn't already signal modified.

   protected boolean hasStructureBeenModifed(VirtualFile root, VFSDeploymentContext deploymentContext) throws IOException
      boolean modified = delegate.hasStructureBeenModifed(root, deploymentContext);
      // it was not modifed & we're actually temped
      if (modified == false && root != deploymentContext.getRoot())
         // check for update or delete
         UpdateDeleteVisitor udVisitor = new UpdateDeleteVisitor(filter, tempAttributes, getCache(), synchAdapter, root);
         VirtualFile tempRoot = deploymentContext.getRoot();
         // check for addition
         AddVisitor addVisitor = new AddVisitor(filter, originalAttributes, getCache(), synchAdapter, tempRoot, root.getPathName().length());
      return modified;

The majority of weird re-deployment problems comes from doing proper check if any of the files was actually modified. In order to do this check, we must hold previous timestamp in some sort of cache - StructureCache. To at least limit the number of resources we check, we try to define proper file filters. And this is what's mostly causing trouble, as we try to be too generic - currently matching all *.xml files, where users were mostly accustomed to classic JEE spec defined files check; e.g. web.xml, app.xml, etc.

    <property name="filter"><bean class="org.jboss.system.server.profile.basic.XmlIncludeVirtualFileFilter" /></property>

By changing this, you limit what we check when doing metadata locations check. But defining this filter one must note that we should be able to filter two different things although they are conceptually the same - VFS files and StructureCache nodes. Hence two different filters, but they can be easily joined into one.

From MetaDataStructureModificationChecker

    * Check filters.
   public void start()
      if (cacheFilter == null)
         if (filter instanceof StructureCacheFilter)
            cacheFilter = (StructureCacheFilter) filter;
            log.warn("No cache filter specified, possible non match on leaves.");
      else if (cacheFilter != filter)
         // not the same instance
         log.debug("VFS filter and structure cache filter are not the same instance, possible compatibility issue?");

Here is an example of the current XmlIncludeVirtualFileFilter

public class XmlIncludeVirtualFileFilter extends AbstractPathNameFilter
   public boolean accepts(String path)
      return path.endsWith(".xml");

Note AbstractPathNameFilter, which is what helps you join the two filters together - VirtualFileFilter and StructureCacheFilter.

We're actually planning on changing the current XmlIncludeVirtualFileFilter with the one that would only understand files from parsing deployers, and at the same time allowing you to define your own extension. Hopefully this way we won't pick-up any Seam custom files; e.g. pages.xml, components.xml, etc.

OK, this is a pretty long read, but hopefully a useful one. ;-)