package net.sourceforge.javautil.web.server.application.impl;
import java.io.IOException;
import java.net.URL;
import java.security.Principal;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.sourceforge.javautil.classloader.impl.ClassContext;
import net.sourceforge.javautil.classloader.impl.StandardClassLoaderHeiarchy;
import net.sourceforge.javautil.classloader.resolver.IClassDependencyPool;
import net.sourceforge.javautil.classloader.resolver.ClassPackageContext;
import net.sourceforge.javautil.classloader.resolver.IClassPackageDependencyReference;
import net.sourceforge.javautil.classloader.resolver.IClassPackageDescriptor;
import net.sourceforge.javautil.classloader.resolver.IClassDependencyPool.PoolScope;
import net.sourceforge.javautil.classloader.resolver.impl.ClassDependencyPoolImpl;
import net.sourceforge.javautil.classloader.resolver.impl.maven.ProjectObjectModel;
import net.sourceforge.javautil.classloader.source.ClassSource;
import net.sourceforge.javautil.classloader.source.ClassSourceScanner;
import net.sourceforge.javautil.classloader.source.CompositeClassSource;
import net.sourceforge.javautil.classloader.source.ClassSourceScanner.ClassSourceMatch;
import net.sourceforge.javautil.classloader.source.ClassSourceVisitorContext.Type;
import net.sourceforge.javautil.classloader.util.ClassPackageUtil;
import net.sourceforge.javautil.common.ReflectionUtil;
import net.sourceforge.javautil.common.exception.ThrowableManagerRegistry;
import net.sourceforge.javautil.common.io.IModifiableInputSource;
import net.sourceforge.javautil.common.io.VirtualArtifactNotFoundException;
import net.sourceforge.javautil.common.io.VirtualArtifactSystem;
import net.sourceforge.javautil.common.io.IVirtualDirectory;
import net.sourceforge.javautil.common.io.impl.Directory;
import net.sourceforge.javautil.common.io.impl.DirectoryRoot;
import net.sourceforge.javautil.common.proxy.DetachedProxy;
import net.sourceforge.javautil.common.proxy.IDetachedProxySource;
import net.sourceforge.javautil.common.proxy.RecursiveInterface;
import net.sourceforge.javautil.common.reflection.cache.ClassCache;
import net.sourceforge.javautil.common.reflection.cache.ClassDescriptor;
import net.sourceforge.javautil.common.xml.XMLDocument;
import net.sourceforge.javautil.common.xml.annotation.XmlTag;
import net.sourceforge.javautil.web.server.application.IWebApplication;
import net.sourceforge.javautil.web.server.application.WebApplicationAuthenticator;
import net.sourceforge.javautil.web.server.application.WebApplicationAuxilaryDescriptorLocation;
import net.sourceforge.javautil.web.server.application.WebApplicationDeploymentContext;
import net.sourceforge.javautil.web.server.application.IWebApplicationResourceResolver;
import net.sourceforge.javautil.web.server.application.ext.IWebApplicationExtension;
import net.sourceforge.javautil.web.server.application.ext.WebApplicationExtensionAbstract;
import net.sourceforge.javautil.web.server.deployer.WebApplicationServerDescriptor;
import net.sourceforge.javautil.web.server.descriptor.IWebXml;
import net.sourceforge.javautil.web.server.descriptor.impl.WebXml;
/**
* This provides a base for most standard {@link IWebApplication}
* implementations, providing common functionality.
*
* @author elponderador
* @author $Author: ponderator $
* @version $Id: WebApplicationAbstract.java 2747 2011-02-05 04:30:12Z ponderator $
*/
public abstract class WebApplicationAbstract implements IWebApplication {
protected final String name;
protected final String path;
protected final Logger log = LoggerFactory.getLogger(getClass().getName());
protected Map<String, String> authenticators = new LinkedHashMap<String, String>();
protected Map<String, ExtensionDefinition> extensions = new LinkedHashMap<String, ExtensionDefinition>();
protected List<WebApplicationAuthenticator> loadedAuthenticators = new ArrayList<WebApplicationAuthenticator>();
protected List<IWebApplicationExtension> loadedExtensions = new ArrayList<IWebApplicationExtension>();
protected List<Object> auxiliaryDescriptors = new ArrayList<Object>();
protected WebXml webXml;
protected IWebXml readOnlyWebXml;
protected VirtualArtifactSystem vasRoot;
protected IWebApplicationResourceResolver resolver;
protected ClassLoader parentClassLoader;
protected ClassContext classContext;
protected IVirtualDirectory root;
protected boolean reloadable = true;
protected WebApplicationServerDescriptor descriptor;
public WebApplicationAbstract(String name, String path, IVirtualDirectory root) {
this.name = name;
this.path = path;
this.vasRoot = VirtualArtifactSystem.get("net.sf.javautil.web.server.application." + name, true);
this.resolver = new VirtualDirectoryResolver(vasRoot);
this.root = root;
this.readOnlyWebXml = DetachedProxy.create(getClass().getClassLoader(),
new Class[] { IWebXml.class }, IWebXml.class, new DetachedWebXml(), new RecursiveInterface());
}
public ClassLoader getParentClassLoader() { return parentClassLoader; }
public void setParentClassLoader(ClassLoader parentClassLoader) { this.parentClassLoader = parentClassLoader; }
public boolean isReloadable() { return reloadable; }
public void setReloadable(boolean reloadable) { this.reloadable = reloadable; }
public void addExtension (String name, String extensionClassName, Map<String, Object> settings) {
this.extensions.put(name, new ExtensionDefinition(name, extensionClassName, settings));
}
public void addAuthenticator (String name, String authenticatorClassName) {
this.authenticators.put(name, authenticatorClassName);
}
public IVirtualDirectory getRoot() { return root; }
public ClassContext getClassLoader() { return this.classContext; }
public boolean hasBeenModifiedSince(long timestamp) {
return this.classContext != null && this.classContext.getAllSources().getLastModifiedClass() > timestamp;
}
public IWebXml getWebXml() { return this.readOnlyWebXml; }
public List<WebApplicationAuthenticator> getAuthenticators() {
return new ArrayList<WebApplicationAuthenticator>(this.loadedAuthenticators);
}
public List<IWebApplicationExtension> getExtensions() {
return new ArrayList<IWebApplicationExtension>(this.loadedExtensions);
}
public String getContextPath() { return this.path; }
public String getName() { return name; }
public IWebApplicationResourceResolver getResourceResolver() { return this.resolver; }
public String getWebXmlAsXml() {
return webXml == null ? null : webXml.toXML();
}
public void postUndeployCleanup(WebApplicationDeploymentContext ctx, boolean reload) {
ctx.getHost().preCleanup(this);
for (IWebApplicationExtension extension : this.loadedExtensions) {
extension.cleanup(ctx);
}
this.webXml = null;
this.loadedAuthenticators.clear();
this.loadedExtensions.clear();
if (this.classContext != null && !reload) {
this.classContext.cleanup();
this.classContext = null;
}
if (this.root instanceof DirectoryRoot && !reload) {
VirtualArtifactSystem vas = VirtualArtifactSystem.get("net.sf.javautil.web.server." + ctx.getHost().getName() + "." + getName(), false);
if (vas != null) vas.unlink(this.root.getPath());
}
}
public void preDeploySetup(WebApplicationDeploymentContext ctx, boolean reload) {
if (this.resolver == null)
throw new IllegalStateException("Cannot call predeploy setup before initializing the resolver");
vasRoot.link(this.root);
this.classContext = new WebApplicationClassLoader(ctx);
this.classContext.setPool(ClassPackageUtil.createStandardDependencyPool("Web Application Pool: " + this.getName(), true, PoolScope.Root));
this.classContext.getAllSources().refresh();
for (String name : this.authenticators.keySet()) {
ClassDescriptor<? extends WebApplicationAuthenticator> cd = ClassCache.getFor(ReflectionUtil.getClass(this.authenticators.get(name), this.classContext));
this.loadedAuthenticators.add(cd.newInstance());
}
this.descriptor = this.getAuxilaryDescriptor(WebApplicationServerDescriptor.class);
if (this.descriptor != null) {
this.setReloadable(this.descriptor.isAutoRestart());
}
this.initializeWebXml();
IClassDependencyPool pool = this.classContext.getPool();
this.loadExtension(ctx, pool, new MavenExtension(name, this));
for (String name : this.extensions.keySet()) {
ClassDescriptor cd = ClassCache.getFor(ReflectionUtil.getClass(this.extensions.get(name).className, this.classContext));
if (this.isExtensionLoaded(cd.getDescribedClass())) continue;
IWebApplicationExtension extension = (IWebApplicationExtension) cd.newInstance(this);
if (!extension.shouldLoad(webXml)) continue;
this.loadedExtensions.add(extension);
if (this.extensions.get(name).settings != null) {
cd.deserializeProperties(extension, this.extensions.get(name).settings);
}
pool = this.loadExtension(ctx, pool, extension);
}
ctx.getHost().postSetup(this, webXml);
}
/**
* @param ctx The context in which this application is being deployed
* @param pool The pool for
*/
protected IClassDependencyPool loadExtension (WebApplicationDeploymentContext ctx, IClassDependencyPool pool, IWebApplicationExtension extension) {
log.info("Loading extension: " + extension.getName());
extension.setup(webXml, ctx);
List<? extends IClassPackageDependencyReference> wdd = extension.getClassPackageDependencies();
if (wdd != null && wdd.size() > 0) {
pool = pool.createChild("Web Application Dependency: " + extension.getName(), PoolScope.Component);
ClassPackageUtil.append(pool, ClassPackageContext.getPackageResolver(),
extension.isArePrimaryDependencies(), extension.getDescriptor(),
wdd.toArray(new IClassPackageDependencyReference[wdd.size()])
);
}
CompositeClassSource ccs = extension.getNonClassPackageDependencies();
if (ccs != null) {
this.classContext.getNonPackageSources().add(ccs);
this.loadDynamicExtensions(ctx, pool, new CompositeClassSource(ccs, pool.getCompositeClassSource()));
} else if (wdd != null && wdd.size() > 0) {
this.loadDynamicExtensions(ctx, pool, pool.getCompositeClassSource());
}
return pool;
}
/**
* @param ctx The context in which we are deploying
* @param pool The current pool for dependency resolution
* @param source The source to load dynamic extensions for
*/
public void loadDynamicExtensions (WebApplicationDeploymentContext ctx, IClassDependencyPool pool, ClassSource source) {
for (IWebApplicationExtension extension : this.findNewExtensions(source)) {
this.loadExtension(ctx, pool, extension);
}
}
/**
* @param source The source to search for dynamic extensions
* @return A list, possibly empty, of extensions found in the source
*/
public List<IWebApplicationExtension> findNewExtensions (ClassSource source) {
List<IWebApplicationExtension> extensions = new ArrayList<IWebApplicationExtension>();
for (ClassSourceMatch match : source.accept(new ClassSourceScanner("webserver.properties").addSuffixes("WebExtension")).getMatches()) {
if (match.getType() == Type.Resource) continue;
try {
ClassDescriptor eclass = ClassCache.getFor( classContext.loadClass(match.getName()) );
if (!this.isExtensionLoaded(eclass.getDescribedClass())) {
boolean load = true;
IWebApplicationExtension replace = null;
for (IWebApplicationExtension ext : extensions) {
if (eclass.getDescribedClass().isAssignableFrom(ext.getClass())) { load = false; break; }
if (ext.getClass().isAssignableFrom(eclass.getDescribedClass())) {
replace = ext; break;
}
}
if (!load) continue;
if (replace != null) { extensions.remove(replace); }
IWebApplicationExtension extension = (IWebApplicationExtension) eclass.newInstance(this);
extensions.add(extension);
if (this.descriptor != null && this.descriptor.getDescriptorFor(extension) != null) {
if (this.descriptor.getDescriptorFor(extension).getSettings() != null)
eclass.deserializeProperties(extension, this.descriptor.getDescriptorFor(extension).getSettings());
}
}
} catch (ClassNotFoundException e) {
ThrowableManagerRegistry.caught(e);
}
}
return extensions;
}
/**
* @param type The type to check for
* @return True if an extension that is of the type or a sub type is already loaded, otherwise false
*/
public boolean isExtensionLoaded (Class<? extends IWebApplicationExtension> type) {
for (IWebApplicationExtension extension : this.loadedExtensions) {
if (type.isAssignableFrom(extension.getClass())) return true;
}
return false;
}
/**
* If a descriptor has already been retreived it can be manually
* added here for later retrieval.
*
* @param descriptor The descriptor to add
*/
public void addAuxilaryDescriptor (Object descriptor) {
this.auxiliaryDescriptors.add(descriptor);
}
public <T> T getAuxilaryDescriptor(Class<T> type) {
String path = null;
WebApplicationAuxilaryDescriptorLocation waadl = type.getAnnotation(WebApplicationAuxilaryDescriptorLocation.class);
if (waadl != null) {
path = waadl.relativeLocation();
} else {
XmlTag tag = type.getAnnotation(XmlTag.class);
if (tag != null && !"".equals(tag.name())) {
path = "WEB-INF/" + tag.name() + ".xml";
} else {
path = "WEB-INF/" + type.getSimpleName() + ".xml";
}
}
return this.getAuxilaryDescriptor(path, type);
}
public <T> T getAuxilaryDescriptor(String path, Class<T> type) {
T descriptor = this.findAuxiliaryDescriptor(type);
if (descriptor == null) {
URL url = this.getResourceResolver().getResource(path);
if (url != null)
descriptor = XMLDocument.read(url, type);
if (descriptor != null)
this.auxiliaryDescriptors.add(descriptor);
}
return descriptor;
}
public void setup(ServletContext ctx) {}
public void cleanup(ServletContext ctx) {}
public void log(String msg) { log.info(msg); }
public void log(String msg, Throwable t) {
log.error(msg + ": " + t.getMessage());
t.printStackTrace();
}
public String toString () { return getClass().getSimpleName() + "[ " + this.name + ", path: " + this.path + " ]"; }
/**
* @param <T> The type of descriptor
* @param type The descriptor class
* @return The instance that is of the type or a sub type of the type specified
*/
protected <T> T findAuxiliaryDescriptor (Class<T> type) {
for (Object ad : this.auxiliaryDescriptors) {
if (type.isAssignableFrom(ad.getClass())) return (T) ad;
}
return null;
}
/**
* Initialize the web.xml
*/
protected void initializeWebXml () {
if (this.webXml == null) {
URL webXmlURL = this.resolver.getResource("WEB-INF/web.xml");
this.webXml = webXmlURL == null ? new WebXml() : WebXml.getInstance(webXmlURL);
}
}
/**
* This will allow access to the web.xml help internally for the
* {@link DetachedProxy} making the web.xml read-only.
*
* @author elponderador
* @author $Author: ponderator $
* @version $Id: WebApplicationAbstract.java 2747 2011-02-05 04:30:12Z ponderator $
*/
private class DetachedWebXml implements IDetachedProxySource {
public <T> T getInstance(Class<T> type) {
return type == IWebXml.class ? (T) webXml : null;
}
}
/**
* The composite information about an extension that should be loaded and executed
* at predeployment.
*
* @author elponderador
* @author $Author: ponderator $
* @version $Id: WebApplicationAbstract.java 2747 2011-02-05 04:30:12Z ponderator $
*/
private class ExtensionDefinition {
protected String name;
protected String className;
protected Map<String, Object> settings;
public ExtensionDefinition(String name, String className, Map<String, Object> settings) {
this.name = name;
this.className = className;
this.settings = settings;
}
}
/**
* This will cause the standard WEB-INF/pom.xml to be recognized for class loaders.
*
* @author elponderador
* @author $Author: ponderator $
* @version $Id: WebApplicationAbstract.java 2747 2011-02-05 04:30:12Z ponderator $
*/
public static class MavenExtension extends WebApplicationExtensionAbstract {
protected ProjectObjectModel pom;
public MavenExtension(String name, IWebApplication application) {
super(name, application);
}
@Override public boolean isArePrimaryDependencies() { return true; }
@Override public List<? extends IClassPackageDependencyReference> getClassPackageDependencies() {
if (this.getDescriptor() != null) {
return this.getDescriptor().getDependencies(net.sourceforge.javautil.classloader.resolver.IClassDependency.Scope.Runtime);
}
return super.getClassPackageDependencies();
}
@Override public ProjectObjectModel getDescriptor() {
if (this.pom == null) {
URL pomURL = this.application.getResourceResolver().getResource("WEB-INF/pom.xml");
if (pomURL != null) {
IModifiableInputSource input = new IModifiableInputSource.URLInputSource(pomURL);
pom = ProjectObjectModel.parse(ClassPackageContext.getPackageResolver(), input);
}
}
return pom;
}
}
}