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.

I've talked about what name-values pairs the application scope, session scope, and request scope contain. But I haven't said when and where to set the name-value pairs in those scopes, or have I? Recall that in the previous section, I said a script servlet initializes the HTTP script context for every HTTP request the servlet receives in the getContext() method. The getContext() method calls the initialize() method on the HTTP script context to set the name-value pairs in the three scopes.

An HTTP script context keeps references to a Servlet, an HttpServletRequest, and an HttpServletResponse, and has methods such as getServlet(), getRequest(), and getResponse for you to get to those references. When getContext() calls initialize(), it passes the initialize() method a Servlet, an HttpServletRequest, and an HttpServletResponse. The initialize() method then initializes the HTTP script context's references to a Servlet, an HttpServletRequest, and an HttpServletResponse with those three parameters. A script servlet needs to initialize the HTTP script context for every HTTP request, otherwise the name-value pairs in the request scope, session scope, and application scope might become stale.

Before we leave this section, let's talk more about the code. The construct that JSR 223 defines to model the HTTP script context is the javax.script.http.HttpScriptContext interface. Note that HttpScriptContext extends javax.script.ScriptContext. This supports my earlier statement that an HTTP script context is a script context. The investment we made in learning about script context will now prove rewarding: Because HttpScriptContext extends ScriptContext, from our knowledge of ScriptContext, we immediately know that we can get and set scopes in an HTTP script context by calling ScriptContext's getBindings() and setBindings() methods. And we also know that we can get and set name-value pairs in a scope by calling ScriptContext's getAttribute() and setAttribute() methods, or by calling the get() and put() method on the scope itself.

If you look at the JSR 223 reference implementation, you'll notice that it provides an implementation of the HttpScriptContext interface in javax.script.http.GenericHttpScriptContext. Unfortunately, it is dated and cannot work with Java SE 6 beta build 80. So I implemented SimpleHttpScriptContext as a quick fix for GenericHttpScriptContext. The problem with GenericHttpScriptContext is that it's missing the implementation of two methods: getBindings() and setBindings(). To fix that, I made SimpleHttpScriptContext inherit everything from GenericHttpScriptContext and augmented it with the two missing methods.

Besides the methods for accessing scopes and name-value pairs that it inherits from ScriptContext, HttpScriptContext has methods for getting various configurations as defined in JSR 223's Web scripting framework.

Configurations

The specification's Web scripting framework defines a handful of configurations that administrators can set to control the execution of script engines. By administrators, I mean people in charge of deploying a script engine in a servlet container. Their roles differ from those of script engine providers. So far in this article, I have talked about script servlet and HTTP script context. It's the script engine provider's responsibility to meet the requirements and consider the details related to those two concepts. If you play the role of administrator and your main purpose of learning Web scripting is so that you know how to deploy a script engine in a servlet container, you could ignore the previous text and just start here.

For those readers who play the role of script engine providers, it's your responsibility to implement in the script engine the expected behavior of the configurations. Otherwise, administrators will be surprised when they deploy your script engine. Let's first look at how administrators deploy script engines in a servlet container. Then we'll look at what script engine providers need to do to implement the expected behavior of the configurations.

To administrators, the work of deploying a script engine in a servlet container consists of two parts: deploying the engine's script servlet and setting configurations. In our discussion of a script servlet, we learned that a script servlet is really a bona fide servlet. So it should not come as a surprise that deploying a script servlet in a servlet container is the same as deploying a typical servlet.

Using Tomcat as an example, here's how I deploy the BoolScript engine's script servlet: First, I add a context entry in the file {CATALINA_HOME}\conf\server.xml. The context entry represents the Web application that will host our script engine and script code. We have already seen the context entry and where to put it in the section "Set Up the Example Code." Next, I want to tell the servlet container that all requests for BoolScript files (i.e., files with .bool file extensions) are to be handled by the BoolScript script servlet. This is done in {CATALINA_HOME}\webapps\BoolScript\WEB-INF\web.xml like this:

 

<servlet> <servlet-name>BoolScriptServlet</servlet-name> <servlet-class> net.sf.model4lang.boolscript.engine.BoolScriptServlet </servlet-class> </servlet>

<servlet-mapping> <servlet-name>BoolScriptServlet</servlet-name> <url-pattern>*.bool</url-pattern> </servlet-mapping>

As you can see, the XML settings above reference the Java class BoolScriptServlet. That class is in the boolscript.jar file, which we place in the {CATALINA_HOME}\webapps\BoolScript\WEB-INF\lib folder so that the servlet container can find it.

Now, the BoolScript engine is deployed in the servlet container, and we are ready to set the configurations that will control the execution of the BoolScript engine within the Web application. The table below lists all the configurations defined in JSR 223's Web scripting framework.

NameExample valuesDefault valueDescription
script-disable true, falsefalse Whether the script servlet should execute scripts at all
script-use-session true, falsetrueWhether scripts executed by the script servlet should have access to HTTP session information
script-methods GET, POSTGET, POSTA comma-delimited list of HTTP request methods the script servlet accepts
script-directory/etc/xyzno directoryThe directory where script files reside
script-display-resultstrue, falsefalseWhether the script servlet should output evaluation result of a script file
allow-languagesbool scriptAny languageA comma-delimited list of script languages the script servlet accepts

Most of the configurations are self-explanatory. Here, I discuss script-disable and script-directory in more detail. You can refer to JSR 223's Web scripting framework if you need more information on other configurations.

Look at {CATALINA_HOME}\webapps\BoolScript\WEB-INF\web.xml and you will see that I set the configurations like this:

 

<context-param> <param-name>script-disable</param-name> <param-value>false</param-value> </context-param>

<context-param> <param-name>script-use-session</param-name> <param-value>false</param-value> </context-param>

<context-param> <param-name>script-methods</param-name> <param-value>GET,POST</param-value> </context-param>

<context-param> <param-name>script-display-results</param-name> <param-value>true</param-value> </context-param>

The script-disable setting controls whether the BoolScript script servlet should execute scripts at all. If you set it to true, you will get a message that says "Access to the specified resource has been forbidden," or something similar, in the Web browser when you browse to http://localhost:8080/BoolScript/logic.bool.

For those readers who play the role of script engine providers, let's see how we implement this configuration behavior in the BoolScript engine. The code is in GenericHttpScriptServlet.java and looks like this:

  //Check Web Scripting configurations.
   //We only perform check on script-disable here as an example.
        if(httpScriptCtxt.disableScript()) {
            httpRes.setStatus(403);
            return;
        }
        
        //Perform check on the rest of configurations such as 
        //script-use-session, script-methods, allow-languages here.

As mentioned earlier, this is boilerplate code that every script servlet will implement, and in the production release of Java SE 6, we will most likely get this code for free by deriving our script servlet class from some class similar to GenericHttpScriptServlet. The GenericHttpScriptServlet class, being a temporary fix, doesn't check all the configurations. It only performs a check on the disable-script configuration as an example.

Besides disable-script, another configuration I'd like to talk about in more detail is script-directory. In web.xml, I didn't set any value for script-directory. When the script-directory setting is missing, that means javax.script.http.HttpScriptContext's getScriptSource() method should follow the same rules used to locate JSP code to locate script code. The file BoolScript-binary.zip contains sample script code in the file logic.bool, and we place it in the {CATALINA_HOME}\webapps\BoolScript folder because that's where we would put the Web application's JSP code if any existed.

Conclusion

In this article, we took the BoolScript language from my previous article, equipped it with Web scripting capabilities, deployed it in the Tomcat servlet container, and tested our work by browsing to a sample script file in a Web browser. You can get any future updates to the BoolScript language from the Model4Lang open source project. The project also has a JSR 223 implementation for the Scheme programming language.

Chaur Wu is a software developer and published author. He has coauthored books on design patterns and software modeling. He is the project administrator of Model4Lang, an open source project dedicated to a model-based approach to language design and construction.

Learn more about this topic