Echo and Spring

I have done a bit of Echo and Spring integration on this weekend and came up with the following two class, ContextLoaderEchoServer and EchoInstanceSupport that *knows* about Spring's WebApplicationContext. Hope this might comes in handy to others. If there are any better ways of doing it, kindly shared it (by posting through this thread). Thanks

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.context.ApplicationContextException;
import org.springframework.util.StringUtils;
import org.springframework.web.context.ConfigurableWebApplicationContext;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.context.support.XmlWebApplicationContext;

import nextapp.echo.EchoInstance;
import nextapp.echoservlet.EchoServer;

/**
 * <pre>
 *  < servlet >
 *      < servlet-name > myEchoServer < /servlet-name >
 *      < servlet-class > org.huihoo.jfox.cms.echo.ContextLoaderEchoServer < /servlet-class >  
 *      < init-param > 
 *          < param-name > NAMESPACE_PARAM < /param-name > 
 *          < param-value > myEchoServer-servlet < /param-value > 
 *      < /init-param > 
 *      < init-param > 
 *          < param-name > CONTEXT_CONFIG_LOCATION_PARAM < /param-name > 
 *          < param-value > /WEB-INF/config1.xml,/WEB-INF/config2.xml,/WEB-INF/config3.xml < /param-value > 
 *      < /init-param > 
 *      < init-param > 
 *          < param-name > SERVLET_CONTEXT_PARAM < /param-name > 
 *          < param-value > myEchoServer-servlet-context < /param-value > 
 *      < /init-param > 
 *      < init-param > 
 *          < param-name > CONTEXT_CLASS_PARAM < /param-name > 
 *          < param-value > org.springframework.web.context.support.XmlWebApplicationContext < /param-value > 
 *      < /init-param > 
 *  < /servlet > 
 * </pre>
 * 
 * OR
 * 
 * <pre>
 * <servlet>
 *      <servlet-name>myEchoServer</servlet-name>
 *      <servlet-class>org.huihoo.jfox.cms.echo.ContextLoaderEchoServer</servlet-class>
 * </servlet>
 * </pre>
 * 
 * 
 * @author <a href="mailto:tm_jee@yahoo.co.uk">tmjee</a>
 * @version $Date$ $Revision$
 * @since 1.0
 */
public abstract class ContextLoaderEchoServer extends EchoServer {

    /**
     * Suffix for WebApplicationContext namespaces. If this 
     * {@link ContextLoaderEchoServer} is given the name "echoserver" in a context, 
     * the namespace used by this {@link ContextLoaderEchoServer} will
     * resolve to "echoserver-servlet".
     */
	public static final String DEFAULT_NAMESPACE_SUFFIX = "-servlet";
    
    /**
     * The default context class for ContextLoaderPlugIn (XmlWebApplicationContext).
     * @see org.springframework.web.context.support.XmlWebApplicationContext
     */
	public static final Class DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class;
    
    /**
     * Prefix for the ServletContext attribute for the WebApplicationContext.
     * The completion is the this class {@link ContextLoaderEchoServer} plus ".CONTEXT".
     */
	public static final String DEFAULT_SERVLET_CONTEXT_PREFIX = ContextLoaderEchoServer.class.getName()+".CONTEXT";

	/**
     * Servlet Context Param to set the WebApplicationContext's namespace, 
     * default to 'NAMESPACE_PARAM'.
  */
    public static final String NAMESPACE_PARAM = "NAMESPACE_PARAM";
    /**
     * Servlet Context Param to set the WebApplicationContext's config location, 
     * default to 'CONTEXT_CONFIG_LOCATION_PARAM'.
     */
    public static final String CONTEXT_CONFIG_LOCATION_PARAM = "CONTEXT_CONFIG_LOCATION_PARAM";
    /**
     * Servlet Context Param to set the WebApplicationContext's servlet context param,
     * default to 'SERVLET_CONTEXT_PARAM'.
     */
    public static final String SERVLET_CONTEXT_PARAM = "SERVLET_CONTEXT_PARAM";
    /**
     * Servlet Context Param to set the WebApplicationContext's Context Class, default
     * to 'CONTEXT_CLASS_PARAM'
     */
    public static final String CONTEXT_CLASS_PARAM = "CONTEXT_CLASS_PARAM";
    
    
    private static final Log log = LogFactory.getLog(ContextLoaderEchoServer.class);
    
    private String _namespace = null;
    private String _contextConfigLocation = null;
    private String _servletContextParam = null;
    private Class _contextClass = null;
    private WebApplicationContext _webApplicationContext = null;
    
    private EchoInstanceSupport _echoInstanceSupport = null;
    private ServletConfig _servletConfig = null;
    private ServletContext _servletContext = null;
    
    /**
     * Initialize the WebApplicationContext.
     */
    public void init(ServletConfig servletConfig) throws ServletException {
        log.info("start initializing "+getClass().getName());
        long startTime = System.currentTimeMillis();
        
        super.init(servletConfig);
    	initWebApplicationContext(servletConfig);
        
        long doneTime = System.currentTimeMillis();
        long duration = doneTime - startTime;
        log.info("done initalizing "+getClass().getName()+" in "+duration+" ms");
    }
    
    
    
	/**
  * @see nextapp.echoservlet.EchoServer#newInstance()
  */
	public EchoInstance newInstance() {
  _echoInstanceSupport =  newEchoInstanceSupport(_webApplicationContext);
        return _echoInstanceSupport;
	}
    
    /**
     * To be implmented by subclass the same way as {@link EchoServer#newInstance()}
     * except that now it has a {@link WebApplicationContext}. Subclass should 
     * return an instance of {@link EchoInstanceSupport} which has a single constructor
     * that takes in a {@link WebApplicationContext}.
     * 
     * @param webApplicationContext
     * @return EchoInstanceSupport
     */
    public abstract EchoInstanceSupport newEchoInstanceSupport(
            WebApplicationContext webApplicationContext);
    
    
    
    // protected ==========================================
    protected void initWebApplicationContext(ServletConfig servletConfig) {
        _servletContext = servletConfig.getServletContext();
        _servletConfig = servletConfig;
        
        WebApplicationContext rootWebApplicationContext = 
            WebApplicationContextUtils.getRequiredWebApplicationContext(_servletContext);
        
        if (getContextConfigLocation() == null) {
        	// no child context config location specified, use the root webApplicationContext
            _webApplicationContext = rootWebApplicationContext;
        }
        else {
            _webApplicationContext = createWebApplicationContext(rootWebApplicationContext);
            
            // store Context in ServletContext
            _servletContext.setAttribute(getServletContextParam(), _webApplicationContext);
        }
    }
    
    protected ConfigurableWebApplicationContext createWebApplicationContext(
            WebApplicationContext rootWebApplicationContext) {
         try {
            Class contextClazz = getContextClass();
            if (! ConfigurableWebApplicationContext.class.isAssignableFrom(contextClazz)) {
                throw new ApplicationContextException(
                        "Fatal initialization error in "+getClass().getName()+" '" + getServletName() +
                        "' : custom WebApplicationContext class [" + contextClazz+
                        "] is not of type ConfigurableWebApplicationContext");
            }
            ConfigurableWebApplicationContext cwac = 
                (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClazz);
            
            // init cwac
            cwac.setParent(rootWebApplicationContext);
            cwac.setNamespace(getNamespace());
            cwac.setServletContext(_servletConfig.getServletContext());
            cwac.setConfigLocations(StringUtils.tokenizeToStringArray(
                    getContextConfigLocation(), 
                    ConfigurableWebApplicationContext.CONFIG_LOCATION_DELIMITERS, 
                    true, true));
            cwac.refresh();
            return cwac;
        }
        catch(ClassNotFoundException e) {
            log.error("Exception while initailizing web application context ", e);
            throw new ApplicationContextException("Exception while initailizing web application context ", e);
        }
    }
    
    
    // private ===========================================
    private String getNamespace() {
        if (_namespace == null) {
        	// first time
        	String param = _servletConfig.getInitParameter(NAMESPACE_PARAM);
        	if (param == null || param.trim().length() <= 0) {
          _namespace = _servletConfig.getServletName()+DEFAULT_NAMESPACE_SUFFIX;
        	}
            else {
            	_namespace = param;
            }
        }
    	return _namespace;
    }
    
    private String getContextConfigLocation() {
        if (_contextConfigLocation == null) {
            String param = _servletConfig.getInitParameter(CONTEXT_CONFIG_LOCATION_PARAM);
            if (param != null && param.trim().length() > 0) {
            	_contextConfigLocation = param;
            }
        }
    	return _contextConfigLocation;
    }
    
    private Class getContextClass() throws ClassNotFoundException {
    	if (_contextClass == null) { // first time
            String param = _servletConfig.getInitParameter(CONTEXT_CLASS_PARAM);
            if (param == null || param.trim().length() <= 0) {
            	_contextClass = DEFAULT_CONTEXT_CLASS;
            }
            else {
                _contextClass = Class.forName(param, true, Thread.currentThread().getContextClassLoader());
            }
        }
        return _contextClass;
    }
    
    private String getServletContextParam() {
    	if (_servletContextParam == null) {
      // first time
            String param = _servletConfig.getInitParameter(SERVLET_CONTEXT_PARAM);
            if (param == null || param.trim().length() <= 0) {
                _servletContextParam = DEFAULT_SERVLET_CONTEXT_PREFIX+ _echoInstanceSupport.getClass().getName();
            }
            else {
            	_servletContextParam = param;
            }
        }
        return _servletContextParam;
    }
}

import org.springframework.web.context.WebApplicationContext;

import nextapp.echo.EchoInstance;

/**
 * @author <a href="mailto:tm_jee@yahoo.co.uk">tmjee</a>
 * @version $Date$ $Revision$
 * @since 1.0
 */
public abstract class EchoInstanceSupport extends EchoInstance {

    private WebApplicationContext _webApplicationContext;
    
    public EchoInstanceSupport(WebApplicationContext webApplicationContext) {
    	_webApplicationContext = webApplicationContext;
    }
    
	protected WebApplicationContext getWebApplicationContext() {
  return _webApplicationContext;
    }
}

regards
tmjee

I'm not a spring expert by *any* means, but this is what I did to get access to the WebApplicationContext:

Inside my web.xml file:

	<listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>

And from within my Echo application startup servlet I have a class, called Application that extends the EchoInstance class. Within the init() method I get a reference to the Spring WebApplicationContext and store it in a field and create a getter/setter.

My Application class instantiates all the Panes, Panels, and other Echo components for my Echo application.

Here's how I get the reference to Spring:

ServerContext serverContext = (ServerContext) this.getAttribute(ServerContext.ATTRIBUTE_NAME);
ServletContext servletContext = serverContext.getServletConfig().getServletContext();

WebApplicationContext springContext = WebApplicationContextUtils.getWebApplicationContext(servletContext);

Then to access a Bean defined in Spring, you just do this:

MySpringDefinedBean myBean = (MySpringDefinedBean) springContext.getBean("myBean");

I use this all over in my code. Using Spring, I define my Hibernate stuff as well so it's all in one tidy package.

HTH

I also found this

I also found this one:
http://jroller.com/fouadhamdi/entry/spring_et_echo2

It's in French but I babelfished it and after all, the code isn't in French.

It shows a different example of mnickel's version.

HTH

I guess that is another way of doing it, thanks for sharing it with us. :-)

I think you encapsulate the <code>getBean(...)</code> in some helper class, so that if your bean name changes in the Spring xml there is one place to look for in changing the bean name.

In the the case above, I normally have a method in EchoInstanceSupport maybe like <code>getBusinessService(...)</code> that does the Spring's <code>getBean(...)</code> stuff so that the subclass needs only know <code>getBusinessService(...)</code> to get the business service. Not sure if this is prefered way to do it though.

Thanks for sharing it. Cheers! :-)

regards
tmjee

Using a facade...

I'm planning on making a facade class that Echo can use which will contain methods to do things with the Spring managed classes, or to get other classes that does things with them. In that scenario the only bean name I need to keep track of will be the name of the facade.

This, however, means that whenever I update the Spring managed application, I'll also need to add methods or classes to the facade, but I still think this is the most clean way of doing it I've come up with so far.

Thanks for the feedback! Your way feels more reusable and I like the idea of creating the Spring support in the EchoInstance layer rather than in my "Application" layer.

A wee bit more reusable.

Thank again!

Mark

tm_jee wrote:

I guess that is another way of doing it, thanks for sharing it with us. :-)

I think you encapsulate the <code>getBean(...)</code> in some helper class, so that if your bean name changes in the Spring xml there is one place to look for in changing the bean name.

In the the case above, I normally have a method in EchoInstanceSupport maybe like <code>getBusinessService(...)</code> that does the Spring's <code>getBean(...)</code> stuff so that the subclass needs only know <code>getBusinessService(...)</code> to get the business service. Not sure if this is prefered way to do it though.

Thanks for sharing it. Cheers! :-)

regards
tmjee

I have successfully used Echo2 in a Spring application and I wanted to share my experience in integrating the two. I am using a bean lookup only once - in my Echo2 servlet, and then everything is done automatically. See how I did it at http://anydoby.com/jblog/article.htm?id=59&lng=en

Hi all, I'm new to both

Hi all,

I'm new to both Echo and Spring, but I need to investigate whether Echo3 and Spring Webflow work together. Does anyone know if this is possible? If so, where can I found some code samples, explanation, documentation, ...?

Thanks!

Joris

Using AspectJ for transparent dependency injection

I use technic describe in following document from IBM to do transparent Spring bean dependency injection or at least be able to instantiate Spring bean as a POJO class :

http://www.ibm.com/developerworks/java/library/j-aopwork13.html

The idea is all my pane requiring to have bean injected are defined as a bean in my applicationContext.xml but I wouldn't to have to use the WebApplicationContext object but still use new operator to instantiate those pane (or any Echo component), AspectJ was the answer for me and everything works very well.

Here is small example :

<bean id="contactPane" class="com.cdsolutionsinfo.contact.ContactPane">
<property name="contactRepository" ref="contactRepository"/>
</bean>

In my code :

@SpringConfigured(name="contactPane")
public class ContactPane extends ContentPane {

private ContactRepository contactRepository;

public ContactPane() {
super();
}

public void setContactRepository(ContactRepository contactRepository) {
this.contactRepository = contactRepository;
}
...
}

Elsewhere in my code where I want to display my contact pane...

ContactPane contactPane = new ContactPane();

etc...