ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

JSP Security for Limiting Access to Application-Internal URLs
Pages: 1, 2, 3

Let's examine each of the three transition tags of the welcome state. A transition tag has three attributes: condition, next_state, and type. The condition attribute specifies the condition under which the state transition is performed. If it is omitted, a default condition is assumed. The default condition is the condition that is used if no other conditions are specified. The first transition tag specifies that the default state transition should be back to the welcome state. This would occur if the user simply re-requested the same page. The second transition tag specifies that if the input condition set by checkstart.jsp is start then the application should transition to the question state. The third transition tag specifies that if the input condition set by checkstart.jsp is help, then the application should transition to the help state. Note that the type attribute is set. There are three types of state transitions: goto (the default), call, and return. Most transitions are of the type goto. The call transition type pushes the current state onto a stack before moving to the new state. The return type pops the state at the top of the stack and moves to that state. The specification of the help state contains an example of the return type.



When the application is in the question state, it displays a question to the user and then processes the user's response via checkanswer.jsp. It moves to the question, response, or help states, based on the condition that is set by checkanswer.jsp.

The response state is the state that the application is in when it displays a response to a user's answer. It displays the response via response.jsp, checks the response via checkfinished.jsp, and then transitions to the response, question, finished, or help states, based on the condition that is set by checkfinished.jsp.

The finished state is the state that the application is in when the user completes the exam. It displays the exam results via finished.jsp and then transitions back to the welcome state.

The help state is called by the application to display a help page (via help.jsp). The checkreturn.jsp page is used to determine whether the application should return from the help state or continue to display help.jsp.

From XML to JSP

The question that should be on your mind is, "OK, if I specify an application's XML file, how do I generate the application page?" That's another easy question to answer. I wrote an XSL transform that creates a JSP file from the XML file. It is available here. Save the file as machina.xsl.

If you're not familiar with XSLT, you can find more information at the W3C Web site. Don't worry about needing to learn the syntax of XSLT. All you have to do is be able to run an XSLT translator to transform your XML files to JSP files using machina.xsl. There are a number of XSLT translators that can be used to do the job. I like Instant Saxon and Xalan. Both tools come with excellent documentation.

For example, after downloading and installing Xalan, you can use the following command to translate exam.xml (the above document) to exam.jsp:

java org.apache.xalan.xslt.Process -in exam.xml -xsl machina.xsl -out exam.jsp

Make sure that exam.xml, machina.dtd, and machina.xsl are in the same directory.

Inside exam.jsp

The exam.jsp file that is generated from exam.xml (via machina.xsl) has the following content. This file is the master application file that users request to interact with the exam application.

<%@ page import="java.util.*" %>

<%!
// State machine data
static final String smName = "javasecurityexam";
static final String smInitialState = "welcome";

// State names
static final String[] states = {"welcome", "question", "response", "finished", "help"};

// Output actions corresponding to each state
static final String[] outputActions = {"welcome.jsp", "question.jsp", "response.jsp", "finished.jsp", "help.jsp"};

// Input actions corresponding to each state
static final String[] inputActions = {"checkstart.jsp", "checkanswer.jsp", "checkfinished.jsp", "", "checkreturn.jsp"};

// Input conditions that are defined for each state
static final String[][] inputConditions = {
{"default", "start", "help"},
{"default", "correct", "incorrect", "help"},
{"default", "nextQuestion", "finished", "help"},
{"default"},
{"default", "return"}
};

// Number of transitions defined for each state.
static final int[] numTransitions = {3, 4, 4, 1, 2};

// Cumulative number of transitions defined for all states before
// a particular state. Used to index outputActions, transitionType,
// and nextState.
static final int[] cumulativeTransitions = {0, 3, 7, 11, 12};

// State transition type (goto, call, or return) associated with a state transition.
static final String[] transitionTypes = {"goto", "goto", "call", "goto", "goto", "goto", "call", "goto", "goto", "goto", "call", "goto", "goto", "return"};

// Next state associated with a state transition.
static final String[] nextStates = {"welcome", "question", "help", "question", "response", "response", "help", "response", "question", "finished", "help", "welcome", "help", ""};

// Returns the index of state in states.
static final int getStateIndex(String state) {
    for(int i=0; i<states.length; ++i) {
        if(states[i].equals(state)) return i;
    }
    return -1;
}

// Returns the index of (state, condition) in transitionType and nextStates.
static int getConditionIndex(int stateIndex, String condition) {
    if(stateIndex == -1) return -1;
    for(int i=0; i<inputConditions[stateIndex].length; ++i) {
        if(inputConditions[stateIndex][i].equals(condition)) {
            return (cumulativeTransitions[stateIndex] + i);
        }
    }
    return -1;
}
%>

<%
// Get the instance's ID
String smID = request.getParameter("smID");
// Define state and stack variables
String state = "";
Stack stack = null;
// Define sm and smInstance Hashtable objects
Hashtable sm = null;
Hashtable smInstance = null;
boolean newInstance = false;
// Check for start/restart
synchronized(session) {
    // sm links to all sm instances for this session
    sm = (Hashtable) session.getAttribute(smName);
    // First time sm is used in this session?
    if(sm == null) {
        // Create a new sm
        sm = new Hashtable();
        // Set the number of instances to 0
        sm.put("instanceCount", "0");
    }
    // Obtain access to an smInstance
    synchronized(sm) {
        // Start a new instance?
        if(smID == null || sm.get(smID) == null) {
            newInstance = true;
            String instanceCountString = (String) sm.get("instanceCount");
            int instanceCount = Integer.decode(instanceCountString).intValue();
            // Create an instance of this sm
            smInstance = new Hashtable();
            // Give it a state and a stack
            smInstance.put("state", smInitialState);
            smInstance.put("stack", new Stack());
            // Link sm to the new instance
            sm.put("instanceCount", ""+(instanceCount+1));
            sm.put("" + instanceCount, smInstance);
            // Update sm in the session
            session.setAttribute(smName, sm);
            smID = instanceCountString;
        }
        smInstance = (Hashtable) sm.get(smID);
        state = (String) smInstance.get("state");
        stack = (Stack) smInstance.get("stack");
    }
}
// Notify called pages of sm's name and ID (in case of new smID)
request.setAttribute("smName", smName);
request.setAttribute("smID", smID);
// Perform input actions
int stateIndex = getStateIndex(state);
// If new instance of application then just display page
if(newInstance) {
    response.sendRedirect(request.getRequestURI()+"?smID="+smID);
    return;
}
// Otherwise perform input action, transition, and display new page
String inputAction = inputActions[stateIndex];
if(!inputAction.equals("")) pageContext.include(inputAction);
// Get condition from request
String condition = (String) request.getAttribute("smCondition");
// If condition is not defined, try the default
if(condition == null) condition = "default";
int conditionIndex = getConditionIndex(stateIndex, condition);
// If condition is not defined and it is not the default, try the default condition
if(conditionIndex == -1 && !condition.equals("default")) {
    condition = "default";
    conditionIndex = getConditionIndex(stateIndex, condition);
}
// If no valid condition, simply redisplay the current page
if(conditionIndex == -1) {
    pageContext.include(outputActions[stateIndex]);
    return;
}
// Change state
String transitionType = transitionTypes[conditionIndex];
String nextState = nextStates[conditionIndex];
if(transitionType.equals("call")) stack.push(state);
else if(transitionType.equals("return")) nextState = (String) stack.pop();
// Save state and stack in smInstance
synchronized(session) {
    sm = (Hashtable) session.getAttribute(smName);
    synchronized(sm) {
        smInstance = (Hashtable) sm.get(smID);
        synchronized(smInstance) {
            smInstance.put("state", nextState);
            smInstance.put("stack", stack);
        }
        sm.put(smID, smInstance);
    }
    session.setAttribute(smName, sm);
}
// Display output
pageContext.include(outputActions[getStateIndex(nextState)]);
%>

Integrating Application Files

One thing that you'll appreciate is that you'll never have to edit the master application file (e.g., exam.jsp). However, you should make a pass through it to understand how it works.

The exam.jsp file begins by defining a number of field variables:

  • smName -- The application's name.
  • smInitialState -- The initial state of the application.
  • states -- The application states.
  • outputActions -- The output actions of each state.
  • inputActions -- The input actions of each state.
  • inputConditions -- The input conditions that are defined for each state.
  • numTransitions -- The number of transitions of each state.
  • cumulativeTransitions -- The cumulative number of transitions defined for all states before a particular state.
  • transitionTypes -- The types of each transition.
  • nextStates -- The state to which each transition moves.

In addition, the getStateIndex() and getConditionIndex() methods are defined. The getStateIndex() method returns the index of a state in states. The getConditionIndex() method returns the index of a state and condition in transitionType and nextStates.

The applications that are generated via machina.xsl provide the capability to execute multiple instances of an application within a single user session. This enables the user to open more than one browser window and execute the application independently in each window. For example, if your application is an online XML editor, the user will be able to edit different XML documents in separate windows. The individual application instance is tracked via an smID parameter that accompanies each user request. You are responsible for propagating this parameter in your application's URLs. Don't worry -- I'll provide you with several examples of how this is accomplished.

Besides propagating the smID parameter, your input actions are required to set input conditions. This is accomplished by setting the current request's smCondition attribute. Again, you'll see more of this in the exam example.

Pages: 1, 2, 3

Next Pagearrow