001: package org.directwebremoting.fluent;
002:
003: import java.util.ArrayList;
004: import java.util.Collections;
005: import java.util.HashMap;
006: import java.util.List;
007: import java.util.Map;
008:
009: import org.apache.commons.logging.LogFactory;
010: import org.apache.commons.logging.Log;
011: import org.directwebremoting.AjaxFilter;
012: import org.directwebremoting.Container;
013: import org.directwebremoting.extend.AccessControl;
014: import org.directwebremoting.extend.AjaxFilterManager;
015: import org.directwebremoting.extend.Configurator;
016: import org.directwebremoting.extend.Converter;
017: import org.directwebremoting.extend.ConverterManager;
018: import org.directwebremoting.extend.Creator;
019: import org.directwebremoting.extend.CreatorManager;
020: import org.directwebremoting.impl.SignatureParser;
021: import org.directwebremoting.util.LocalUtil;
022:
023: /**
024: * A {@link Configurator} that used the FluentInterface style as
025: * <a href="http://www.martinfowler.com/bliki/FluentInterface.html">described by
026: * Martin Fowler</a>.
027: *
028: * <p>To wire up the configuration programmatically rather than having to use
029: * <code>dwr.xml</code>. In order to use this style, you'll need to:</p>
030: *
031: * <ul>
032: * <li>Create a concrete implementation of {@link FluentConfigurator} which
033: * implements the {@link #configure()} method.</li>
034: * <li>Add an init param '<code>customConfigurator</code>' to the DWR servlet in
035: * <code>web.xml</code> to point at your new class.</li>
036: * </ul>
037: *
038: * <p>The implementation of {@link #configure()} will look something like
039: * this:</p>
040: *
041: * <pre>
042: * public void configure() {
043: * withConverterType("dog", "com.yourcompany.beans.Dog");
044: * withCreatorType("ejb", "com.yourcompany.dwr.creator.EJBCreator");
045: * withCreator("new", "ApartmentDAO")
046: * .addParam("scope", "session")
047: * .addParam("class", "com.yourcompany.dao.ApartmentDAO")
048: * .exclude("saveApartment")
049: * .withAuth("method", "role");
050: * withCreator("struts", "DogDAO")
051: * .addParam("clas", "com.yourcompany.dao.DogDAO")
052: * .include("getDog")
053: * .include("getColor");
054: * withConverter("dog", "*.Dog")
055: * .addParam("name", "value");
056: * withSignature()
057: * .addLine("import java.util.List;")
058: * .addLine("import com.example.Check;")
059: * .addLine("Check.setLotteryResults(List<Integer> nos);");
060: * }
061: * </pre>
062: * @author Aaron Johnson [ajohnson at cephas dot net / <a href="http://cephas.net/blog">http://cephas.net/blog</a>]
063: * @author Joe Walker [joe at getahead dot ltd dot uk]
064: */
065: public abstract class FluentConfigurator implements Configurator {
066: /**
067: * This method is used to configure DWR using the fluent style.
068: */
069: public abstract void configure();
070:
071: /**
072: * Add a new {@link Converter} definition.
073: * @param id The id referred to by the {@link #withConverter(String, String)}
074: * @param converterClassName The implementation of {@link Converter} to instantiate.
075: * @return <code>this</code> to continue the fluency
076: */
077: public FluentConfigurator withConverterType(String id,
078: String converterClassName) {
079: setState(STATE_INIT_CONVERT);
080: converterManager.addConverterType(id, converterClassName);
081: return this ;
082: }
083:
084: /**
085: * Use a {@link Converter} to instantiate a class
086: * @param newConverter A predefined {@link Converter} or one defined by
087: * {@link #withConverterType(String, String)}.
088: * @param newMatch The javascript name of this component
089: * @return <code>this</code> to continue the fluency
090: */
091: public FluentConfigurator withConverter(String newConverter,
092: String newMatch) {
093: setState(STATE_ALLOW_CONVERT);
094: this .converter = newConverter;
095: this .match = newMatch;
096: return this ;
097: }
098:
099: /**
100: * Add a new {@link Creator} definition.
101: * @param id The id referred to by the {@link #withCreator(String, String)}
102: * @param creatorClassName The implementation of {@link Creator} to instantiate.
103: * @return <code>this</code> to continue the fluency
104: */
105: public FluentConfigurator withCreatorType(String id,
106: String creatorClassName) {
107: setState(STATE_INIT_CREATE);
108: creatorManager.addCreatorType(id, creatorClassName);
109: return this ;
110: }
111:
112: /**
113: * Use a {@link Creator} to instantiate a class
114: * @param newTypeName A predefined {@link Creator} or one defined by
115: * {@link #withCreatorType(String, String)}.
116: * @param newScriptName The javascript name of this component
117: * @return <code>this</code> to continue the fluency
118: */
119: public FluentConfigurator withCreator(String newTypeName,
120: String newScriptName) {
121: setState(STATE_ALLOW_CREATE);
122: this .typeName = newTypeName;
123: this .scriptName = newScriptName;
124: return this ;
125: }
126:
127: /**
128: * @param newFilterClassName filter class name
129: * @return <code>this</code> to continue the fluency
130: */
131: public FluentConfigurator withFilter(String newFilterClassName) {
132: setState(STATE_ALLOW_FILTER);
133: this .filterClassName = newFilterClassName;
134: return this ;
135: }
136:
137: /**
138: * Add a parameter to whatever is being configured.
139: * @param name The name of the parameter
140: * @param value The value of the parameter
141: * @return <code>this</code> to continue the fluency
142: */
143: public FluentConfigurator addParam(String name, String value) {
144: if (params == null) {
145: params = new HashMap<String, String>();
146: }
147:
148: params.put(name, value);
149: return this ;
150: }
151:
152: /**
153: * Add a filter to whatever is being configured.
154: * @param newFilterClassName The class to add as a filter
155: * @return <code>this</code> to continue the fluency
156: */
157: public FluentConfigurator addFilter(String newFilterClassName) {
158: if (filters == null) {
159: filters = new ArrayList<String>();
160: }
161:
162: filters.add(newFilterClassName);
163: return this ;
164: }
165:
166: /**
167: * Add an include rule to a {@link Creator}.
168: * This should be used during a {@link #withCreator(String, String)} call.
169: * @param methodName The method name to be allowed
170: * @return <code>this</code> to continue the fluency
171: */
172: public FluentConfigurator include(String methodName) {
173: accessControl.addIncludeRule(scriptName, methodName);
174: return this ;
175: }
176:
177: /**
178: * Add an exclude rule to a {@link Creator}
179: * This should be used during a {@link #withCreator(String, String)} call.
180: * @param methodName The method name to be disallowed
181: * @return <code>this</code> to continue the fluency
182: */
183: public FluentConfigurator exclude(String methodName) {
184: accessControl.addExcludeRule(scriptName, methodName);
185: return this ;
186: }
187:
188: /**
189: * Add an authorization rule to a {@link Creator}
190: * This should be used during a {@link #withCreator(String, String)} call.
191: * @param methodName The method name to have a required role
192: * @param role The required role for the given method
193: * @return <code>this</code> to continue the fluency
194: */
195: public FluentConfigurator withAuth(String methodName, String role) {
196: accessControl.addRoleRestriction(scriptName, methodName, role);
197: return this ;
198: }
199:
200: /**
201: * Add lines to a signature.
202: * @return <code>this</code> to continue the fluency
203: */
204: public FluentConfigurator withSignature() {
205: setState(STATE_SIGNATURE);
206: return this ;
207: }
208:
209: /**
210: * Add lines to a signature.
211: * @param line The line of text to add to the signature configuration
212: * @return <code>this</code> to continue the fluency
213: */
214: public FluentConfigurator addLine(String line) {
215: if (null == line) {
216: return this ;
217: }
218:
219: if (null == signature) {
220: signature = new StringBuffer();
221: }
222:
223: signature.append(line);
224: signature.append(System.getProperty("line.separator"));
225:
226: return this ;
227: }
228:
229: /**
230: * Because some parts of the configuration require multiple steps, the instance
231: * needs to maintain a state across invocations. Whenever the state is changed
232: * by calling this method, the instance will 'flush' anything in the queue
233: * applicable to that state EVEN IF the state itself doesn't change. Thus, it's
234: * important that the child methods don't call setState() when being invoked.
235: * @param state The new state. See the STATE_* constants.
236: */
237: private void setState(int state) {
238: flush();
239: this .state = state;
240: }
241:
242: /**
243: * Takes and configuration that is in progress and calls methods on the
244: * various objects to enable that configuration.
245: */
246: private void flush() {
247: switch (state) {
248: case STATE_INIT_CONVERT:
249: // do nothing;
250: break;
251:
252: case STATE_INIT_CREATE:
253: // do nothing;
254: break;
255:
256: case STATE_ALLOW_CONVERT:
257: try {
258: if (params == null) {
259: converterManager.addConverter(match, converter,
260: EMPTY_MAP);
261: } else {
262: converterManager.addConverter(match, converter,
263: params);
264: }
265: } catch (Exception e) {
266: log.warn("Failed to add converter of type='"
267: + converter + "', match=" + match + ": ", e);
268: }
269: params = null;
270: match = null;
271: converter = null;
272: break;
273:
274: case STATE_ALLOW_CREATE:
275: try {
276: if (params == null) {
277: creatorManager.addCreator(scriptName, typeName,
278: EMPTY_MAP);
279: } else {
280: creatorManager.addCreator(scriptName, typeName,
281: params);
282: }
283:
284: if (filters != null) {
285: for (String className : filters) {
286: AjaxFilter filter = LocalUtil
287: .classNewInstance(scriptName,
288: className, AjaxFilter.class);
289:
290: if (filter != null) {
291: LocalUtil.setParams(filter, Collections
292: .<String, Object> emptyMap(),
293: Collections.<String> emptyList());
294: ajaxFilterManager.addAjaxFilter(filter,
295: scriptName);
296: }
297:
298: }
299: }
300: } catch (Exception e) {
301: log.warn("Failed to add creator of type='" + typeName
302: + "', scriptName=" + scriptName + ": ", e);
303: }
304: params = null;
305: scriptName = null;
306: typeName = null;
307: filters = null;
308: break;
309:
310: case STATE_ALLOW_FILTER:
311: try {
312: Class<?> impl = LocalUtil.classForName(filterClassName);
313: AjaxFilter object = (AjaxFilter) impl.newInstance();
314:
315: if (params != null) {
316: LocalUtil.setParams(object, params, Collections
317: .<String> emptyList());
318: }
319:
320: ajaxFilterManager.addAjaxFilter(object);
321: } catch (ClassCastException ex) {
322: log.error(filterClassName + " does not implement "
323: + AjaxFilter.class.getName(), ex);
324: } catch (NoClassDefFoundError ex) {
325: log.info("Missing class for filter (class='"
326: + filterClassName + "'). Cause: "
327: + ex.getMessage());
328: } catch (Exception ex) {
329: log.error("Failed to add filter: class="
330: + filterClassName, ex);
331: }
332:
333: params = null;
334: filterClassName = null;
335:
336: break;
337:
338: case STATE_SIGNATURE:
339: if (signature != null && signature.length() > 0) {
340: SignatureParser sigp = new SignatureParser(
341: converterManager, creatorManager);
342: sigp.parse(signature.toString());
343: }
344: break;
345:
346: default:
347: break;
348: }
349: }
350:
351: /* (non-Javadoc)
352: * @see org.directwebremoting.Configurator#configure(org.directwebremoting.Container)
353: */
354: public void configure(Container container) {
355: converterManager = container.getBean(ConverterManager.class);
356: ajaxFilterManager = container.getBean(AjaxFilterManager.class);
357: accessControl = container.getBean(AccessControl.class);
358: creatorManager = container.getBean(CreatorManager.class);
359:
360: configure();
361:
362: setState(STATE_COMPLETE);
363: }
364:
365: /**
366: * Used for <allow create .../>
367: */
368: private String typeName = null;
369:
370: /**
371: * Used for <allow create .../>
372: */
373: private String scriptName = null;
374:
375: /**
376: * Used for <allow filter .../>
377: */
378: private String filterClassName = null;
379:
380: /**
381: * Used for <allow convert .../>
382: */
383: private String converter = null;
384:
385: /**
386: * Used for <allow convert .../>
387: */
388: private String match = null;
389:
390: /**
391: * holds name / value pairs used in <allow create|convert ... />
392: */
393: private Map<String, String> params = null;
394:
395: /**
396: * holds classNames of filters used in <allow create/ filter />
397: */
398: private List<String> filters = null;
399:
400: /**
401: * holds signature lines
402: */
403: private StringBuffer signature = null;
404:
405: /**
406: * What section of a configuration are we in?
407: */
408: private int state = -1;
409:
410: /**
411: * JDK5: we can convert this to Collections.emptyMap();
412: */
413: private static final Map<String, String> EMPTY_MAP = Collections
414: .unmodifiableMap(new HashMap<String, String>());
415:
416: /**
417: * What AjaxFilters apply to which Ajax calls?
418: */
419: private AjaxFilterManager ajaxFilterManager = null;
420:
421: /**
422: * The ConverterManager that we are configuring
423: */
424: private ConverterManager converterManager = null;
425:
426: /**
427: * The AccessControl that we are configuring
428: */
429: private AccessControl accessControl = null;
430:
431: /**
432: * The CreatorManager that we are configuring
433: */
434: private CreatorManager creatorManager = null;
435:
436: /**
437: * {@link #state} to say we are working in {@link #withCreatorType(String, String)}
438: */
439: private static final int STATE_INIT_CREATE = 0;
440:
441: /**
442: * {@link #state} to say we are working in {@link #withConverterType(String, String)}
443: */
444: private static final int STATE_INIT_CONVERT = 1;
445:
446: /**
447: * {@link #state} to say we are working in {@link #withCreator(String, String)}
448: */
449: private static final int STATE_ALLOW_CREATE = 2;
450:
451: /**
452: * {@link #state} to say we are working in {@link #withFilter(String)}
453: */
454: private static final int STATE_ALLOW_FILTER = 3;
455:
456: /**
457: * {@link #state} to say we are working in {@link #withConverter(String, String)}
458: */
459: private static final int STATE_ALLOW_CONVERT = 4;
460:
461: /**
462: * {@link #state} to say we are working in {@link #withSignature()}
463: */
464: private static final int STATE_SIGNATURE = 5;
465:
466: /**
467: * {@link #state} to say {@link #configure()} has completed
468: */
469: private static final int STATE_COMPLETE = 6;
470:
471: /**
472: * The log stream
473: */
474: private static final Log log = LogFactory
475: .getLog(FluentConfigurator.class);
476: }
|