Skip to main content

skip to main content

developerWorks  >  Web development  >

Remote scripting using a servlet

How to give Web applications interactivity and dynamism that you'd expect from desktop apps

developerWorks

Return to article.


Listing 8


// eHatcher Solutions, Inc

import java.lang.reflect.*;
import java.io.*;
import java.net.URLEncoder;
import java.text.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

/**
 * Base class for remote scripting servlets that use Microsoft's remote scripting (MSRS) or JavaScript remote scripting (JSRS).
 *
 * Usage:
 * <pre>
 * public class RSExample extends RemoteScriptingServlet {
 *    public static String getSubcategories (String catstr) throws Exception
 *    {
 *      String retval = "";
 *        // ... implementation details
 *      return retval;
 *    }
 * }
 * </pre>
 *
 * @version1.0  February 10, 2001
 * @authorErik Hatcher
 */

abstract public class RemoteScriptingServlet extends HttpServlet {

    boolean debugOn = false;

    private final static int MSRS = 0;
    private final static int JSRS = 1;

    private int clientType;

    /**
     * Generates the appropriate response to either an MSRS or JSRS method invocation.
     */
    public void doGet(HttpServletRequest request, HttpServletResponse response)
        throws IOException, ServletException
    {
        // Requests look like this:
        // MSRS: <servletname>?_method=method=execute=1=1
        // JSRS: <servletname>?C=callback=method=[1]

        debugOn = request.getParameter("debug") != null;
        debug("-------");

        boolean error = false;
        String returnValue;
        String callbackName = "";

        // Everything is wrapped in a try/catch block, any exception will cause the ERROR return value
        // so that the client side can deal with it gracefully
        try {
          String method;
          int pcount = 0;

          callbackName = request.getParameter("C");
          if (callbackName != null) {
            // client is JSRS - it passes a "C" parameter
            clientType = JSRS;
            method = request.getParameter("F");

            // JSRS doesn't tell us how many parameters, so count them
            while (request.getParameter("P" + pcount) != null)
              pcount++;
          }
          else {
            clientType = MSRS;
            method = request.getParameter("_method");
            pcount = Integer.parseInt(request.getParameter("pcount"));
          }

          debug("clientType = " + clientType);
          debug("Method = " + method);
          debug("pcount = " + pcount);
          // build paramSpec array with all String class items
          // build params array with the p0, p1, ...., pN request values
          Class paramSpec[] = new Class[pcount];
          Class stringClass = Class.forName("java.lang.String");
          String params[] = new String[pcount];
          if (pcount > 0) {
            for (int i=0; i < pcount; i++) {
              paramSpec[i] = stringClass;
              params[i] = request.getParameter(( (clientType == MSRS) ? "p" : "P") + i);

              if (clientType == JSRS) {
                // JSRS sends parameters wrapped with brackets, strip them off
                params[i] = params[i].substring(1, params[i].length() - 1);
              }

              debug("p" + i + " = " + params[i]);
            }
          }

          // find and invoke the appropriate static method in the concrete class
          Class c = this.getClass();
          Method m = c.getMethod(method, paramSpec);
          returnValue = (String) m.invoke(null, params);
        } catch (Exception e) {
          // if the invoked method threw an exception, pull it out of the wrapper exception that "invoke" throws
          // so that the client gets the real error message
          if (e instanceof InvocationTargetException) {
            e = (Exception) ((InvocationTargetException)e).getTargetException();
          }
          debug("Oops, exception: " + e);
          error = true;
          returnValue = e.toString();
          e.printStackTrace();
        }

        String outputString = "";
        if (clientType == MSRS) {
          // Build the appropriate MSRS response
          // Microsoft's Remote Scripting has three types: SIMPLE, EVAL_OBJECT, and ERROR.
          // Currently only SIMPLE and ERROR are supported.
          outputString = "<METHOD VERSION=\"1.0.8044\"><RETURN_VALUE TYPE=" + (error ? "ERROR" : "SIMPLE") + ">" + encode(returnValue) + "</RETURN_VALUE></METHOD>";
        }
        else {
          // Build the appropriate JSRS response
          outputString = "<html><head></head><body onload=\"p=document.layers?parentLayer:window.parent;";
          if (error) {
            outputString += "p.jsrsError('" + callbackName + "','jsrsError: " + encode(returnValue) + "');\">jsrsError: " + jsrsErrorEscape(returnValue);
          }
          else {
            outputString += "p.jsrsLoaded('" + callbackName + "');\">jsrsPayload:<br><form name=\"jsrs_Form\"><textarea name=\"jsrs_Payload\">" + jsrsEscape(returnValue) + "</textarea></form>";
          }
          outputString += "</body></html>";
        }

        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.println(outputString);
    }

    private void debug (String str)
    {
      if (debugOn) System.out.println("[RemoteScriptingServlet] " + str);
    }

    public static String jsrsEscape (String str)
    {
      StringBuffer sb = new StringBuffer(str);

      // probably an easier way to do this, but this will do for now
      for (int i = 0; i < sb.length(); i++) {
        if (sb.charAt(i) == '/') {
          sb.replace(i,i+1,"\\/");
          i += 2;
        }
      }

      return new String(sb);
    }

    public static String jsrsErrorEscape (String str)
    {
      StringBuffer sb = new StringBuffer(str);

      // probably an easier way to do this, but this will do for now
      for (int i = 0; i < sb.length(); i++) {
        if (sb.charAt(i) == '\'') {
          sb.replace(i,i+1,"\\'");
          i += 2;
        }
        if (sb.charAt(i) == '\"') {
          sb.replace(i,i+1,"\\\"");
          i += 2;
        }
      }

      return new String(sb);
    }
    public static String encode (String str)
    {
      // but URLEncoder.encode isn't enough... '+' should really be "%20"
      StringBuffer sb = new StringBuffer(URLEncoder.encode(str));

      for (int i = 0; i < sb.length(); i++) {
        if (sb.charAt(i) == '+') {
          sb.replace(i,i+1,"%20");
          i += 2;
        }
      }

      return new String(sb);
    }
/*
MSRS return details:

String return:
<METHOD VERSION="1.0.8044"><RETURN_VALUE TYPE=SIMPLE>%3CMessages%3E%3CMsg7%20ID%3D%221%22%3EMsg7%20data%3C/Msg7%3E%3C/Messages%3E</RETURN_VALUE></METHOD>

Error return:
<METHOD VERSION="1.0.8044"><RETURN_VALUE TYPE=ERROR>xyz%20%3A%20not%20a%20public%20function</RETURN_VALUE></METHOD>
*/

/*
JSRS return details:

Successful JSRS return:
<html><head></head><body onload="p=document.layers?parentLayer:window.parent;p.jsrsLoaded('jsrs1');">jsrsPayload:<br><form name="jsrs_Form"><textarea name="jsrs_Payload">string~TEST</textarea></form></body></html>

Error JSRS return:
<html><head></head><body onload="p=document.layers?parentLayer:window.parent;p.jsrsError('jsrs1','error');">error</body></html>
*/

}

Return to article.