Speak your own programming language with Web scripting

An introduction to JSR 223's Web scripting framework

The Web scripting capability of Java Specification Request (JSR) 223, Scripting for the Java Platform, specifies what a script engine must implement so that code written in the script language can run in a servlet container. In this article, we will equip BoolScript, a simple Boolean language I introduced in my previous JavaWorld article, "Build Your Own Scripting Language for Java," with Web scripting. If we draw an analogy between BoolScript and JavaServer Pages (JSP) in regards to Web scripting, we can see that essentially what we will be doing in this article is turning our BoolScript engine into something like a JSP engine. A JSP engine allows JSP code to run in a servlet container. Similarly, a BoolScript engine allows BoolScript code to run in a servlet container.

Before we begin, a word of caution is in order: JSR 223 implementation is still a work in progress. The implementation of Web scripting is not yet included in Java Platform, Standard Edition 6 beta build 80 (Java SE is Sun's new name for J2SE), the latest build as of this writing. Besides the Java SE beta, another source we can turn to for a JSR 223 implementation is the reference implementation. However, this reference implementation is dated to the extent that it is broken if you use it with build 80. In the article's example code, I spent minimum effort fixing those issues since they are temporarily due to the beta quality of the Java SE build. Although I can't speak for the Java SE development community, I believe the implementation and specification will undergo some changes, but the concepts and overall structure will remain. And in the production release of Java SE 6, the temporary issues will all be fixed. For now, think of my temporary fixes as an opportunity to see how things work behind the scenes.

This article doesn't require much knowledge of Java servlets. If you know the use of a servlet context in a servlet and the basics of deploying a servlet in a servlet container, you know enough to proceed.

Set up the example code

To set up the environment for this article's example code, you need three things:

  1. Java SE 6 beta build 80. You can download it from java.net.
  2. A servlet container. In this article, I use Tomcat 5.5 as an example. You can download Tomcat 5.5 from Apache Tomcat.
  3. The JSR 223 reference implementation. You can download that from the JSR 223 Webpage.

The article's code example, which can be downloaded from Resources, comes in two files:

  1. BoolScript-src.zip contains the source code of the BoolScript engine
  2. BoolScript-binary.zip contains the binary of the BoolScript engine and some other files that we'll look at in detail in this article's "Configurations" section

After you download all the required software, you must perform the following steps to set up and run the example code.

  • Install Java SE 6 beta build 80 and Tomcat 5.5. Following Tomcat conventions, I use {CATALINA_HOME} to refer to the folder where Tomcat is installed.
  • Extract the contents of BoolScript-binary.zip into {CATALINA_HOME}\webapps. If you use a different servlet container, you must extract BoolScript-binary.zip into the folder where you would deploy a Web application.
  • Extract the contents of the JSR 223 reference implementation into a temporary folder and copy the file jars\script.jar into {CATALINA_HOME}\webapps\BoolScript\WEB_INF\lib.
  • Open the file {CATALINA_HOME}\conf\server.xml and add a context entry like this:

     <Server>
      ...
      <Service>
        ...
        <Engine>
          ...
    <Host>
            ...
      <Context path="/BoolScript" docBase="BoolScript" 
    debug="0" reloadable="true" />
            ...
          </Host>
        </Engine>
      </Service>
    </Server>
    
    

Now you are ready to test the example. Ensure the Tomcat server is up and running. Open a Web browser and type in this URL: http://localhost:8080/BoolScript/logic.bool. If everything is set up correctly, the result you will see should look like this: [true, false, true].

The figure below and subsequent list describe the sequence of events that occur when you run the above test. The rest of the article explains the various parts of this figure in detail.

Overview of example. Click on thumbnail to view full-sized image.
  1. Web browser sends an HTTP request to Web server
  2. Servlet container dispatches the HTTP request to our script servlet
  3. The script servlet calls the getScriptSource() method on the HTTP script context to get the script source file logic.bool
  4. HTTP script context locates logic.bool
  5. HTTP script context reads in the file logic.bool
  6. HTTP script context returns the contents of logic.bool back to the script servlet
  7. The script servlet passes the contents to the script engine for evaluation
  8. The script engine returns the result of evaluation back to the script servlet
  9. The script servlet returns the result back to the servlet container
  10. Web server returns the result back to the Web browser

From the figure and the list above, you can see that the script servlet and the HTTP script context are the key components that carry out script evaluation in a servlet container. Let's first look at the script servlet in detail. Then I'll explain the HTTP script context and its functionalities. After those discussions, we will look at the configurations that glue together all the parties depicted in the figure above.

Script servlet

JSR 223's Web scripting framework consists of mainly three items: script servlet, HTTP script context, and configurations. (Note: Though these are not standard terms, they prove helpful for our discussion.) Let's discuss them one-by-one, starting with the script servlet. A script servlet is really just a servlet (i.e., an instance of GenericServlet). A servlet performs its tasks in its service() method and so does a script servlet. Like a typical servlet, a script servlet performs in its service() method tasks such as processing an HTTP request and stuffing Web contents in an HTTP response. Unlike a typical servlet, a script servlet, while performing its tasks, runs script code in one or more script engines.

Now that I've explained a script servlet at a conceptual level, let's talk about the code. A servlet must directly or indirectly derive from javax.servlet.GenericServlet. In this article's example code, we implement a script servlet class called BoolScriptServlet. It inherits GenericHttpScriptServlet, which in turn inherits GenericServlet. From this, we confirm that our script servlet is a bona fide servlet as I claimed it must be.

If you look at the JSR 223 reference implementation, you'll notice that it provides a subtype of GenericServlet called HttpScriptServlet in the javax.script.http package. HttpScriptServlet is a convenience class on which script engine providers can base their script servlet classes. Unfortunately, it is dated and cannot work with Java SE beta 6 build 80. Otherwise I would have derived BoolScriptServlet from HttpScriptServlet instead of GenericHttpScriptServlet. I implemented GenericHttpScriptServlet as a quick fix of the issues I found in HttpScriptServlet.

While I was at it, I also improved the implementation a bit by declaring an abstract method called runScript() in GenericHttpScriptServlet. This is helpful because the bulk of GenericHttpScriptServlet's service() method is boilerplate code necessary to make a script servlet class conform to JSR 223's Web scripting framework. If a script servlet class derives from GenericHttpScriptServlet, the only part it will most likely want to change in the inherited service() method is how it will invoke a script engine to execute script code. By deriving from GenericHttpScriptServlet, a script servlet class gets to keep the boilerplate code in the service() method and, at the same time, has the flexibility of customizing how it will invoke a script engine in the runScript() method.

Another important method is the getContext() method in BoolScriptServlet.java:

 public HttpScriptContext getContext(HttpServletRequest request, HttpServletResponse response) throws ServletException {
   if (ctx == null)
      ctx = new SimpleHttpScriptContext();
      
   ctx.initialize(this, request, response);
   return ctx;
}

The getContext() method returns an HTTP script context (i.e., an instance of some class that implements the interface javax.script.http.HttpScriptContext), the topic of the next section. For now, the important thing to know is that a script servlet should initialize the HTTP script context for every HTTP request the servlet receives. And the place where a script servlet does that initialization is in its getContext() method. The line of code in bold above shows that the getContext() method calls the initialize() method on the HTTP script context to initialize it before returning it back to the caller. We will see in the next section what the initialization does and why a script servlet should initialize the HTTP script context for every HTTP request.

HTTP script context

A servlet runs in a servlet context. Since a script servlet is a servlet, it also runs in a servlet context. But in addition to a servlet context, a script servlet has another context that a typical servlet doesn't have called an HTTP script context (i.e., an instance of some class that implements interface javax.script.http.HttpScriptContext). The purpose of a servlet context is to provide a servlet with a view of the Web application the servlet runs in. The purpose of an HTTP script context is to provide a script (i.e., code written in a script language) with a view of the script engine the script runs in.

An HTTP script context is a script context, which I discussed in my previous article. So whatever I said about script context there still holds true for our discussion of the HTTP script context here. To refresh what we learned, recall that a script context has zero or multiple scopes; a scope is basically just a collection of name-value pairs. In the previous article, I discussed global and engine scopes.

So what's new about HTTP script context? Not much except that it is required to have three special scopes and has some methods for accessing configuration information, which I'll talk about later. The three special scopes are request scope, session scope, and application scope. Why do we need those three scopes and what do we do with them? Recall that in the previous article, I described a script context as a means for you to pass data back and forth between a host Java application and a script engine. Similarly, an HTTP script context is a means for you to pass data back and forth between a host Web application and a script engine. When a host Web application invokes a servlet upon an HTTP request, it passes the servlet the HTTP request and the corresponding HTTP response.

When everything is in Java code, no data needs to be passed across the boundary between Java and a script language. So no script context is needed. But if the servlet is a script servlet that uses script engine(s) to evaluate script code, an HTTP script context is needed for carrying Web-related data across the language boundary. To be neat and tidy, JSR 223's Web scripting framework requires that we put Web-related data in the request scope, session scope, and application scope of an HTTP script context.

The names of the three scopes are self-descriptive. The request scope contains information about an HTTP request, as shown in the following code:

 

//Type of httpReq is HttpServletRequest httpReq.setAttribute(nameX, valueX);

//Type of httpScriptCtx is HttpScriptContext httpScriptCtx.setAttribute(nameX, valueX, ScriptContext.REQUEST_SCOPE)

//The two method calls above have the same effect

valueX1 = httpReq.getAttribute(nameX)

valueX2 = httpScriptCtx.getAttribute(nameX, ScriptContext.REQUEST_SCOPE)

//The two method calls above have the same effect //and valueX1 = valueX2 = valueX

Similarly, the session scope contains information about an HTTP session, as shown in the following code:

 

//Type of httpSession is HttpSession httpSession.setAttribute(nameX, valueX);

//Type of httpScriptCtx is HttpScriptContext httpScriptCtx.setAttribute(nameX, valueX, ScriptContext.SESSION_SCOPE)

//The two method calls above have the same effect

valueX1 = httpSession.getAttribute(nameX)

valueX2 = httpScriptCtx.getAttribute(nameX, ScriptContext.SESSION_SCOPE)

//The two method calls above have the same effect //and valueX1 = valueX2 = valueX

The application scope contains information about a Web application, as shown in the following code:

 

//Type of servletCtx is ServletContext servletCtx.setAttribute(nameX, valueX);

//Type of httpScriptCtx is HttpScriptContext httpScriptCtx.setAttribute(nameX, valueX, ScriptContext.APPLICATION_SCOPE)

//The two method calls above have the same effect

valueX1 = servletCtx.getAttribute(nameX)

valueX2 = httpScriptCtx.getAttribute(nameX, ScriptContext.APPLICATION_SCOPE)

//The two method calls above have the same effect //and valueX1 = valueX2 = valueX

The distinction and relationship between a servlet context and an HTTP script context should become clearer with an explanation of the application scope: A servlet context and an HTTP script context are two separate contexts each of which exists for its own purpose. They are related to each other via the HTTP script context's application scope because the application scope of an HTTP script context is a servlet context.

1 2 Page 1
Page 1 of 2