/* * 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.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.StringWriter; import java.lang.reflect.Method; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.directwebremoting.extend.AccessControl; import org.directwebremoting.extend.ConverterManager; import org.directwebremoting.extend.Creator; import org.directwebremoting.extend.CreatorManager; import org.directwebremoting.extend.DebugPageGenerator; import org.directwebremoting.extend.DwrConstants; import org.directwebremoting.servlet.PathConstants; import org.directwebremoting.util.CopyUtils; import org.directwebremoting.util.JavascriptUtil; import org.directwebremoting.util.LocalUtil; /** * A default implementation of TestPageGenerator * @author Joe Walker [joe at getahead dot ltd dot uk] */ public class DefaultDebugPageGenerator implements DebugPageGenerator { /* (non-Javadoc) * @see org.directwebremoting.DebugPageGenerator#generateIndexPage(java.lang.String) */ public String generateIndexPage(final String root) throws SecurityException { if (!creatorManager.isDebug()) { log.warn("Failed attempt to access test pages outside of debug mode. Set the debug init-parameter to true to enable."); throw new SecurityException("Access to debug pages is denied."); } StringBuffer buffer = new StringBuffer(); buffer.append("<html>\n"); buffer.append("<head><title>DWR Test Index</title></head>\n"); buffer.append("<body>\n"); buffer.append("<h2>Classes known to DWR:</h2>\n"); buffer.append("<ul>\n"); for (String name : creatorManager.getCreatorNames(false)) { Creator creator = creatorManager.getCreator(name, false); buffer.append("<li><a href='"); buffer.append(root); buffer.append(testHandlerUrl); buffer.append(name); buffer.append("'>"); buffer.append(name); buffer.append("</a> ("); buffer.append(creator.getType().getName()); buffer.append(")</li>\n"); } buffer.append("</ul>\n"); buffer.append("</body></html>\n"); return buffer.toString(); } /* (non-Javadoc) * @see org.directwebremoting.DebugPageGenerator#generateTestPage(java.lang.String, java.lang.String) */ public String generateTestPage(final String root, final String scriptName) throws SecurityException { if (!creatorManager.isDebug()) { log.warn("Failed attempt to access test pages outside of debug mode. Set the debug init-parameter to true to enable."); throw new SecurityException("Access to debug pages is denied."); } String interfaceURL = root + interfaceHandlerUrl + scriptName + PathConstants.EXTENSION_JS; String engineURL = root + engineHandlerUrl; String utilURL = root + utilHandlerUrl; String proxyInterfaceURL = PATH_UP + interfaceHandlerUrl + scriptName + PathConstants.EXTENSION_JS; String proxyEngineURL = PATH_UP + engineHandlerUrl; String proxyUtilURL = PATH_UP + utilHandlerUrl; Creator creator = creatorManager.getCreator(scriptName, true); Method[] methods = creator.getType().getMethods(); StringBuffer buffer = new StringBuffer(); buffer.append("<html>\n"); buffer.append("<head>\n"); buffer.append(" <title>DWR Test</title>\n"); buffer.append(" <!-- These paths use .. so that they still work behind a path mapping proxy. The fully qualified version is more cut and paste friendly. -->\n"); buffer.append(" <script type='text/javascript' src='" + proxyInterfaceURL + "'></script>\n"); buffer.append(" <script type='text/javascript' src='" + proxyEngineURL + "'></script>\n"); buffer.append(" <script type='text/javascript' src='" + proxyUtilURL + "'></script>\n"); buffer.append(" <script type='text/javascript'>\n"); buffer.append(" function objectEval(text)\n"); buffer.append(" {\n"); buffer.append(" // eval() breaks when we use it to get an object using the { a:42, b:'x' }\n"); buffer.append(" // syntax because it thinks that { and } surround a block and not an object\n"); buffer.append(" // So we wrap it in an array and extract the first element to get around\n"); buffer.append(" // this.\n"); buffer.append(" // This code is only needed for interpreting the parameter input fields,\n"); buffer.append(" // so you can ignore this for normal use.\n"); buffer.append(" // The regex = [start of line][whitespace]{[stuff]}[whitespace][end of line]\n"); buffer.append(" text = text.replace(/\\n/g, ' ');\n"); buffer.append(" text = text.replace(/\\r/g, ' ');\n"); buffer.append(" if (text.match(/^\\s*\\{.*\\}\\s*$/))\n"); buffer.append(" {\n"); buffer.append(" text = '[' + text + '][0]';\n"); buffer.append(" }\n"); buffer.append(" return eval(text);\n"); buffer.append(" }\n"); buffer.append(" </script>\n"); buffer.append(" <style>\n"); buffer.append(" input.itext { font-size: smaller; background: #E4E4E4; border: 0; }\n"); buffer.append(" input.ibutton { font-size: xx-small; border: 1px outset; margin: 0px; padding: 0px; }\n"); buffer.append(" span.reply { background: #ffffdd; white-space: pre; }\n"); buffer.append(" span.warning { font-size: smaller; color: red; }\n"); buffer.append(" </style>\n"); buffer.append("</head>\n"); buffer.append("<body onload='dwr.util.useLoadingMessage()'>\n"); buffer.append("<h2>Methods For: " + scriptName + " (" + creator.getType().getName() + ")</h2>\n"); buffer.append("<p>To use this class in your javascript you will need the following script includes:</p>\n"); buffer.append("<pre>\n"); buffer.append(" <script type='text/javascript' src='<a href='" + interfaceURL + "'>" + interfaceURL + "</a>'></script>\n"); buffer.append(" <script type='text/javascript' src='<a href='" + engineURL + "'>" + engineURL + "</a>'></script>\n"); buffer.append("</pre>\n"); buffer.append("<p>In addition there is an optional utility script:</p>\n"); buffer.append("<pre>\n"); buffer.append(" <script type='text/javascript' src='<a href='" + utilURL + "'>" + utilURL + "</a>'></script>\n"); buffer.append("</pre>\n"); buffer.append("<p>Replies from DWR are shown with a yellow background if they are simple or in an alert box otherwise.<br/>\n"); buffer.append("The inputs are evaluated as Javascript so strings must be quoted before execution.</p>\n"); for (int i = 0; i < methods.length; i++) { Method method = methods[i]; String methodName = method.getName(); // Is it on the list of banned names if (JavascriptUtil.isReservedWord(methodName)) { buffer.append("<li style='color: #88A;'>" + methodName + "() is not available because it is a reserved word.</li>\n"); continue; } buffer.append("<li>\n"); buffer.append(" " + methodName + '('); Class<?>[] paramTypes = method.getParameterTypes(); for (int j = 0; j < paramTypes.length; j++) { Class<?> paramType = paramTypes[j]; // The special type that we handle transparently if (LocalUtil.isServletClass(paramType)) { buffer.append("AUTO"); } else { String value = ""; if (paramType == String.class) { value = "\"\""; } else if (paramType == Boolean.class || paramType == Boolean.TYPE) { value = "true"; } else if (paramType == Integer.class || paramType == Integer.TYPE || paramType == Short.class || paramType == Short.TYPE || paramType == Long.class || paramType == Long.TYPE || paramType == Byte.class || paramType == Byte.TYPE) { value = "0"; } else if (paramType == Float.class || paramType == Float.TYPE || paramType == Double.class || paramType == Double.TYPE) { value = "0.0"; } else if (paramType.isArray() || Collection.class.isAssignableFrom(paramType)) { value = "[]"; } else if (Map.class.isAssignableFrom(paramType)) { value = "{}"; } buffer.append(" <input class='itext' type='text' size='10' value='" + value + "' id='p" + i + j + "' title='Will be converted to: " + paramType.getName() + "'/>"); } buffer.append(j == paramTypes.length - 1 ? "" : ", \n"); } buffer.append(" );\n"); String onclick = scriptName + '.' + methodName + "("; for (int j = 0; j < paramTypes.length; j++) { if (!LocalUtil.isServletClass(paramTypes[j])) { onclick += "objectEval($(\"p" + i + j + "\").value), "; } } onclick += "reply" + i + ");"; buffer.append(" <input class='ibutton' type='button' onclick='" + onclick + "' value='Execute' title='Calls " + scriptName + '.' + methodName + "(). View source for details.'/>\n"); buffer.append(" <script type='text/javascript'>\n"); buffer.append(" var reply" + i + " = function(data)\n"); buffer.append(" {\n"); buffer.append(" if (data != null && typeof data == 'object') alert(dwr.util.toDescriptiveString(data, 2));\n"); buffer.append(" else dwr.util.setValue('d" + i + "', dwr.util.toDescriptiveString(data, 1));\n"); buffer.append(" }\n"); buffer.append(" </script>\n"); buffer.append(" <span id='d" + i + "' class='reply'></span>\n"); // Print a warning if this method is overloaded boolean overloaded = false; for (int j = 0; j < methods.length; j++) { if (j != i && methods[j].getName().equals(methodName)) { overloaded = true; } } if (overloaded) { buffer.append("<br/><span class='warning'>(Warning: overloaded methods are not recommended. See <a href='#overloadedMethod'>below</a>)</span>\n"); } // Print a warning if the method uses un-marshallable types for (Class<?> paramType1 : paramTypes) { if (!converterManager.isConvertable(paramType1)) { buffer.append("<br/><span class='warning'>(Warning: No Converter for " + paramType1.getName() + ". See <a href='#missingConverter'>below</a>)</span>\n"); } } if (!converterManager.isConvertable(method.getReturnType())) { buffer.append("<br/><span class='warning'>(Warning: No Converter for " + method.getReturnType().getName() + ". See <a href='#missingConverter'>below</a>)</span>\n"); } // See also the call to getReasonToNotExecute() above try { accessControl.assertIsDisplayable(creator, scriptName, method); } catch (SecurityException ex) { buffer.append("<br/><span class='warning'>(Warning: " + methodName + "() is excluded: " + ex.getMessage() + ". See <a href='#excludedMethod'>below</a>)</span>\n"); } // We don't need to call assertExecutionIsPossible() because those // checks should be done by assertIsDisplayable() above // accessControl.assertExecutionIsPossible(creator, scriptName, method); buffer.append("</li>\n"); } buffer.append("</ul>\n"); buffer.append("<h2>Other Links</h2>\n"); buffer.append("<ul>\n"); buffer.append("<li>Back to <a href='" + root + "/'>class index</a>.</li>\n"); buffer.append("</ul>\n"); synchronized (scriptCache) { String output = scriptCache.get(PathConstants.FILE_HELP); if (output == null) { InputStream raw = getClass().getResourceAsStream(DwrConstants.PACKAGE + PathConstants.FILE_HELP); if (raw == null) { log.error("Missing file " + PathConstants.FILE_HELP + ". Check the dwr.jar file was built to include html files."); output = "<p>Failed to read help text from resource file. Check dwr.jar is built to include html files.</p>"; } else { BufferedReader in = new BufferedReader(new InputStreamReader(raw)); try { StringWriter writer = new StringWriter(); CopyUtils.copy(in, writer); output = writer.toString(); } catch (IOException ex) { log.error("Failed to read help text from resource file.", ex); output = "Failed to read help text from resource file."; } } scriptCache.put(PathConstants.FILE_HELP, output); } buffer.append(output); } buffer.append("</body></html>\n"); return buffer.toString(); } /* (non-Javadoc) * @see org.directwebremoting.DebugPageGenerator#generateInterfaceUrl(java.lang.String, java.lang.String) */ @Deprecated public String generateInterfaceUrl(String root, String scriptName) { return root + interfaceHandlerUrl + scriptName + PathConstants.EXTENSION_JS; } /* (non-Javadoc) * @see org.directwebremoting.DebugPageGenerator#generateEngineUrl(java.lang.String) */ @Deprecated public String generateEngineUrl(String root) { return root + engineHandlerUrl; } /* (non-Javadoc) * @see org.directwebremoting.DebugPageGenerator#generateLibraryUrl(java.lang.String, java.lang.String) */ @Deprecated public String generateLibraryUrl(String root, String library) { return root + library; } /* (non-Javadoc) * @see org.directwebremoting.DebugPageGenerator#getAvailableLibraries() */ @Deprecated public Collection<String> getAvailableLibraries() { if (availableLibraries == null) { availableLibraries = Collections.unmodifiableCollection(Arrays.asList(utilHandlerUrl)); } return availableLibraries; } /** * Accessor for the DefaultCreatorManager that we configure * @param converterManager The new DefaultConverterManager */ public void setConverterManager(ConverterManager converterManager) { this.converterManager = converterManager; } /** * Accessor for the DefaultCreatorManager that we configure * @param creatorManager The new DefaultConverterManager */ public void setCreatorManager(CreatorManager creatorManager) { this.creatorManager = creatorManager; } /** * Accessor for the security manager * @param accessControl The accessControl to set. */ public void setAccessControl(AccessControl accessControl) { this.accessControl = accessControl; } /** * @param engineHandlerUrl the engineHandlerUrl to set */ public void setEngineHandlerUrl(String engineHandlerUrl) { this.engineHandlerUrl = engineHandlerUrl; } /** * @param utilHandlerUrl the utilHandlerUrl to set */ public void setUtilHandlerUrl(String utilHandlerUrl) { this.utilHandlerUrl = utilHandlerUrl; } /** * @param testHandlerUrl the testHandlerUrl to set */ public void setTestHandlerUrl(String testHandlerUrl) { this.testHandlerUrl = testHandlerUrl; } /** * Setter for the URL that this handler available on * @param interfaceHandlerUrl the interfaceHandlerUrl to set */ public void setInterfaceHandlerUrl(String interfaceHandlerUrl) { this.interfaceHandlerUrl = interfaceHandlerUrl; } /** * The URL for the {@link org.directwebremoting.servlet.EngineHandler} */ protected String engineHandlerUrl; /** * The URL for the {@link org.directwebremoting.ui.servlet.UtilHandler} */ protected String utilHandlerUrl; /** * The URL for the {@link org.directwebremoting.servlet.TestHandler} */ protected String testHandlerUrl; /** * What URL is this handler available on? */ protected String interfaceHandlerUrl; /** * How we convert parameters */ protected ConverterManager converterManager = null; /** * How we create new beans */ protected CreatorManager creatorManager = null; /** * The security manager */ protected AccessControl accessControl = null; /** * We cache the script output for speed */ protected final Map<String, String> scriptCache = new HashMap<String, String>(); /** * For getAvailableLibraries() - just a RO Collection that currently returns * only util.js, but may be expanded in the future. */ private Collection<String> availableLibraries = null; /** * 2 dots */ private static final String PATH_UP = ".."; /** * The log stream */ private static final Log log = LogFactory.getLog(DefaultDebugPageGenerator.class); }