package org.directwebremoting.fluent; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.Log; import org.directwebremoting.AjaxFilter; import org.directwebremoting.Container; import org.directwebremoting.extend.AccessControl; import org.directwebremoting.extend.AjaxFilterManager; import org.directwebremoting.extend.Configurator; import org.directwebremoting.extend.Converter; import org.directwebremoting.extend.ConverterManager; import org.directwebremoting.extend.Creator; import org.directwebremoting.extend.CreatorManager; import org.directwebremoting.impl.SignatureParser; import org.directwebremoting.util.LocalUtil; /** * A {@link Configurator} that used the FluentInterface style as * <a href="http://www.martinfowler.com/bliki/FluentInterface.html">described by * Martin Fowler</a>. * * <p>To wire up the configuration programmatically rather than having to use * <code>dwr.xml</code>. In order to use this style, you'll need to:</p> * * <ul> * <li>Create a concrete implementation of {@link FluentConfigurator} which * implements the {@link #configure()} method.</li> * <li>Add an init param '<code>customConfigurator</code>' to the DWR servlet in * <code>web.xml</code> to point at your new class.</li> * </ul> * * <p>The implementation of {@link #configure()} will look something like * this:</p> * * <pre> * public void configure() { * withConverterType("dog", "com.yourcompany.beans.Dog"); * withCreatorType("ejb", "com.yourcompany.dwr.creator.EJBCreator"); * withCreator("new", "ApartmentDAO") * .addParam("scope", "session") * .addParam("class", "com.yourcompany.dao.ApartmentDAO") * .exclude("saveApartment") * .withAuth("method", "role"); * withCreator("struts", "DogDAO") * .addParam("clas", "com.yourcompany.dao.DogDAO") * .include("getDog") * .include("getColor"); * withConverter("dog", "*.Dog") * .addParam("name", "value"); * withSignature() * .addLine("import java.util.List;") * .addLine("import com.example.Check;") * .addLine("Check.setLotteryResults(List<Integer> nos);"); * } * </pre> * @author Aaron Johnson [ajohnson at cephas dot net / <a href="http://cephas.net/blog">http://cephas.net/blog</a>] * @author Joe Walker [joe at getahead dot ltd dot uk] */ public abstract class FluentConfigurator implements Configurator { /** * This method is used to configure DWR using the fluent style. */ public abstract void configure(); /** * Add a new {@link Converter} definition. * @param id The id referred to by the {@link #withConverter(String, String)} * @param converterClassName The implementation of {@link Converter} to instantiate. * @return <code>this</code> to continue the fluency */ public FluentConfigurator withConverterType(String id, String converterClassName) { setState(STATE_INIT_CONVERT); converterManager.addConverterType(id, converterClassName); return this; } /** * Use a {@link Converter} to instantiate a class * @param newConverter A predefined {@link Converter} or one defined by * {@link #withConverterType(String, String)}. * @param newMatch The javascript name of this component * @return <code>this</code> to continue the fluency */ public FluentConfigurator withConverter(String newConverter, String newMatch) { setState(STATE_ALLOW_CONVERT); this.converter = newConverter; this.match = newMatch; return this; } /** * Add a new {@link Creator} definition. * @param id The id referred to by the {@link #withCreator(String, String)} * @param creatorClassName The implementation of {@link Creator} to instantiate. * @return <code>this</code> to continue the fluency */ public FluentConfigurator withCreatorType(String id, String creatorClassName) { setState(STATE_INIT_CREATE); creatorManager.addCreatorType(id, creatorClassName); return this; } /** * Use a {@link Creator} to instantiate a class * @param newTypeName A predefined {@link Creator} or one defined by * {@link #withCreatorType(String, String)}. * @param newScriptName The javascript name of this component * @return <code>this</code> to continue the fluency */ public FluentConfigurator withCreator(String newTypeName, String newScriptName) { setState(STATE_ALLOW_CREATE); this.typeName = newTypeName; this.scriptName = newScriptName; return this; } /** * @param newFilterClassName filter class name * @return <code>this</code> to continue the fluency */ public FluentConfigurator withFilter(String newFilterClassName) { setState(STATE_ALLOW_FILTER); this.filterClassName = newFilterClassName; return this; } /** * Add a parameter to whatever is being configured. * @param name The name of the parameter * @param value The value of the parameter * @return <code>this</code> to continue the fluency */ public FluentConfigurator addParam(String name, String value) { if (params == null) { params = new HashMap<String, String>(); } params.put(name, value); return this; } /** * Add a filter to whatever is being configured. * @param newFilterClassName The class to add as a filter * @return <code>this</code> to continue the fluency */ public FluentConfigurator addFilter(String newFilterClassName) { if (filters == null) { filters = new ArrayList<String>(); } filters.add(newFilterClassName); return this; } /** * Add an include rule to a {@link Creator}. * This should be used during a {@link #withCreator(String, String)} call. * @param methodName The method name to be allowed * @return <code>this</code> to continue the fluency */ public FluentConfigurator include(String methodName) { accessControl.addIncludeRule(scriptName, methodName); return this; } /** * Add an exclude rule to a {@link Creator} * This should be used during a {@link #withCreator(String, String)} call. * @param methodName The method name to be disallowed * @return <code>this</code> to continue the fluency */ public FluentConfigurator exclude(String methodName) { accessControl.addExcludeRule(scriptName, methodName); return this; } /** * Add an authorization rule to a {@link Creator} * This should be used during a {@link #withCreator(String, String)} call. * @param methodName The method name to have a required role * @param role The required role for the given method * @return <code>this</code> to continue the fluency */ public FluentConfigurator withAuth(String methodName, String role) { accessControl.addRoleRestriction(scriptName, methodName, role); return this; } /** * Add lines to a signature. * @return <code>this</code> to continue the fluency */ public FluentConfigurator withSignature() { setState(STATE_SIGNATURE); return this; } /** * Add lines to a signature. * @param line The line of text to add to the signature configuration * @return <code>this</code> to continue the fluency */ public FluentConfigurator addLine(String line) { if (null == line) { return this; } if (null == signature) { signature = new StringBuffer(); } signature.append(line); signature.append(System.getProperty("line.separator")); return this; } /** * Because some parts of the configuration require multiple steps, the instance * needs to maintain a state across invocations. Whenever the state is changed * by calling this method, the instance will 'flush' anything in the queue * applicable to that state EVEN IF the state itself doesn't change. Thus, it's * important that the child methods don't call setState() when being invoked. * @param state The new state. See the STATE_* constants. */ private void setState(int state) { flush(); this.state = state; } /** * Takes and configuration that is in progress and calls methods on the * various objects to enable that configuration. */ private void flush() { switch (state) { case STATE_INIT_CONVERT: // do nothing; break; case STATE_INIT_CREATE: // do nothing; break; case STATE_ALLOW_CONVERT: try { if (params == null) { converterManager.addConverter(match, converter, EMPTY_MAP); } else { converterManager.addConverter(match, converter, params); } } catch (Exception e) { log.warn("Failed to add converter of type='" + converter + "', match=" + match + ": ", e); } params = null; match = null; converter = null; break; case STATE_ALLOW_CREATE: try { if (params == null) { creatorManager.addCreator(scriptName, typeName, EMPTY_MAP); } else { creatorManager.addCreator(scriptName, typeName, params); } if (filters != null) { for (String className : filters) { AjaxFilter filter = LocalUtil.classNewInstance(scriptName, className, AjaxFilter.class); if (filter != null) { LocalUtil.setParams(filter, Collections.<String, Object>emptyMap(), Collections.<String>emptyList()); ajaxFilterManager.addAjaxFilter(filter, scriptName); } } } } catch (Exception e) { log.warn("Failed to add creator of type='" + typeName + "', scriptName=" + scriptName + ": ", e); } params = null; scriptName = null; typeName = null; filters = null; break; case STATE_ALLOW_FILTER: try { Class<?> impl = LocalUtil.classForName(filterClassName); AjaxFilter object = (AjaxFilter) impl.newInstance(); if (params != null) { LocalUtil.setParams(object, params, Collections.<String>emptyList()); } ajaxFilterManager.addAjaxFilter(object); } catch (ClassCastException ex) { log.error(filterClassName + " does not implement " + AjaxFilter.class.getName(), ex); } catch (NoClassDefFoundError ex) { log.info("Missing class for filter (class='" + filterClassName + "'). Cause: " + ex.getMessage()); } catch (Exception ex) { log.error("Failed to add filter: class=" + filterClassName, ex); } params = null; filterClassName = null; break; case STATE_SIGNATURE: if (signature != null && signature.length() > 0) { SignatureParser sigp = new SignatureParser(converterManager, creatorManager); sigp.parse(signature.toString()); } break; default: break; } } /* (non-Javadoc) * @see org.directwebremoting.Configurator#configure(org.directwebremoting.Container) */ public void configure(Container container) { converterManager = container.getBean(ConverterManager.class); ajaxFilterManager = container.getBean(AjaxFilterManager.class); accessControl = container.getBean(AccessControl.class); creatorManager = container.getBean(CreatorManager.class); configure(); setState(STATE_COMPLETE); } /** * Used for <allow create .../> */ private String typeName = null; /** * Used for <allow create .../> */ private String scriptName = null; /** * Used for <allow filter .../> */ private String filterClassName = null; /** * Used for <allow convert .../> */ private String converter = null; /** * Used for <allow convert .../> */ private String match = null; /** * holds name / value pairs used in <allow create|convert ... /> */ private Map<String, String> params = null; /** * holds classNames of filters used in <allow create/ filter /> */ private List<String> filters = null; /** * holds signature lines */ private StringBuffer signature = null; /** * What section of a configuration are we in? */ private int state = -1; /** * JDK5: we can convert this to Collections.emptyMap(); */ private static final Map<String, String> EMPTY_MAP = Collections.unmodifiableMap(new HashMap<String, String>()); /** * What AjaxFilters apply to which Ajax calls? */ private AjaxFilterManager ajaxFilterManager = null; /** * The ConverterManager that we are configuring */ private ConverterManager converterManager = null; /** * The AccessControl that we are configuring */ private AccessControl accessControl = null; /** * The CreatorManager that we are configuring */ private CreatorManager creatorManager = null; /** * {@link #state} to say we are working in {@link #withCreatorType(String, String)} */ private static final int STATE_INIT_CREATE = 0; /** * {@link #state} to say we are working in {@link #withConverterType(String, String)} */ private static final int STATE_INIT_CONVERT = 1; /** * {@link #state} to say we are working in {@link #withCreator(String, String)} */ private static final int STATE_ALLOW_CREATE = 2; /** * {@link #state} to say we are working in {@link #withFilter(String)} */ private static final int STATE_ALLOW_FILTER = 3; /** * {@link #state} to say we are working in {@link #withConverter(String, String)} */ private static final int STATE_ALLOW_CONVERT = 4; /** * {@link #state} to say we are working in {@link #withSignature()} */ private static final int STATE_SIGNATURE = 5; /** * {@link #state} to say {@link #configure()} has completed */ private static final int STATE_COMPLETE = 6; /** * The log stream */ private static final Log log = LogFactory.getLog(FluentConfigurator.class); }