/*
* Copyright 2005 Joe Walker
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.directwebremoting.impl;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.directwebremoting.AjaxFilter;
import org.directwebremoting.AjaxFilterChain;
import org.directwebremoting.WebContext;
import org.directwebremoting.WebContextFactory;
import org.directwebremoting.extend.AccessControl;
import org.directwebremoting.extend.AjaxFilterManager;
import org.directwebremoting.extend.Call;
import org.directwebremoting.extend.Calls;
import org.directwebremoting.extend.Converter;
import org.directwebremoting.extend.ConverterManager;
import org.directwebremoting.extend.Creator;
import org.directwebremoting.extend.CreatorManager;
import org.directwebremoting.extend.EnginePrivate;
import org.directwebremoting.extend.NamedConverter;
import org.directwebremoting.extend.Property;
import org.directwebremoting.extend.Remoter;
import org.directwebremoting.extend.Replies;
import org.directwebremoting.extend.Reply;
import org.directwebremoting.filter.LogAjaxFilter;
import org.directwebremoting.util.Continuation;
import org.directwebremoting.util.JavascriptUtil;
import org.directwebremoting.util.LocalUtil;
/**
* In implementation of Remoter that delegates requests to a set of Modules
* @author Joe Walker [joe at getahead dot ltd dot uk]
* @author Mike Wilson
*/
public class DefaultRemoter implements Remoter
{
/* (non-Javadoc)
* @see org.directwebremoting.Remoter#generateInterfaceScript(java.lang.String, java.lang.String)
*/
public String generateInterfaceScript(String scriptName, boolean includeDto, String contextServletPath) throws SecurityException
{
StringBuilder buffer = new StringBuilder();
buffer.append(EnginePrivate.getEngineInitScript());
if (includeDto)
{
buffer.append(createParameterDefinitions(scriptName));
}
buffer.append(createDojoProvides(scriptName));
buffer.append(createClassDefinition(scriptName));
buffer.append(createPathDefinition(scriptName, contextServletPath));
buffer.append(createMethodDefinitions(scriptName));
return buffer.toString();
}
/* (non-Javadoc)
* @see org.directwebremoting.extend.Remoter#generateDtoScript(java.lang.String)
*/
public String generateDtoScript(String jsClassName) throws SecurityException
{
return createDtoClassDefinition(jsClassName, true);
}
/* (non-Javadoc)
* @see org.directwebremoting.extend.Remoter#generateAllDtoScripts()
*/
public String generateAllDtoScripts() throws SecurityException
{
return createAllDtoClassDefinitions();
}
/**
* Create a class definition string.
* This is similar to {@link EnginePrivate#getEngineInitScript()} except
* that it creates scripts for a specific class not for dwr.engine
* @see EnginePrivate#getEngineInitScript()
* @param scriptName
*/
protected String createDojoProvides(String scriptName)
{
return "if (window['dojo']) dojo.provide('dwr.interface." + scriptName + "');\n\n";
}
/**
* Create a class definition string.
* This is similar to {@link EnginePrivate#getEngineInitScript()} except
* that it creates scripts for a specific class not for dwr.engine
* @see EnginePrivate#getEngineInitScript()
* @param scriptName
*/
protected String createClassDefinition(String scriptName)
{
return "if (typeof this['" + scriptName + "'] == 'undefined') " + scriptName + " = {};\n\n";
}
/**
* Create a _path member to point at DWR
* @param scriptName The class that we are creating a member for
* @param path The default path to the DWR servlet
*/
protected String createPathDefinition(String scriptName, String path)
{
return scriptName + "._path = '" + getPathToDwrServlet(path) + "';\n\n";
}
/* (non-Javadoc)
* @see org.directwebremoting.extend.Remoter#getPathToDwrServlet(java.lang.String)
*/
public String getPathToDwrServlet(String contextServletPath)
{
String actualPath = contextServletPath;
if (overridePath != null)
{
actualPath = overridePath;
}
if (useAbsolutePath)
{
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
StringBuffer absolutePath = new StringBuffer(48);
String scheme = request.getScheme();
int port = request.getServerPort();
absolutePath.append(scheme);
absolutePath.append("://");
absolutePath.append(request.getServerName());
if (port > 0 &&
(("http".equalsIgnoreCase(scheme) && port != 80) ||
("https".equalsIgnoreCase(scheme) && port != 443)))
{
absolutePath.append(':');
absolutePath.append(port);
}
absolutePath.append(request.getContextPath());
absolutePath.append(request.getServletPath());
actualPath = absolutePath.toString();
}
return actualPath;
}
/**
* Create a list of method definitions for the given creator.
* @param fullCreatorName To allow AccessControl to allow/deny requests
*/
protected String createMethodDefinitions(String fullCreatorName)
{
Creator creator = creatorManager.getCreator(fullCreatorName, false);
String scriptName = creator.getJavascript();
StringBuilder buffer = new StringBuilder();
Method[] methods = creator.getType().getMethods();
for (Method method : methods)
{
String methodName = method.getName();
// We don't need to check accessControl.getReasonToNotExecute()
// because the checks are made by the execute() method, but we do
// check if we can display it
try
{
accessControl.assertIsDisplayable(creator, scriptName, method);
}
catch (SecurityException ex)
{
if (!allowImpossibleTests)
{
continue;
}
}
// Is it on the list of banned names
if (JavascriptUtil.isReservedWord(methodName))
{
continue;
}
// Check to see if the creator is reloadable
// If it is, then do not cache the generated Javascript
// See the notes on creator.isCacheable().
String script;
if (!creator.isCacheable())
{
script = getMethodJS(scriptName, method);
}
else
{
String key = scriptName + "." + method.getName();
// For optimal performance we might use the Memoizer pattern
// JCiP#108 however performance isn't a big issue and we are
// prepared to cope with getMethodJS() being run more than once.
script = methodCache.get(key);
if (script == null)
{
script = getMethodJS(scriptName, method);
methodCache.put(key, script);
}
}
buffer.append(script);
}
return buffer.toString();
}
/**
* Output the class definitions for all the converted objects.
* An optimization for this class might be to only generate class
* definitions for classes used as parameters in the class that we are
* currently generating a proxy for.
* <p>Currently the <code>scriptName</code> parameter is not used, we just
* generate the class definitions for all types, however conceptually, it
* should be used.
* @param scriptName The script for which we are generating parameter classes
*/
protected String createParameterDefinitions(String scriptName)
{
return createAllDtoClassDefinitions();
}
/**
* Output the class definitions for all mapped converted classes.
*/
protected String createAllDtoClassDefinitions()
{
StringBuilder buffer = new StringBuilder();
// First output class definitions
for (String match : converterManager.getConverterMatchStrings())
{
Converter conv = converterManager.getConverterByMatchString(match);
// We will only generate JavaScript classes for compound objects/beans
if (conv instanceof NamedConverter)
{
NamedConverter namedConv = (NamedConverter) conv;
String jsClassName = namedConv.getJavascript();
// We need a configured JavaScript class name
if (LocalUtil.hasLength(jsClassName))
{
buffer.append(createDtoClassDefinition(namedConv));
}
}
}
// Then output superclass definitions
for (String match : converterManager.getConverterMatchStrings())
{
Converter conv = converterManager.getConverterByMatchString(match);
// We will only generate JavaScript classes for compound objects/beans
if (conv instanceof NamedConverter)
{
NamedConverter namedConv = (NamedConverter) conv;
String jsClassName = namedConv.getJavascript();
// We need a configured JavaScript class name
if (LocalUtil.hasLength(jsClassName))
{
buffer.append(createDtoSuperClassDefinition(namedConv));
}
}
}
return buffer.toString();
}
/**
* Create a JavaScript class definition for a mapped converted class.
* @param jsClassName The mapped class's JavaScript class name
* @param setupSuperClass Control whether to generate code for superclass referral
*/
protected String createDtoClassDefinition(String jsClassName, boolean setupSuperClass)
{
StringBuilder buf = new StringBuilder();
for (String match : converterManager.getConverterMatchStrings())
{
Converter conv = converterManager.getConverterByMatchString(match);
// We will only generate JavaScript classes for compound objects/beans
if (conv instanceof NamedConverter)
{
NamedConverter namedConv = (NamedConverter) conv;
if (jsClassName.equals(namedConv.getJavascript()))
{
buf.append(createDtoClassDefinition(namedConv));
if (setupSuperClass)
{
buf.append(createDtoSuperClassDefinition(namedConv));
}
return buf.toString();
}
}
}
log.warn("Failed to create class definition for JS class " + jsClassName + " because it was not found.");
buf.append("// Missing mapped class definition for ");
buf.append(jsClassName);
buf.append(". See the server logs for details.\n");
return buf.toString();
}
/**
* Create a JavaScript class definition for a mapped converted class.
* @param namedConv The class's converter
*/
protected String createDtoClassDefinition(NamedConverter namedConv)
{
// The desired output should follow this scheme:
// (1) if (!('pkg1' in this)) this.pkg1 = {};
// (1) if (!('pkg2' in this.pkg1)) this.pkg1.pkg2 = {};
// (2) if (typeof this.pkg1.pkg2.MyClass != 'function') {
// (2) this.pkg1.pkg2.MyClass = function() {
// (3) this.myProp = <initial value>;
// (3) ...
// (3) }
// (4) this.pkg1.pkg2.MyClass.$dwrClassName = 'pkg1.pkg2.MyClass';
// (5) this.pkg1.pkg2.MyClass.$dwrClassMembers = {};
// (6) this.pkg1.pkg2.MyClass.$dwrClassMembers.myProp = {}; // object is placeholder for additional info in the future and also evals to true
// (6) ...
// (7) this.pkg1.pkg2.MyClass.createFromMap = function(map) {
// (7) var obj = new this(); // this = MyClass = constructor function
// (8) for(prop in map) if (map.hasOwnProperty(prop)) obj[prop] = map[prop];
// (8) return obj;
// (8) }
// (9) dwr.engine._mappedClasses['pkg1.pkg2.MyClass'] = this.pkg1.pkg2.MyClass;
// (10) }
String jsClassName = namedConv.getJavascript();
try
{
StringBuilder buf = new StringBuilder();
String[] parts = jsClassName.split("\\.");
// Generate (1): if (!('pkg2' in this.pkg1)) this.pkg1.pkg2 = {};
String path = "";
for (int i = 0; i < parts.length-1; i++)
{
String leaf = parts[i];
buf.append("if (!('");
buf.append(leaf);
buf.append("' in this");
buf.append(path);
buf.append(")) this");
buf.append(path);
buf.append(".");
buf.append(leaf);
buf.append(" = {};\n");
path += "." + leaf;
}
// Generate (2): if (typeof this.pkg1.pkg2.MyClass != 'function') { this.pkg1.pkg2.MyClass = function() {
buf.append("if (typeof this.");
buf.append(jsClassName);
buf.append(" != 'function') {\n");
buf.append(" this.");
buf.append(jsClassName);
buf.append(" = function() {\n");
// Generate (3): this.myProp = <initial value>;
Map<String, Property> properties = namedConv.getPropertyMapFromClass(namedConv.getInstanceType(), true, true);
for (Entry<String, Property> entry : properties.entrySet())
{
String name = entry.getKey();
Property property = entry.getValue();
Class<?> propType = property.getPropertyType();
// Property name
buf.append(" this.");
buf.append(name);
buf.append(" = ");
// Default property values
if (propType.isArray())
{
buf.append("[]");
}
else if (propType == boolean.class)
{
buf.append("false");
}
else if (propType.isPrimitive())
{
buf.append("0");
}
else
{
buf.append("null");
}
buf.append(";\n");
}
buf.append(" }\n");
// Generate (4): this.pkg1.pkg2.MyClass.$dwrClassName = 'pkg1.pkg2.MyClass';
buf.append(" this.");
buf.append(jsClassName);
buf.append(".$dwrClassName = '");
buf.append(jsClassName);
buf.append("';\n");
// Generate (5): this.pkg1.pkg2.MyClass.$dwrClassMembers = {};
buf.append(" this.");
buf.append(jsClassName);
buf.append(".$dwrClassMembers = {};\n");
// Generate (6): this.pkg1.pkg2.MyClass.$dwrClassMembers.myProp = {};
for (Entry<String, Property> entry : properties.entrySet())
{
String name = entry.getKey();
buf.append(" this.");
buf.append(jsClassName);
buf.append(".$dwrClassMembers.");
buf.append(name);
buf.append(" = {};\n");
}
// Generate (7): this.pkg1.pkg2.MyClass.createFromMap = function(map) { var obj = new this.pkg1.pkg2.MyClass();
buf.append(" this.");
buf.append(jsClassName);
buf.append(".createFromMap = function(map) {\n");
buf.append(" var obj = new this();\n");
// Generate (8): if ('myProp' in map) obj.myProp = map.myProp;
buf.append(" for(prop in map) if (map.hasOwnProperty(prop)) obj[prop] = map[prop];\n");
buf.append(" return obj;\n");
buf.append(" }\n");
// Generate (9): dwr.engine._mappedClasses['pkg1.pkg2.MyClass'] = this.pkg1.pkg2.MyClass;
buf.append(" dwr.engine._mappedClasses['");
buf.append(jsClassName);
buf.append("'] = this.");
buf.append(jsClassName);
buf.append(";\n");
// Generate (10): end of definition
buf.append("}\n");
buf.append("\n");
return buf.toString();
}
catch (Exception ex)
{
log.warn("Failed to create class definition for JS class " + jsClassName, ex);
StringBuilder buf = new StringBuilder();
buf.append("// Missing mapped class definition for ");
buf.append(jsClassName);
buf.append(". See the server logs for details.\n");
return buf.toString();
}
}
/**
* Create the superclass definition for a mapped converted class.
* @param namedConv The class's converter
*/
protected String createDtoSuperClassDefinition(NamedConverter namedConv)
{
// The desired output is something like this:
// MySubClass.prototype = new MySuperClass();
// MySubClass.prototype.constructor = MySubClass;
StringBuilder buf = new StringBuilder();
if (LocalUtil.hasLength(namedConv.getJavascriptSuperClass()))
{
String subclass = namedConv.getJavascript();
String superclass = namedConv.getJavascriptSuperClass();
buf.append(subclass);
buf.append(".prototype = new ");
buf.append(superclass);
buf.append("();\n");
buf.append(subclass);
buf.append(".prototype.constructor = ");
buf.append(subclass);
buf.append(";\n");
buf.append("\n");
}
return buf.toString();
}
/**
* Generates Javascript for a given Java method
* @param scriptName Name of the Javascript file, without ".js" suffix
* @param method Target method
* @return Javascript implementing the DWR call for the target method
*/
protected String getMethodJS(String scriptName, Method method)
{
StringBuffer buffer = new StringBuffer();
String methodName = method.getName();
Class<?>[] paramTypes = method.getParameterTypes();
// Create the sdoc comment
buffer.append("/**\n");
for (int j = 0; j < paramTypes.length; j++)
{
if (!LocalUtil.isServletClass(paramTypes[j]))
{
buffer.append(" * @param {");
buffer.append(paramTypes[j]);
buffer.append("} p");
buffer.append(j);
buffer.append(" a param\n");
}
}
buffer.append(" * @param {function|Object} callback callback function or options object\n");
buffer.append(" */\n");
// Create the function definition
buffer.append(scriptName);
buffer.append('.');
buffer.append(methodName);
buffer.append(" = function(");
for (int j = 0; j < paramTypes.length; j++)
{
if (!LocalUtil.isServletClass(paramTypes[j]))
{
buffer.append("p");
buffer.append(j);
buffer.append(", ");
}
}
buffer.append("callback) {\n");
// The method body calls into engine.js
buffer.append(" return ");
buffer.append(EnginePrivate.getExecuteFunctionName());
buffer.append("(");
buffer.append(scriptName);
buffer.append("._path, '");
buffer.append(scriptName);
buffer.append("', '");
buffer.append(methodName);
buffer.append("\', arguments);\n");
buffer.append("};\n\n");
return buffer.toString();
}
/* (non-Javadoc)
* @see org.directwebremoting.Remoter#execute(org.directwebremoting.Calls)
*/
public Replies execute(Calls calls)
{
Replies replies = new Replies(calls);
int callCount = calls.getCallCount();
if (callCount > maxCallCount)
{
log.error("Call count for batch exceeds maxCallCount. Add an init-param of maxCallCount to increase this limit");
throw new SecurityException("Call count for batch is too high");
}
for (Call call : calls)
{
Reply reply = execute(call);
replies.addReply(reply);
}
return replies;
}
/**
* Execute a single call object
* @param call The call to execute
* @return A Reply to the Call
*/
public Reply execute(Call call)
{
// We set this up here because if something goes wrong we want to know
// if there are any LogAjaxFilter implementations to provide any logging
List<AjaxFilter> filters = ajaxFilterManager.getAjaxFilters(call.getScriptName());
try
{
Method method = call.getMethod();
if (method == null || call.getException() != null)
{
return new Reply(call.getCallId(), null, call.getException());
}
// Get a list of the available matching methods with the coerced
// parameters that we will use to call it if we choose to use that
// method.
Creator creator = creatorManager.getCreator(call.getScriptName(), true);
// We don't need to check accessControl.getReasonToNotExecute()
// because the checks are made by the doExec method, but we do check
// if we can display it
accessControl.assertExecutionIsPossible(creator, call.getScriptName(), method);
// Get ourselves an object to execute a method on unless the
// method is static
Object object = null;
String scope = creator.getScope();
boolean create = false;
if (!Modifier.isStatic(method.getModifiers()))
{
WebContext webcx = WebContextFactory.get();
// Check the various scopes to see if it is there
if (scope.equals(Creator.APPLICATION))
{
object = webcx.getServletContext().getAttribute(call.getScriptName());
}
else if (scope.equals(Creator.SESSION))
{
object = webcx.getSession().getAttribute(call.getScriptName());
}
else if (scope.equals(Creator.SCRIPT))
{
object = webcx.getScriptSession().getAttribute(call.getScriptName());
}
else if (scope.equals(Creator.REQUEST))
{
object = webcx.getHttpServletRequest().getAttribute(call.getScriptName());
}
// Creator.PAGE scope means we create one every time anyway
// If we don't have an object the call the creator
if (object == null)
{
create = true;
object = creator.getInstance();
}
// Remember it for next time
if (create)
{
if (scope.equals(Creator.APPLICATION))
{
// This might also be done at application startup by
// DefaultCreatorManager.addCreator(String, Creator)
webcx.getServletContext().setAttribute(call.getScriptName(), object);
}
else if (scope.equals(Creator.SESSION))
{
webcx.getSession().setAttribute(call.getScriptName(), object);
}
else if (scope.equals(Creator.SCRIPT))
{
webcx.getScriptSession().setAttribute(call.getScriptName(), object);
}
else if (scope.equals(Creator.REQUEST))
{
webcx.getHttpServletRequest().setAttribute(call.getScriptName(), object);
}
// Creator.PAGE scope means we create one every time anyway
}
}
// Some debug
/*
if (log.isDebugEnabled())
{
StringBuffer buffer = new StringBuffer();
buffer.append("Exec: ")
.append(call.getScriptName())
.append(".")
.append(call.getMethodName())
.append("()");
if (create)
{
buffer.append(" Object created, ");
if (!scope.equals(Creator.PAGE))
{
buffer.append(" stored in ");
buffer.append(scope);
}
else
{
buffer.append(" not stored");
}
}
else
{
buffer.append(" Object found in ");
buffer.append(scope);
}
buffer.append(". ");
buffer.append("id=");
buffer.append(call.getCallId());
log.debug(buffer.toString());
}
//*/
// Execute the filter chain method.toString()
final Iterator<AjaxFilter> it = filters.iterator();
AjaxFilterChain chain = new AjaxFilterChain()
{
public Object doFilter(Object obj, Method meth, Object[] params) throws Exception
{
if (it.hasNext())
{
AjaxFilter next = it.next();
return next.doFilter(obj, meth, params, this);
}
else
{
return meth.invoke(obj, params);
}
}
};
Object reply = chain.doFilter(object, method, call.getParameters());
return new Reply(call.getCallId(), reply);
}
catch (SecurityException ex)
{
if (!filtersIncludeLogging(filters))
{
log.warn("Security Exception: " + ex.getMessage());
}
// If we are in live mode, then we don't even say what went wrong
if (debug)
{
return new Reply(call.getCallId(), null, ex);
}
else
{
return new Reply(call.getCallId(), null, new SecurityException());
}
}
catch (InvocationTargetException ex)
{
// Allow Jetty RequestRetry exception to propagate to container
Continuation.rethrowIfContinuation(ex);
debugException(filters, ex.getTargetException());
return new Reply(call.getCallId(), null, ex.getTargetException());
}
catch (Exception ex)
{
// Allow Jetty RequestRetry exception to propagate to container
Continuation.rethrowIfContinuation(ex);
debugException(filters, ex);
return new Reply(call.getCallId(), null, ex);
}
}
/**
* Do logging output if there are no logging filters and add a note of
* explanation the first time
* @param filters The configured filters
* @param ex The exception saying what broke
*/
private void debugException(List<AjaxFilter> filters, Throwable ex)
{
if (debug && !filtersIncludeLogging(filters))
{
if (!givenAuditLogHint)
{
log.debug("No logging filters defined. Minimal execption logging. For more detail add <filter class='org.directwebremoting.filter.AuditLogAjaxFilter'/> to dwr.xml");
givenAuditLogHint = true;
}
log.debug("Method execution failed: ", ex);
}
}
/**
* A quick check to see if we are already doing some form of logging
* @param filters The list of configured filters
* @return true if we are logging
*/
private boolean filtersIncludeLogging(List<AjaxFilter> filters)
{
for (AjaxFilter element : filters)
{
if (element instanceof LogAjaxFilter)
{
return true;
}
}
return false;
}
/**
* By default we use a relative path to the DWR servlet which can help if
* there are several routes to the servlet. However it can be a pain if
* the DWR engine is running on a different port from the web-server.
* However this is a minority case so this is not officially supported.
* @param useAbsolutePath Does DWR generate an absolute _path property
*/
public void setUseAbsolutePath(boolean useAbsolutePath)
{
this.useAbsolutePath = useAbsolutePath;
}
/**
* Accessor for the CreatorManager that we configure
* @param creatorManager The new ConverterManager
*/
public void setCreatorManager(CreatorManager creatorManager)
{
this.creatorManager = creatorManager;
}
/**
* Accessor for the ConverterManager that we configure
* @param converterManager The new ConverterManager
*/
public void setConverterManager(ConverterManager converterManager)
{
this.converterManager = converterManager;
}
/**
* Accessor for the security manager
* @param accessControl The accessControl to set.
*/
public void setAccessControl(AccessControl accessControl)
{
this.accessControl = accessControl;
}
/**
* Accessor for the AjaxFilterManager
* @param ajaxFilterManager The AjaxFilterManager to set.
*/
public void setAjaxFilterManager(AjaxFilterManager ajaxFilterManager)
{
this.ajaxFilterManager = ajaxFilterManager;
}
/**
* If we need to override the default path
* @param overridePath The new override path
*/
public void setOverridePath(String overridePath)
{
this.overridePath = overridePath;
}
/**
* Do we allow impossible tests for debug purposes
* @param allowImpossibleTests The allowImpossibleTests to set.
*/
public void setAllowImpossibleTests(boolean allowImpossibleTests)
{
this.allowImpossibleTests = allowImpossibleTests;
}
/**
* To prevent a DoS attack we limit the max number of calls that can be
* made in a batch
* @param maxCallCount the maxCallCount to set
*/
public void setMaxCallCount(int maxCallCount)
{
this.maxCallCount = maxCallCount;
}
/**
* Set the debug status
* @param debug The new debug setting
*/
public void setDebug(boolean debug)
{
this.debug = debug;
}
/**
* Have we given the hint about {@link org.directwebremoting.filter.AuditLogAjaxFilter}
*/
protected boolean givenAuditLogHint = false;
/**
* Are we in debug-mode and therefore more helpful at the expense of security?
*/
private boolean debug = false;
/**
* What AjaxFilters apply to which Ajax calls?
*/
private AjaxFilterManager ajaxFilterManager = null;
/**
* How we create new beans
*/
protected CreatorManager creatorManager = null;
/**
* How we convert beans - or in this case create client side classes
*/
protected ConverterManager converterManager = null;
/**
* The security manager
*/
protected AccessControl accessControl = null;
/**
* If we need to override the default path
*/
protected String overridePath = null;
/**
* @see #setUseAbsolutePath(boolean)
*/
protected boolean useAbsolutePath = false;
/**
* This helps us test that access rules are being followed
*/
protected boolean allowImpossibleTests = false;
/**
* To prevent a DoS attack we limit the max number of calls that can be
* made in a batch
*/
protected int maxCallCount = 20;
/**
* Generated Javascript cache
*/
protected Map<String, String> methodCache = Collections.synchronizedMap(new HashMap<String, String>());
/**
* The log stream
*/
private static final Log log = LogFactory.getLog(DefaultRemoter.class);
}