Java Development News:

Dynamically Merging Directories inside a Web App Context

By Howard Lewis Ship

07 Dec 2004 | TheServerSide.com

So, I'm working with their workspace directory layout; there's a common directory that contains the main content (images, stylesheets, Tapestry artifacts). Then there's additional directories, such as defaultskin, that contain skin-specific assets and artifacts.

When building and deploying, the content is merged together to form a composite web application context.

For development purposes, I want to leave the directory structure intact, but that makes it impossible to run the application (the assets and artifacts in the defaultskin directory simply aren't visible. What I need is to be able to have a virtual directory within my web application that points to an entirely different directory; I want to map /skin in my context to the defaultskin folder, so I can use URLs like http://.../skin/images/about.gif.

Did a bit of research and realized that the correct approach was to create my own implementation of Jetty's WebApplicationContext that included the necessary hooks:

package portal.jetty;

import java.io.IOException;

import org.mortbay.jetty.servlet.ServletHttpContext;
import org.mortbay.jetty.servlet.WebApplicationContext;
import org.mortbay.util.Resource;

/**
 * Used only during testing using Jetty, this allows resources "within"
 * a web application context to be retrieved from an entirely different
 * directory.
 *
 * @author Howard Lewis Ship
 */
public class SkinContext extends WebApplicationContext
{
    private String _skinURI;
    private String _skinPath;
    private Resource _skinResource;

    /**
     * Standard constructor; passed a path or URL for a war (or exploded war
     * directory).
     */
    public SkinContext(String war)
    {
        super(war);
    }

    public String getSkinURI()
    {
        return _skinURI;
    }

    public void setSkinURI(String string)
    {
        _skinURI = string;
    }

    public Resource getResource(String contextPath) throws IOException
    {
        if (contextPath.startsWith(_skinURI))
        {
            String postPrefixPath = contextPath.substring(_skinURI.length());

            return _skinResource.addPath(postPrefixPath);
        }

        return super.getResource(contextPath);
    }

    public String getSkinPath()
    {
        return _skinPath;
    }

    public void setSkinPath(String string)
    {
        _skinPath = string;
    }

    public void start() throws Exception
    {
        super.start();

        _skinResource = Resource.newResource(_skinPath);
    }

}

This gets combined with a specialized jetty.xml (startup configuration file):

<!-- At deployment time, the WAR will be built properly, so that content under 
  "skin" is just appropriate to the type of site. During development, we don't want to have
  to copy files around unecessarily, so we alias the web/defaultskin directory as "/skin". -->
   
<Configure class="org.mortbay.jetty.Server">
  <Call name="addListener">
    <Arg>
      <New class="org.mortbay.http.SocketListener">
        <Set name="port">8080</Set>
      </New>
    </Arg>
  </Call>
  
  <Call name="addContext">
    <Arg/>
    <Arg>
      <New class="portal.jetty.SkinContext">
        
          <Arg>web/common</Arg>
        
        <Set name="contextPath">/</Set>
        <Set name="skinURI">/skin/</Set>
        <Set name="skinPath">web/defaultskin/</Set>
      </New>
    </Arg>
  </Call>
  
</Configure>

Viva open source! I didn't even bother pulling down the Jetty source to figure this out, I just navigated around Jetty's browse CVS to find the few code snippets. Of course, I don't think this solution is ideal for deployment, but that's not the point ... it's about making my development time efficient and I'm quite happy with it.



About the author

Author:Howard Lewis Ship howard@howardlewisship.com
Blog: http://howardlewisship.com/blog/

Howard Lewis Ship is the lead developer for the Tapestry project. He has over fifteen years of full-time software development under his belt, with over six years of Java. He cut his teeth writing customer support software for Stratus Computer, but eventually traded PL/1 for Objective-C and NextStep before settling into Java. Howard is the author of Tapestry in Action (Manning), and is currently an independent open-source and J2EE consultant, specializing in customized Tapestry training.

Related Resources