/*
* Adito
*
* Copyright (C) 2003-2006 3SP LTD. All Rights Reserved
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package com.adito.server.jetty;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.mortbay.http.ResourceCache;
import org.mortbay.jetty.servlet.AbstractSessionManager;
import org.mortbay.jetty.servlet.ServletHolder;
import org.mortbay.jetty.servlet.WebApplicationContext;
import org.mortbay.util.Resource;
import com.adito.boot.Branding;
import com.adito.boot.ContextHolder;
import com.adito.boot.SystemProperties;
/**
* <p>
* An extension to the standard Jetty
* {@link org.mortbay.jetty.servlet.WebApplicationContext} that allows resources
* to be loaded from multiple {@link org.mortbay.http.ResourceCache}s.
*
* <p>
* This is necessary for the plugin architecture so that plugins may register
* their own <b>webapp</b> directories which can then be overlaid onto the
* namespace of the main Adito webapp.
*
* <p>
* Other Adito specific webapp configuration is also performed here,
* including setting up the special /defaultStyle.css alias that is used to
* workaround the change to the way the CSS file is load. If this alias was not
* set up, upgraders would have lost their CSS as /defaultStyle.css no longer
* really exists.
*
* <p>
* Plugins also register new classpaths here so that any Java bytecode they may
* require (be it in .CLASS format or .JAR format) may be loaded in the same
* Class Loader as the main webapp.
*/
public class CustomWebApplicationContext extends WebApplicationContext {
final static Log log = LogFactory.getLog(CustomWebApplicationContext.class);
private List<ResourceCache> resourceCaches;
private String additionalClasspath;
private List<ResourceCache> reverseCaches;
private Map<String, ResourceCache> resourceCacheMap;
private ResourceCache mainWebappResourceCache;
private Map<String, CacheState> cacheState;
/**
* Constructor
*
* @param useDevConfig <code>true</code> if running in development mode
* @throws Exception on any error
*/
public CustomWebApplicationContext(boolean useDevConfig, ClassLoader bootLoader) throws Exception {
super("webapp");
Resource webInf = getWebInf();
additionalClasspath = "";
resourceCacheMap = new HashMap<String, ResourceCache>();
reverseCaches = new ArrayList<ResourceCache>();
cacheState = new HashMap<String, CacheState>();
setContextPath("/");
setDefaultsDescriptor("/com/adito/boot/webdefault.xml");
setDisplayName(Branding.PRODUCT_NAME);
setTempDirectory(ContextHolder.getContext().getTempDirectory());
setResourceAlias("/defaultStyle.css", "/css/defaultStyle.jsp");
setParentClassLoader(bootLoader);
setWelcomeFiles(new String[] { "showHome.do" });
resourceCaches = new ArrayList<ResourceCache>();
if("true".equals(SystemProperties.get("adito.paranoidSessionManager", "true"))) {
((AbstractSessionManager)getServletHandler().getSessionManager()).setUseRequestedId(false);
((AbstractSessionManager)getServletHandler().getSessionManager()).setSecureCookies(true);
}
}
@Override
public void setClassPath(String classPath) {
super.setClassPath(classPath);
File webappBuild = new File(new File("build"), "webapp");
if(webappBuild.exists()) {
addClassPath(webappBuild.toURI().toString());
}
}
/**
* Get all resource caches
*
* @return resource caches
*/
public Collection<ResourceCache> getResourceCaches() {
return resourceCaches;
}
/**
* <p>
* Add a new Resource Cache. Whenever a resource is requested, this handler
* will search all registered resource caches until one can locate it.
*
* <p>
* This shouldn't be called directly, but through
* {@link com.adito.boot.Context#addResourceBase(URL)}
*
* @param cache cache to add
*/
public void addResourceCache(ResourceCache cache) {
resourceCaches.add(cache);
reverseCaches.clear();
cacheState.clear();
reverseCaches.addAll(resourceCaches);
Collections.reverse(reverseCaches);
}
/**
* <p>
* Remove a Resrouce Cache. Whenever a resource is requested, this handler
* will no longer use this cache.
*
* <p>
* This shouldn't be called directly, but through
* {@link com.adito.boot.Context#removeResourceBase(URL)}
*
* @param cache cache to remove
*/
public void removeResourceCache(ResourceCache cache) {
resourceCaches.remove(cache);
reverseCaches.clear();
cacheState.clear();
reverseCaches.addAll(resourceCaches);
Collections.reverse(reverseCaches);
}
protected void addComponent(Object o) {
if (o instanceof ResourceCache) {
mainWebappResourceCache = ((ResourceCache) o);
}
super.addComponent(o);
}
/*
* (non-Javadoc)
*
* @see org.mortbay.http.ResourceCache#getResource(java.lang.String)
*/
public Resource getResource(String pathInContext) throws IOException {
boolean fullResourceCache = SystemProperties.get("adito.fullResourceCache",
String.valueOf(!(SystemProperties.get("adito.useDevConfig", "false").equals("true")))).equals("true");
Resource r;
if (log.isDebugEnabled())
log.debug("Request for " + pathInContext);
// This is a work around to prevent WEB-INF getting listed by using the
// path //WEB-INF
if (pathInContext.indexOf("//WEB-INF") != -1) {
return null;
}
/*
* When in 'Full resource cache' mode, check if we already have cached
* the resource
*/
if (fullResourceCache && cacheState.containsKey(pathInContext)) {
r = cacheState.get(pathInContext).getResource();
if (log.isDebugEnabled())
if (r == null)
log.debug("Resource " + pathInContext + " is permanently as missing.");
else
log.debug("Resource " + pathInContext + " found in permanent cache");
return r;
}
/*
* Determine if the resource has already been found in a resource cache
* (be it an extensions resource cache or the cores)
*/
ResourceCache o = fullResourceCache ? null : (ResourceCache) resourceCacheMap.get(pathInContext);
if (o == null) {
/*
* The existence of the resource has not yet been determined. Search
* all resource caches in reverse until the extension is found. When
* found, store which cache it was found in for quick look up in the
* future.
*/
if (log.isDebugEnabled())
log.debug("Resource " + pathInContext + " not found in any resource cache, checking in plugins");
for (Iterator i = reverseCaches.iterator(); i.hasNext();) {
ResourceCache cache = (ResourceCache) i.next();
r = cache.getResource(pathInContext);
if (r != null && r.exists() && !r.isDirectory()) {
if (fullResourceCache) {
if (log.isDebugEnabled())
log.debug(" Found in " + cache.getBaseResource().toString());
cacheState.put(pathInContext, new CacheState(CacheState.FOUND, pathInContext, r));
} else {
if (log.isDebugEnabled())
log.debug(" Found in " + cache.getBaseResource().toString());
resourceCacheMap.put(pathInContext, cache);
}
return r;
}
}
/*
* The resource cannot be found in this caches base directory
*/
if (log.isDebugEnabled())
log.debug(" Not found");
} else {
/*
* We know what cache the resource came from so check it still
* exists and return. This will only happen when not in full cache
* mode
*/
r = o.getResource(pathInContext);
if (r != null && r.exists() && !r.isDirectory()) {
if (log.isDebugEnabled())
log.debug(" Found in " + o.getBaseResource().toString());
return r;
}
}
if (log.isDebugEnabled())
log.debug("Checking for alias in plugins");
String resourceAlias = getResourceAlias(pathInContext);
if (resourceAlias != null) {
/*
* The resource was not found with its real name in any caches base
* directory, so repeat the operation but look for the alias
*/
if (log.isDebugEnabled())
log.debug(" Found alias of " + resourceAlias + ", checking in plugins");
for (Iterator i = reverseCaches.iterator(); i.hasNext();) {
ResourceCache cache = (ResourceCache) i.next();
r = cache.getResource(resourceAlias);
/*
* When checking for resource modification, check for existence
* of file. This allows file to be removed at runtime without
* adding overhead when used on deployed server
*/
if (r != null && r.exists() && !r.isDirectory()) {
if (fullResourceCache) {
if (log.isDebugEnabled())
log.debug(" Found in " + cache.getBaseResource().toString());
cacheState.put(pathInContext, new CacheState(CacheState.FOUND, pathInContext, r));
return r;
} else {
if (log.isDebugEnabled())
log.debug(" Found in " + cache.getBaseResource().toString());
resourceCacheMap.put(pathInContext, cache);
return r;
}
}
}
if (log.isDebugEnabled())
log.debug(" Not found");
}
/*
* The resource could not be found in any caches base directory, so pass
* to the main webapp
*/
if (log.isDebugEnabled())
log.debug("Passing to main webapp");
r = super.getResource(pathInContext);
if (r != null && r.exists() && !r.isDirectory()) {
/*
* The resource has been found in the main webapps base directory,
* store where it was found for quick lookup in future requests
*/
if (log.isDebugEnabled())
log.debug(" Found in main webapp");
if (fullResourceCache) {
cacheState.put(pathInContext, new CacheState(CacheState.FOUND, pathInContext, r));
} else {
resourceCacheMap.put(pathInContext, mainWebappResourceCache);
}
return r;
} else {
if (fullResourceCache) {
if (log.isDebugEnabled())
log.debug(" Not found, caching as missing");
cacheState.put(pathInContext, new CacheState(CacheState.MISSING, pathInContext, r));
}
}
/* Not found at all */
if (log.isDebugEnabled())
log.debug(" Found in main webapp");
return r;
}
}