package jfun.yan.web;
import java.io.InputStream;
import javax.servlet.ServletContext;
import jfun.util.ClassLoaderUtils;
import jfun.yan.Container;
import jfun.yan.YanException;
import jfun.yan.lifecycle.DefaultLifecycleManager;
import jfun.yan.lifecycle.LifecycleManager;
import jfun.yan.util.resource.ClassLoader2ResourceLoader;
import jfun.yan.util.resource.ResourceLoader;
import jfun.yan.xml.NutsProcessor;
import jfun.yan.xml.nuts.spring.SpringNuts;
/**
* This class is responsible for setting up web integration for Yan.
* <p>
* @author Ben Yu
* Jan 17, 2006 1:27:59 PM
*/
public class YanLoader {
/**
* Name of servlet context parameter that can specify the configuration file
* for the root container, falling back to {@link #DEFAULT_CONFIG_FILE}
* otherwise.
*/
public static final String CONFIG_FILE_PARAM = "yanConfigFile";
/**
* Name of the servlet context parameter that specifies whether Spring
* framework integration is required. The default is "false".
*/
public static final String USE_SPRING = "yanSpringIntegration";
/**
* The value "true" that should be specified for {@link #USE_SPRING}
* if Spring framework integration is desired.
*/
public static final String TRUE = Boolean.valueOf(true).toString();
/**
* The default location of the yan configuration file.
*/
public static final String DEFAULT_CONFIG_FILE = "/WEB-INF/yan.xml";
/**
* The service key for the ServletContext object.
*/
public static final String SERVLET_CONTEXT_KEY = "jfun.yan.web.ServletContext";
/**
* The name of the servlet context attribut that holds the Container instance.
* Equal to the fully qualified class name of {@link Container}.
*/
public static final String CONTAINER_ROOT = Container.class.getName();
/**
* The name of the servlet context attribute that holds the life cycle manager
* instance. Equal to the fully qualified name of {@link LifecycleManager}.
*/
public static final String LIFECYCLE_MANAGER_ROOT = LifecycleManager.class.getName();
/**
* The default xml file that configures the non-spring nut classes.
*/
public static final String DEFAULT_NUTS_FILE = "jfun/yan/web/webnuts.xml";
/**
* The default xml file that configures spring aware nut classes.
*/
public static final String DEFAULT_SPRING_NUTS_FILE = "jfun/yan/web/webspringnuts.xml";
private NutsProcessor processor;
private Container yan;
private DefaultLifecycleManager lifecycle_manager;
/**
* Initialize this object by instantiating container and life cycle manager.
* Relevant servlet context parameters are read.
* @param ctxt the servlet context object.
* @return the Container object instantiated.
*/
public Container initContainer(ServletContext ctxt){
return initContainer(
ClassLoaderUtils.guessClassLoader(getClass().getClassLoader()),
ctxt);
}
/**
* Call start on the life cycle manager.
*/
public synchronized void start()
throws Throwable{
if(lifecycle_manager!=null)
lifecycle_manager.start();
}
/**
* Call stop on the life cycle manager.
*/
public synchronized void stop()throws Throwable{
if(lifecycle_manager != null)
lifecycle_manager.stop();
}
/**
* Initialize this object by instantiating container and life cycle manager.
* Relevant servlet context parameters are read.
* <p>
* This implementation calls {@link #process(ClassLoader, ServletContext)}
* for interpreting the configuration file and obtaining the Container instance
* and LifecycleManager instance.
* </p>
* @param cloader the ClassLoader used to load component classes.
* @param servletContext the servlet context object.
* @return the Container object instantiated.
* null is returned if configuration file not found.
*/
public synchronized Container initContainer(ClassLoader cloader, ServletContext servletContext){
servletContext.log("Loading Nuts container");
if(processor!=null){
throw new IllegalStateException("YanLoader already initialized.");
}
try {
this.processor = process(cloader, servletContext);
if(this.processor==null){
return null;
}
Container parent = loadParentContainer(servletContext);
this.yan = parent!=null?processor.getContainer().inherit(parent)
: processor.getContainer();
this.lifecycle_manager = processor.getLifecycleManager();
servletContext.setAttribute(CONTAINER_ROOT, this.yan);
servletContext.setAttribute(LIFECYCLE_MANAGER_ROOT, this.yan);
processor.preInstantiate(this.yan);
return this.yan;
}
catch (RuntimeException ex) {
servletContext.log("Container initialization failed", ex);
servletContext.setAttribute(CONTAINER_ROOT, ex);
throw ex;
}
catch(Exception e){
servletContext.log("Container initialization failed", e);
servletContext.setAttribute(CONTAINER_ROOT, e);
throw new YanException(e);
}
catch (Error err) {
servletContext.log("Container initialization error", err);
servletContext.setAttribute(CONTAINER_ROOT, err);
throw err;
}
}
/**
* Load the parent Container object that's shared among web applications.
* @param servletContext the servlet context.
* @return the Container object. or null if not found.
*/
protected Container loadParentContainer(ServletContext servletContext)
throws Exception{
return null;
}
/**
* Create a NutsProcessor object based on the configuration in ServletContext.
* The configuration file is also parsed and interpreted.
* <p>
* This default implementation delegates to {@link #getNutsProcessor(ClassLoader, ResourceLoader, ServletContext)}
* for constructing the NutsProcessor object.
* </p>
* <p>
* If no ServletContext component is registered, the current ServletContext object
* is registered as a component in the container.
* </p>
* @param cloader the ClassLoader object used to load component classes.
* @param ctxt the ServletContext object.
* @return the populated NutsProcessor object.
* @throws Exception any error.
*/
protected NutsProcessor process(ClassLoader cloader, ServletContext ctxt)
throws Exception{
String config_file = ctxt.getInitParameter(CONFIG_FILE_PARAM);
if(config_file==null){
ctxt.log(CONFIG_FILE_PARAM
+" not specified, looking for default "+DEFAULT_CONFIG_FILE);
config_file = DEFAULT_CONFIG_FILE;
}
final ResourceLoader rloader = new ServletContextResourceLoader(ctxt,
new ClassLoader2ResourceLoader(cloader));
final InputStream in = rloader.getResourceAsStream(config_file);
if(in == null){
ctxt.log("yan configuration file "+config_file+" not found.");
return null;
}
try{
final NutsProcessor proc = getNutsProcessor(cloader, rloader, ctxt);
proc.process(rloader.getResource(config_file), in);
registerServletContext(proc.getContainer(), ctxt);
return proc;
}
finally{
in.close();
}
}
/**
* Register the ServletContext into the container using
* either {@link WebUtils#DEFAULT_SERVLET_CONTEXT_PROPERTY} or
* else ServletContext.class as long as there's no ServletContext
* component already registered.
* @param container the Yan container object.
* @param ctxt the ServletContext object.
*/
protected static void registerServletContext(Container container, ServletContext ctxt){
if(!container.containsType(ServletContext.class)){
if(container.containsKey(WebUtils.DEFAULT_SERVLET_CONTEXT_PROPERTY)){
container.registerValue(ServletContext.class, ctxt);
}
else{
container.registerValue(WebUtils.DEFAULT_SERVLET_CONTEXT_PROPERTY, ctxt);
}
}
}
/**
* Construct a NutsProcessor object based on the configuration information
* stored in ServletContext. And then call {@link #decorateProcessor(NutsProcessor, ServletContext)}
* to decorate it.
* @param cloader the ClassLoader object used to load component class.
* @param rloader the ResourceLoader object used to load resource.
* @param ctxt the ServletContext object.
* @return the result NutsProcessor object.
* @throws Exception any error.
*/
protected NutsProcessor getNutsProcessor(ClassLoader cloader, ResourceLoader rloader,
ServletContext ctxt)throws Exception{
final NutsProcessor proc = new NutsProcessor(cloader, rloader);
decorateProcessor(proc, ctxt);
return proc;
}
/**
* Decorate a NutsProcessor object by adding or removing features.
* <p>
* This default implementation replaces <bean>, <ctor>
* and <method> tags with the ones that're aware of the ServletContext.
* </p>
* <p>
* It does so by registering the ServletContext as a service under {@link #SERVLET_CONTEXT_KEY}.
* The customized tags then look up this service and call "setServletContext()" or
* any other specified receiver method to set this dependency.
* </p>
* <p>
* Based on whether {@link #USE_SPRING} parameter is set in ServletContext,
* this implementation calls either {@link #setSpringAware(NutsProcessor, ServletContext)}
* or {@link #setWebAware(NutsProcessor, ServletContext)} to do the decoration.
* </p>
* <p>
* Subclasses can override this method to provide customized decoration.
* </p>
* @param processor the NutsProcessor object to decorate.
* @param ctxt the ServletContext object.
* @throws Exception any error.
*/
protected void decorateProcessor(NutsProcessor processor, ServletContext ctxt)
throws Exception{
processor.registerService(SERVLET_CONTEXT_KEY, ctxt);
String spring_integration = ctxt.getInitParameter(USE_SPRING);
if(spring_integration!=null && TRUE.equals(spring_integration)){
setSpringAware(processor, ctxt);
}
else{
setWebAware(processor, ctxt);
}
}
/**
* Decorate NutsProcessor object with nut classes that integrates Spring framework
* and also aware of the web infrastructure, i.e. ServletContext.
* @param processor the NutsProcessor object to decorate.
* @param ctxt the ServletContext object.
* @throws Exception any error.
*/
protected void setSpringAware(NutsProcessor processor, ServletContext ctxt)
throws Exception{
SpringNuts.setSpringAware("yan_web_spring", processor, DEFAULT_SPRING_NUTS_FILE);
}
/**
* Decorate NutsProcessor object with nut classes that are aware of the web infrastructure, i.e. ServletContext.
* @param processor the NutsProcessor object to decorate.
* @param ctxt the ServletContext object.
* @throws Exception any error.
*/
protected void setWebAware(NutsProcessor processor, ServletContext ctxt)
throws Exception{
processor.loadNutsMetaResource(DEFAULT_NUTS_FILE);
}
/**
* Destroy the container.
* @param servletContext the servlet context.
*/
public synchronized void destroy(ServletContext servletContext){
if(lifecycle_manager==null) return;
servletContext.log("Closing Yan root container");
try{
lifecycle_manager.dispose();
this.lifecycle_manager = null;
this.yan = null;
this.processor = null;
}
catch(Error e){
servletContext.log("error in container shutdown", e);
throw e;
}
catch(Throwable e){
servletContext.log("error in container shutdown", e);
}
}
}