001: /*
002: * Copyright 2005 Joe Walker
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package org.directwebremoting.impl;
017:
018: import java.io.BufferedReader;
019: import java.io.IOException;
020: import java.io.InputStream;
021: import java.io.InputStreamReader;
022: import java.lang.reflect.Method;
023: import java.util.Arrays;
024: import java.util.Collection;
025: import java.util.Collections;
026: import java.util.HashMap;
027: import java.util.Map;
028:
029: import org.apache.commons.logging.LogFactory;
030: import org.apache.commons.logging.Log;
031: import org.directwebremoting.extend.AccessControl;
032: import org.directwebremoting.extend.ConverterManager;
033: import org.directwebremoting.extend.Creator;
034: import org.directwebremoting.extend.CreatorManager;
035: import org.directwebremoting.extend.DebugPageGenerator;
036: import org.directwebremoting.extend.DwrConstants;
037: import org.directwebremoting.servlet.EngineHandler;
038: import org.directwebremoting.servlet.PathConstants;
039: import org.directwebremoting.servlet.UtilHandler;
040: import org.directwebremoting.util.JavascriptUtil;
041: import org.directwebremoting.util.LocalUtil;
042: import org.directwebremoting.util.Messages;
043:
044: /**
045: * A default implementation of TestPageGenerator
046: * @author Joe Walker [joe at getahead dot ltd dot uk]
047: */
048: public class DefaultDebugPageGenerator implements DebugPageGenerator {
049: /* (non-Javadoc)
050: * @see org.directwebremoting.DebugPageGenerator#generateIndexPage(java.lang.String)
051: */
052: public String generateIndexPage(final String root)
053: throws SecurityException {
054: if (!creatorManager.isDebug()) {
055: log
056: .warn("Failed attempt to access test pages outside of debug mode. Set the debug init-parameter to true to enable.");
057: throw new SecurityException(
058: Messages
059: .getString("DefaultDebugPageGenerator.AccessDenied"));
060: }
061:
062: StringBuffer buffer = new StringBuffer();
063:
064: buffer.append("<html>\n");
065: buffer.append("<head><title>DWR Test Index</title></head>\n");
066: buffer.append("<body>\n");
067:
068: buffer.append("<h2>Classes known to DWR:</h2>\n");
069: buffer.append("<ul>\n");
070: for (String name : creatorManager.getCreatorNames()) {
071: Creator creator = creatorManager.getCreator(name);
072:
073: buffer.append("<li><a href='");
074: buffer.append(root);
075: buffer.append(testHandlerUrl);
076: buffer.append(name);
077: buffer.append("'>");
078: buffer.append(name);
079: buffer.append("</a> (");
080: buffer.append(creator.getType().getName());
081: buffer.append(")</li>\n");
082: }
083: buffer.append("</ul>\n");
084:
085: buffer.append("</body></html>\n");
086:
087: return buffer.toString();
088: }
089:
090: /* (non-Javadoc)
091: * @see org.directwebremoting.DebugPageGenerator#generateTestPage(java.lang.String, java.lang.String)
092: */
093: public String generateTestPage(final String root,
094: final String scriptName) throws SecurityException {
095: if (!creatorManager.isDebug()) {
096: log
097: .warn("Failed attempt to access test pages outside of debug mode. Set the debug init-parameter to true to enable.");
098: throw new SecurityException(Messages
099: .getString("DefaultAccessControl.AccessDenied"));
100: }
101:
102: String interfaceURL = root + interfaceHandlerUrl + scriptName
103: + PathConstants.EXTENSION_JS;
104: String engineURL = root + engineHandlerUrl;
105: String utilURL = root + utilHandlerUrl;
106:
107: String proxyInterfaceURL = PATH_UP + interfaceHandlerUrl
108: + scriptName + PathConstants.EXTENSION_JS;
109: String proxyEngineURL = PATH_UP + engineHandlerUrl;
110: String proxyUtilURL = PATH_UP + utilHandlerUrl;
111:
112: Creator creator = creatorManager.getCreator(scriptName);
113: Method[] methods = creator.getType().getMethods();
114: StringBuffer buffer = new StringBuffer();
115:
116: buffer.append("<html>\n");
117: buffer.append("<head>\n");
118: buffer.append(" <title>DWR Test</title>\n");
119: buffer
120: .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");
121: buffer.append(" <script type='text/javascript' src='"
122: + proxyInterfaceURL + "'></script>\n");
123: buffer.append(" <script type='text/javascript' src='"
124: + proxyEngineURL + "'></script>\n");
125: buffer.append(" <script type='text/javascript' src='"
126: + proxyUtilURL + "'></script>\n");
127: buffer.append(" <script type='text/javascript'>\n");
128: buffer.append(" function objectEval(text)\n");
129: buffer.append(" {\n");
130: buffer
131: .append(" // eval() breaks when we use it to get an object using the { a:42, b:'x' }\n");
132: buffer
133: .append(" // syntax because it thinks that { and } surround a block and not an object\n");
134: buffer
135: .append(" // So we wrap it in an array and extract the first element to get around\n");
136: buffer.append(" // this.\n");
137: buffer
138: .append(" // This code is only needed for interpreting the parameter input fields,\n");
139: buffer
140: .append(" // so you can ignore this for normal use.\n");
141: buffer
142: .append(" // The regex = [start of line][whitespace]{[stuff]}[whitespace][end of line]\n");
143: buffer.append(" text = text.replace(/\\n/g, ' ');\n");
144: buffer.append(" text = text.replace(/\\r/g, ' ');\n");
145: buffer.append(" if (text.match(/^\\s*\\{.*\\}\\s*$/))\n");
146: buffer.append(" {\n");
147: buffer.append(" text = '[' + text + '][0]';\n");
148: buffer.append(" }\n");
149: buffer.append(" return eval(text);\n");
150: buffer.append(" }\n");
151: buffer.append(" </script>\n");
152: buffer.append(" <style>\n");
153: buffer
154: .append(" input.itext { font-size: smaller; background: #E4E4E4; border: 0; }\n");
155: buffer
156: .append(" input.ibutton { font-size: xx-small; border: 1px outset; margin: 0px; padding: 0px; }\n");
157: buffer
158: .append(" span.reply { background: #ffffdd; white-space: pre; }\n");
159: buffer
160: .append(" span.warning { font-size: smaller; color: red; }\n");
161: buffer.append(" </style>\n");
162: buffer.append("</head>\n");
163: buffer.append("<body onload='dwr.util.useLoadingMessage()'>\n");
164:
165: buffer.append("<h2>Methods For: " + scriptName + " ("
166: + creator.getType().getName() + ")</h2>\n");
167: buffer
168: .append("<p>To use this class in your javascript you will need the following script includes:</p>\n");
169: buffer.append("<pre>\n");
170: buffer
171: .append(" <script type='text/javascript' src='<a href='"
172: + interfaceURL
173: + "'>"
174: + interfaceURL
175: + "</a>'></script>\n");
176: buffer
177: .append(" <script type='text/javascript' src='<a href='"
178: + engineURL
179: + "'>"
180: + engineURL
181: + "</a>'></script>\n");
182: buffer.append("</pre>\n");
183:
184: buffer
185: .append("<p>In addition there is an optional utility script:</p>\n");
186: buffer.append("<pre>\n");
187: buffer
188: .append(" <script type='text/javascript' src='<a href='"
189: + utilURL
190: + "'>"
191: + utilURL
192: + "</a>'></script>\n");
193: buffer.append("</pre>\n");
194:
195: buffer
196: .append("<p>Replies from DWR are shown with a yellow background if they are simple or in an alert box otherwise.<br/>\n");
197: buffer
198: .append("The inputs are evaluated as Javascript so strings must be quoted before execution.</p>\n");
199:
200: for (int i = 0; i < methods.length; i++) {
201: Method method = methods[i];
202: String methodName = method.getName();
203:
204: // Is it on the list of banned names
205: if (JavascriptUtil.isReservedWord(methodName)) {
206: buffer
207: .append("<li style='color: #88A;'>"
208: + methodName
209: + "() is not available because it is a reserved word.</li>\n");
210: continue;
211: }
212:
213: buffer.append("<li>\n");
214: buffer.append(" " + methodName + '(');
215:
216: Class<?>[] paramTypes = method.getParameterTypes();
217: for (int j = 0; j < paramTypes.length; j++) {
218: Class<?> paramType = paramTypes[j];
219:
220: // The special type that we handle transparently
221: if (LocalUtil.isServletClass(paramType)) {
222: buffer.append("AUTO");
223: } else {
224: String value = "";
225: if (paramType == String.class) {
226: value = "\"\"";
227: } else if (paramType == Boolean.class
228: || paramType == Boolean.TYPE) {
229: value = "true";
230: } else if (paramType == Integer.class
231: || paramType == Integer.TYPE
232: || paramType == Short.class
233: || paramType == Short.TYPE
234: || paramType == Long.class
235: || paramType == Long.TYPE
236: || paramType == Byte.class
237: || paramType == Byte.TYPE) {
238: value = "0";
239: } else if (paramType == Float.class
240: || paramType == Float.TYPE
241: || paramType == Double.class
242: || paramType == Double.TYPE) {
243: value = "0.0";
244: } else if (paramType.isArray()
245: || Collection.class
246: .isAssignableFrom(paramType)) {
247: value = "[]";
248: } else if (Map.class.isAssignableFrom(paramType)) {
249: value = "{}";
250: }
251:
252: buffer
253: .append(" <input class='itext' type='text' size='10' value='"
254: + value
255: + "' id='p"
256: + i
257: + j
258: + "' title='Will be converted to: "
259: + paramType.getName() + "'/>");
260: }
261:
262: buffer.append(j == paramTypes.length - 1 ? "" : ", \n");
263: }
264: buffer.append(" );\n");
265:
266: String onclick = scriptName + '.' + methodName + "(";
267: for (int j = 0; j < paramTypes.length; j++) {
268: if (!LocalUtil.isServletClass(paramTypes[j])) {
269: onclick += "objectEval($(\"p" + i + j
270: + "\").value), ";
271: }
272: }
273: onclick += "reply" + i + ");";
274:
275: buffer
276: .append(" <input class='ibutton' type='button' onclick='"
277: + onclick
278: + "' value='Execute' title='Calls "
279: + scriptName
280: + '.'
281: + methodName
282: + "(). View source for details.'/>\n");
283:
284: buffer.append(" <script type='text/javascript'>\n");
285: buffer.append(" var reply" + i + " = function(data)\n");
286: buffer.append(" {\n");
287: buffer
288: .append(" if (data != null && typeof data == 'object') alert(dwr.util.toDescriptiveString(data, 2));\n");
289: buffer.append(" else dwr.util.setValue('d" + i
290: + "', dwr.util.toDescriptiveString(data, 1));\n");
291: buffer.append(" }\n");
292: buffer.append(" </script>\n");
293: buffer.append(" <span id='d" + i
294: + "' class='reply'></span>\n");
295:
296: // Print a warning if this method is overloaded
297: boolean overloaded = false;
298: for (int j = 0; j < methods.length; j++) {
299: if (j != i && methods[j].getName().equals(methodName)) {
300: overloaded = true;
301: }
302: }
303: if (overloaded) {
304: buffer
305: .append("<br/><span class='warning'>(Warning: overloaded methods are not recommended. See <a href='#overloadedMethod'>below</a>)</span>\n");
306: }
307:
308: // Print a warning if the method uses un-marshallable types
309: for (Class<?> paramType1 : paramTypes) {
310: if (!converterManager.isConvertable(paramType1)) {
311: buffer
312: .append("<br/><span class='warning'>(Warning: No Converter for "
313: + paramType1.getName()
314: + ". See <a href='#missingConverter'>below</a>)</span>\n");
315: }
316: }
317:
318: if (!converterManager.isConvertable(method.getReturnType())) {
319: buffer
320: .append("<br/><span class='warning'>(Warning: No Converter for "
321: + method.getReturnType().getName()
322: + ". See <a href='#missingConverter'>below</a>)</span>\n");
323: }
324:
325: // See also the call to getReasonToNotExecute() above
326: try {
327: accessControl.assertIsDisplayable(creator, scriptName,
328: method);
329: } catch (SecurityException ex) {
330: buffer
331: .append("<br/><span class='warning'>(Warning: "
332: + methodName
333: + "() is excluded: "
334: + ex.getMessage()
335: + ". See <a href='#excludedMethod'>below</a>)</span>\n");
336: }
337:
338: // We don't need to call assertExecutionIsPossible() because those
339: // checks should be done by assertIsDisplayable() above
340: // accessControl.assertExecutionIsPossible(creator, scriptName, method);
341:
342: buffer.append("</li>\n");
343: }
344:
345: buffer.append("</ul>\n");
346:
347: buffer.append("<h2>Other Links</h2>\n");
348: buffer.append("<ul>\n");
349: buffer.append("<li>Back to <a href='" + root
350: + "/'>class index</a>.</li>\n");
351: buffer.append("</ul>\n");
352:
353: synchronized (scriptCache) {
354: String output = scriptCache.get(PathConstants.FILE_HELP);
355: if (output == null) {
356: InputStream raw = getClass().getResourceAsStream(
357: DwrConstants.PACKAGE + PathConstants.FILE_HELP);
358: if (raw == null) {
359: log.error(Messages.getString(
360: "DefaultProcessor.MissingHelp",
361: PathConstants.FILE_HELP));
362: output = "<p>Failed to read help text from resource file. Check dwr.jar is built to include html files.</p>";
363: } else {
364: StringBuffer fileBuffer = new StringBuffer();
365:
366: BufferedReader in = new BufferedReader(
367: new InputStreamReader(raw));
368: while (true) {
369: try {
370: String line = in.readLine();
371: if (line == null) {
372: break;
373: }
374:
375: fileBuffer.append(line);
376: fileBuffer.append('\n');
377: } catch (IOException ex) {
378: fileBuffer.append(ex.toString());
379: fileBuffer.append('\n');
380: break;
381: }
382: }
383:
384: output = fileBuffer.toString();
385: }
386:
387: scriptCache.put(PathConstants.FILE_HELP, output);
388: }
389:
390: buffer.append(output);
391: }
392:
393: buffer.append("</body></html>\n");
394:
395: return buffer.toString();
396: }
397:
398: /* (non-Javadoc)
399: * @see org.directwebremoting.DebugPageGenerator#generateInterfaceUrl(java.lang.String, java.lang.String)
400: */
401: @Deprecated
402: public String generateInterfaceUrl(String root, String scriptName) {
403: return root + interfaceHandlerUrl + scriptName
404: + PathConstants.EXTENSION_JS;
405: }
406:
407: /* (non-Javadoc)
408: * @see org.directwebremoting.DebugPageGenerator#generateEngineUrl(java.lang.String)
409: */
410: @Deprecated
411: public String generateEngineUrl(String root) {
412: return root + engineHandlerUrl;
413: }
414:
415: /* (non-Javadoc)
416: * @see org.directwebremoting.DebugPageGenerator#generateLibraryUrl(java.lang.String, java.lang.String)
417: */
418: @Deprecated
419: public String generateLibraryUrl(String root, String library) {
420: return root + library;
421: }
422:
423: /* (non-Javadoc)
424: * @see org.directwebremoting.DebugPageGenerator#getAvailableLibraries()
425: */
426: @Deprecated
427: public Collection<String> getAvailableLibraries() {
428: if (availableLibraries == null) {
429: availableLibraries = Collections
430: .unmodifiableCollection(Arrays
431: .asList(utilHandlerUrl));
432: }
433:
434: return availableLibraries;
435: }
436:
437: /**
438: * Accessor for the DefaultCreatorManager that we configure
439: * @param converterManager The new DefaultConverterManager
440: */
441: public void setConverterManager(ConverterManager converterManager) {
442: this .converterManager = converterManager;
443: }
444:
445: /**
446: * Accessor for the DefaultCreatorManager that we configure
447: * @param creatorManager The new DefaultConverterManager
448: */
449: public void setCreatorManager(CreatorManager creatorManager) {
450: this .creatorManager = creatorManager;
451: }
452:
453: /**
454: * Accessor for the security manager
455: * @param accessControl The accessControl to set.
456: */
457: public void setAccessControl(AccessControl accessControl) {
458: this .accessControl = accessControl;
459: }
460:
461: /**
462: * @param engineHandlerUrl the engineHandlerUrl to set
463: */
464: public void setEngineHandlerUrl(String engineHandlerUrl) {
465: this .engineHandlerUrl = engineHandlerUrl;
466: }
467:
468: /**
469: * @param utilHandlerUrl the utilHandlerUrl to set
470: */
471: public void setUtilHandlerUrl(String utilHandlerUrl) {
472: this .utilHandlerUrl = utilHandlerUrl;
473: }
474:
475: /**
476: * @param testHandlerUrl the testHandlerUrl to set
477: */
478: public void setTestHandlerUrl(String testHandlerUrl) {
479: this .testHandlerUrl = testHandlerUrl;
480: }
481:
482: /**
483: * Setter for the URL that this handler available on
484: * @param interfaceHandlerUrl the interfaceHandlerUrl to set
485: */
486: public void setInterfaceHandlerUrl(String interfaceHandlerUrl) {
487: this .interfaceHandlerUrl = interfaceHandlerUrl;
488: }
489:
490: /**
491: * The URL for the {@link EngineHandler}
492: */
493: protected String engineHandlerUrl;
494:
495: /**
496: * The URL for the {@link UtilHandler}
497: */
498: protected String utilHandlerUrl;
499:
500: /**
501: * The URL for the {@link UtilHandler}
502: */
503: protected String testHandlerUrl;
504:
505: /**
506: * What URL is this handler available on?
507: */
508: protected String interfaceHandlerUrl;
509:
510: /**
511: * How we convert parameters
512: */
513: protected ConverterManager converterManager = null;
514:
515: /**
516: * How we create new beans
517: */
518: protected CreatorManager creatorManager = null;
519:
520: /**
521: * The security manager
522: */
523: protected AccessControl accessControl = null;
524:
525: /**
526: * We cache the script output for speed
527: */
528: protected final Map<String, String> scriptCache = new HashMap<String, String>();
529:
530: /**
531: * For getAvailableLibraries() - just a RO Collection that currently returns
532: * only util.js, but may be expanded in the future.
533: */
534: private Collection<String> availableLibraries = null;
535:
536: /**
537: * 2 dots
538: */
539: private static final String PATH_UP = "..";
540:
541: /**
542: * The log stream
543: */
544: private static final Log log = LogFactory
545: .getLog(DefaultDebugPageGenerator.class);
546: }
|