FRAMEWORK 101: Building XCF – Lesson 02 – Request Processing

October 12th, 2006

In the last lesson, you built the core of our framework: the key interfaces, logging facilities, and the facade. The goal of that last lesson was to use the facade pattern to insure sub-systems would be highly decoupled from each other and the client applications that invoke them. The primary mechanism used to accomplish this was the facade design pattern. And the primary interface was one where behavior was invoked via submitting requests. Today, you will build the interfaces and supporting classes needed to model request processing.

Course outline:

NOTE: You can download the source code for today’s lesson here: Lesson 02 Source Code. The most up to date source can be found on sourceforge here.

What you will be building today

In the last lesson you built an infrastructure that allowed you to build a facade that you could submit a request for processing. Today you will build the infrastructure used to process the requests submitted to the facade. When you are done, you will again test it with the code you used from the last lesson.


  REQUEST_SimpleTest request = new REQUEST_SimpleTest("account", "login");
  request.setParameter("userName", "myloginname");
  request.setParameter("password", "mypassword");
  facade.process(request);

The difference is this time your test will first validate that the userName and password parameters exist and then execute a command that will print the request to the console.

Understanding Request Processing

Request processing is typically composed of the following actions:

  • Process Parameters: The request parameters may need validation or tranformations. For example, parameters may need to fall into a range or tranformed to numeric values (or perhaps even used to lookup model objects).
  • Execute Behavior: The request was invoked specifically to generate a behavior (like loging a user into the system).
  • Render Response: When the request is finished, we may need to send a response message to facade client. The response action is an important one. Rather than go over it here, we will dedicate an entire lesson to it (Lesson 04)

In XCF Version 5, these actions were accomplished with three separate sub-systems. It turns out that that was a design error. From a cohesion point of view, the behaviors were sliced to thin. (For more on cohesion, see the OOAD lessons). That is XCF 5 has too many objects to represent these actions. The mistake was in viewing these actions as three different things. They are really one thing, and that is request processing.

Composite Structures

The design pattern that best fit the problem was the Composite Structural pattern:

From Design Patterns, the intent is to: compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly.

For request processing, the composite pattern is useful because:

  • it can be used to represent complex request processing as a tree of objects
  • the root of each tree to can be attached modules
  • the modules don’t have to know they are dealing with a tree — they simply process the object they have a reference to

Request Processing Design

The design is based on the composite design pattern. From Lesson 01, we know that XCFRequest objects contain a module and an operation. The module binds to an XCFModule object. That model object, then, will likely contain a hash that binds the operation to a composite object that will be used to process the request.

code will look like:


   void process(XCFRequest request) throws XCFException {
     XCFProcessing instruction = instructions.get(request.getOperation());
     instruction.process(request);
   }

The class diagram for the design is:

To test this design, you will build a module called “account” that contains one operation: “login”. This operation will have two parameters: userName and password. Both parameters are required. If both parameters are present, then the operation will execute a command called CMD_PrintRequest. This command simply prints the request on the console. Given the above design, the object model for this processing is:

When the module “account” recieves the request. The following happens:

  • The module “account” looks up the instruction bound to “login”
  • The module “account” invokes the process method on that instruction
  • That instruction is a composite instruction, so it iterates through its children executing the process method for each one.
  • The first child instruction is a parameter processing instruction for the “userName” parameter. This instruction will execute all its child instructions. In this case it has just one, the “required” validation rule. When this instruction executes, it will check for the presense of the “userName” parameter. If the parameter exists, processing will continue. If it doesn’t, processing will stop.
  • The second child instruction is a parameter processing instruction for the “password” parameter. This instruction is like the one for the “userName” parameter, but it checks for the existence of “password”.
  • The third child instruction is the command CMD_PrintRequest. This command will print the request to the console.

The Request Processing Instruction Interface: XCFProcessingInstruction


package com.eternal.xcf.request.processor;

import com.eternal.xcf.core.XCFException;
import com.eternal.xcf.core.XCFRequest;

/**
 * This is the interface implemented by classes used to process requests.  There several type of processing instructions.
 * The most common types are:
 *   Commands (implemented by INSTRUCTION_Command) -- these objects are the primary handlers for requests
 *   Validators (implemented by INSTRUCTION_Validator and XCFValidator) -- these objects are used to validate
 *   	request parameters.
 *   Setters (implemented by INSTRUCTION_Setter and XCFSetter) -- these objects are used to transform request
 *   	parameter values to native objects in the context.
 *   Parameter Processor (implemented by INSTRUCTION_ParameterProcessor) -- these objects act as containers for
 *      validator and setter objects.  There is typically one of these for each request parameter processed.
 *   Composite (implemented by INSTRUCTION_Composite) -- these objects act as containers for other processing instructions.
 *   	There is typcially one of these object bound to a module operation.  This object will typically contain one or more
 *   	child insructions.  
 *   
 * @author Sonjaya Tandon
 *
 */
public interface XCFProcessingInstruction {
	
	/**
	 * Gets the processing instructions name
	 * @return
	 */
	public String getName();
	
	/**
	 * This is used to set properties, such as range or address.
	 * @param propertyName
	 * @param propertyValue
	 */
	public void setProperty(String propertyName, Object propertyValue);
	
	/**
	 * Returns the object associated with the propertyName.  If this instance does not contain
	 * the property, then returns the getProperty of the container (if a container exists).
	 * @param propertyName
	 * @return
	 */
	public Object getProperty(String propertyName);

	/**
	 * Sets this instruction's container
	 * @param container
	 */
	public void setContainer(XCFProcessingInstruction container);
	
	/**
	 * Gets this instructions container.
	 * @return
	 */
	public XCFProcessingInstruction getContainer();
	
	/**
	 * Process an instruction using the request as input
	 * @param request
	 * @return true if processing should continue, false if it should stop
	 * @throws XCFException
	 */
	boolean process(XCFRequest request) throws XCFException;

}

The key method here is of course process. It looks very much like the process method of facade and module with one significant and important difference. The process methods for facade and module are void methods (i.e. no return value). This process method returns a boolean. A false value is returned if processing was stopped (as in the case where one of the parameters validations failed). This is a very useful piece of information during request processing. However, this information is not carried through to the module and facade level because doing so would imply a synchronous communication mechasinm with clients of the facade. There are many technology stacks which are based on asynchronous communication. And while it is relatively easy to interface a system designed for asynchronous communication to one that accepts synchronous communication, going the other way is not always possible. So at the module and facade level, we use an interface where the path of least resistence is asynchronous.

The next methods of note are the composite support methods: getContainer and setContainer. This allows for all instructions to have a parent instruction. Below you will see the definition of the composite instruction. Having a parent container supports the concept of scope: something very relevant to the next set of methods.

The methods setProperty and getProperty allow the setting of name value pairs on the instruction. While processing instructions, there are three locations that may contain values: instruction properties, the request object, the request context. The instruction properties (which relate to the get and set property method) are values unique to the instruction. The properties may include range values on an integer validation or the location in the context space to store the address. Used in conjuction with scope, if the property is not defined in the instruction, the instruction should then return the getProperty value of its container. Values in request objects and the request context are not related to the set and get Property. The values in the request object are the actual parameter values being processed and have the same lifecycle as the request. The request context may have a lifecycle that spans multiple request as do the values it contains.

Not all instructions need a name, but the getName method is there for those that do. If the instruction is a parameter processor, then the name will be the name of the parameter it processes. If the instruction is the root instruction used to handle an operation, then the name will be the name of the operation.

The Base Instruction: INSTRUCTION_Base

All instructions that implement XCFProcessingInstruction should extend INSTRUCTION_Base. By extending this class, XCFProcessingInstruction implemtations need only implement process.


package com.eternal.xcf.request.processor.instructions;

import java.util.HashMap;

import com.eternal.xcf.request.processor.XCFProcessingInstruction;

/**
 * This is a base utility class for request processing instructions.  Though it is not required to 
 * extend this class, most classes should as it provides an implementation for basic parameter processing functionality.
 * 
 * @author sonjaya
 *
 */
public abstract class INSTRUCTION_Base implements XCFProcessingInstruction {
	private String name;
	private XCFProcessingInstruction container = null;
	private HashMap properties = new HashMap();

	/**
	 * This constructor is used by processing instructions, like commands, that do not
	 * require a name.
	 *
	 */
	public INSTRUCTION_Base() {
	}
	
	/**
	 * This constructor is used by those processing instructions, such as parameter processors, that do 
	 * require a name.
	 * 
	 * @param name
	 */
	protected INSTRUCTION_Base(String name) {
		this.name = name;
	}

	/**
	 * Sets this instruction's container
	 * @param container
	 */	
	public XCFProcessingInstruction getContainer() {
		return container;
	}

	/**
	 * Gets this instructions container.
	 * @return
	 */
	public void setContainer(XCFProcessingInstruction container) {
		this.container = container;
	}

	/**
	 * Returns the instructions name
	 */
	public final String getName() {
		return name;
	}

	/**
	 * Returns the object associated with the propertyName.  If this instance does not contain
	 * the property, then returns the getProperty of the container (if a container exists).
	 * @param propertyName
	 * @return
	 */
	public Object getProperty(String propertyName) {
		Object propertyValue = properties.get(propertyName);
		if (propertyValue == null && container != null) {
			propertyValue = container.getProperty(propertyName);
		}
		return propertyValue;
	}

	/**
	 * This is used to set properties, such as range or address.
	 * @param propertyName
	 * @param propertyValue
	 */
	public void setProperty(String propertyName, Object propertyValue) {
		properties.put(propertyName, propertyValue);
	}
}

Composite Instructions: INSTRUCTION_Composite

Central to the request processing design is the composite instruction class. This class contains child instructions. It implements process by processing all child instructions. It also acts as the container for those child instructions. It also adds a method called addInstruction. This method is used during the building process (more on that in a future article) to add child instructions. Note the absence of a removeInstruction method. This is a slight deviation from the composite design pattern. However, it is very uncommon for request processing to alter once the initial building process is completed. In the rare case where that is needed, a new class should be introduced (rather than modifying this one).


/**
 * This class is used to hold multiple instructions.  Invoking process on an instance of this object will 
 * cause process to be invoked on all the child instructions.
 * @author sonjaya
 *
 */
public class INSTRUCTION_Composite extends INSTRUCTION_Base {
	private ArrayList instructions = new ArrayList();

	public INSTRUCTION_Composite(String name) {
		super(name);
	}
	
	/**
	 * Process this request by processing all the child instructions.
	 * Stop as soon as one returns false (and return false) or return
	 * true if all instructions process.
	 * 
	 * TODO: Add a switch that allows composite to process all instructions
	 * even when one fails.
	 */
	public boolean process(XCFRequest request) throws XCFException {
		for (XCFProcessingInstruction instruction : instructions) {
			if (instruction.process(request) == false) return false;	
		}
		return true;
	}
	
	/**
	 * Add a child instruction to the composite.
	 * @param instruction
	 */
	public void addInstruction(XCFProcessingInstruction instruction) {
		instruction.setContainer(this);
		instructions.add(instruction);
	}

}

Parameter Processing

It is often useful to process the request parameters prior to handling the request. There are typically two types of request processing: validation and context setting. Validation is the act of checking the parameter value, insuring its existence, or satisfying some business rule. Context setting is the act of copying the parameter value from the request to the request context. In the process of copying the parameter value, the values type may be transformed from a string to some other type of value. Parameter processing is supported with a specicialization of the composite instruction, an interface that validation objects implement, an interface that setter objects implment and two hidden classes which adapt the instruction interface to the two new interfaces.

XCFValidator

Classes that validate parameters will implement the XCFValidator interface. Note how this interface does not extend XCFProcessingInstruction. During this refactoring pass, the XCFValidator class and XCFSetter class caused me great consternation as I kept trying to stuff them into the XCFProcessingInstruction family of interfaces. After all validating and setting perform the same overall behavior: that of parameter processing. But that was my error. The parameter processing design is based on the composite design pattern: a structural pattern. And structurally validators and setters are very different than other types of instructions. They are not composites. There is not an instance of each one per module operation. Rather, validators and setters are flyweight objects: that is there is typically only one instance of a validator class per facade. This is classic example of what I term an “impedence mismatch”. To solve an impedance mismatch we will have two different intefaces (in this case XCFProcessingInstruction and XCFValidator) and an adapter class (INSTRUCTION_Validator) which adapts one interface to another.


package com.eternal.xcf.request.parameter;

import com.eternal.xcf.core.XCFException;
import com.eternal.xcf.core.XCFRequest;
import com.eternal.xcf.request.processor.XCFProcessingInstruction;

/**
 * This interface is implemented by objects that are used to validate
 *   	request parameters.
 * @author sonjaya
 *
 */
public interface XCFValidator {
	
	/**
	 * Validate the parameter identified by parameterSpecification.getName()
	 * @param req
	 * @param parameterSpecification
	 * @return true if the parameter is valid, false otherwise.
	 * @throws XCFException
	 */
	boolean validate(XCFRequest req, XCFProcessingInstruction parameterSpecification) throws XCFException;

}

The following is an example of a validation flyweight object that validates that a parameter exists.


package com.eternal.xcf.request.parameter.validator;

import com.eternal.xcf.core.XCFException;
import com.eternal.xcf.core.XCFRequest;
import com.eternal.xcf.request.parameter.XCFValidator;
import com.eternal.xcf.request.processor.XCFProcessingInstruction;

/**
 * This validator checks for the presense of the request parameter.  It returns true if the parameter exists
 * and false otherwise.
 * @author sonjaya
 *
 */
public class VALIDATOR_Required implements XCFValidator {

	public boolean validate(XCFRequest req,	XCFProcessingInstruction parameterSpecification)
			throws XCFException {
		boolean exists = req.parameterExists(parameterSpecification.getName());
		
		if (!exists) {
			// TODO req.addNotice(-- message -- ) We will add this in a later lesson
		}
		return exists;
	}

}

INSTRUCTION_Validator

This class adapts the XCFProcessingInstruction interface to the XCFValidator interface. Each instance of this class will point to the flyweight instance of the validator. For example, if there are 20 parameters that are required, then there will be 20 instances of this class that point to the one instance of the required flyweight object.


package com.eternal.xcf.request.processor.instructions;

import com.eternal.xcf.core.XCFException;
import com.eternal.xcf.core.XCFRequest;
import com.eternal.xcf.request.parameter.XCFValidator;

/**
 * This package level class is used to contain an XCFValidator.  Using this class as a container
 * allows for easier code migration from XCF 5.
 * @author sonjaya
 *
 */
final class INSTRUCTION_Validator extends INSTRUCTION_Base {

	private final XCFValidator validator;
	
	/**
	 * Invoke the super constructor.  Name is the name of the 
	 * request parameter.  The validator is what is used to 
	 * process the parameter.
	 * @param name
	 * @param validator
	 */
	INSTRUCTION_Validator(String name, XCFValidator validator) {
		super(name);
		this.validator = validator;
	}
	
	/**
	 * Process the parameter by invoking the validators validate
	 * method.  Return the results.
	 */
	public boolean process(XCFRequest request) throws XCFException {
		return validator.validate(request, this);
	}

}

XCFSetter

The job of setter objects is to move parameter values from the request object to the context object. I will go into more depth on setters in the lesson I cover contexts. The same impedance issues which exist with XCFValidator also exist here. The same mechanism is used: that is using the new interface (XCFSetter) and a class that adapts XCFProcessingInstruction (INSTRUCTION_Setter) to that interface.


package com.eternal.xcf.request.parameter;

import com.eternal.xcf.core.XCFException;
import com.eternal.xcf.core.XCFRequest;
import com.eternal.xcf.request.processor.XCFProcessingInstruction;

/**
 * This interface is implemented by objects that are used to transform request
 *   	parameter values to native objects in the context.
 * @author sonjaya
 *
 */
public interface XCFSetter {
	
	/**
	 * Transform the parameter identified by parameterSpecification.getName to a context value
	 * @param req
	 * @param parameterSpecification
	 * @throws XCFException
	 */
	void set(XCFRequest req, XCFProcessingInstruction parameterSpecification) throws XCFException;
}

INSTRUCTION_Setter


package com.eternal.xcf.request.processor.instructions;

import com.eternal.xcf.core.XCFException;
import com.eternal.xcf.core.XCFRequest;
import com.eternal.xcf.request.parameter.XCFSetter;

/**
 * This package level class is used to contain an XCFSetter.  Using this class as a container
 * allows for easier code migration from XCF 5.
 * @author sonjaya
 *
 */
final class INSTRUCTION_Setter extends INSTRUCTION_Base {

	private final XCFSetter setter;
	
	/**
	 * Invoke the super constructor.  Name is the name of the 
	 * request parameter.  The setter is what is used to 
	 * process the parameter.
	 * @param name
	 * @param validator
	 */
	INSTRUCTION_Setter(String name, XCFSetter setter) {
		super(name);
		this.setter = setter;
	}
	
	/**
	 * Process the parameter by invoking the setter's set method.
	 * Never interrupt processing by returning true.
	 */
	public boolean process(XCFRequest request) throws XCFException {
		setter.set(request, this);
		return true;
	}
}

INSTRUCTION_ParameterProcessor

This class is a special case of the composite instruction. There will be an instance of this per parameter that is processed. It contains two new methods which are used during the building process: addValidator and addSetter. These methods simply take as input the validator or setter flyweight and wrap those objects in their respective instruction adapters. The instruction adapters are then added as child instructions to the parameter processor.


package com.eternal.xcf.request.processor.instructions;

import com.eternal.xcf.request.parameter.XCFSetter;
import com.eternal.xcf.request.parameter.XCFValidator;
import com.eternal.xcf.request.processor.XCFProcessingInstruction;

/**
 * This class is a special purpose composite class.  It is intended to be used as
 * a container for instructions that process a single request parameter.  It contains
 * two types of instructions: setters and validators.
 * 
 * It's name is the name of the parameter it is processing.
 * @author sonjaya
 *
 */
public final class INSTRUCTION_ParameterProcessor extends INSTRUCTION_Composite {

	/**
	 * Invoke the parent constructor.  Name should be the parameterName of the 
	 * request parameter this instruction processes.
	 * @param name
	 */
	public INSTRUCTION_ParameterProcessor(String name) {
		super(name);
	}
	
	/**
	 * Adds a setter child instruction.
	 * @param setter
	 * @return
	 */
	public XCFProcessingInstruction addSetter(XCFSetter setter) {
		XCFProcessingInstruction instruction = new INSTRUCTION_Setter(getName(), setter);
		addInstruction(instruction);
		return instruction;
	}

	/**
	 * Adds a validator child instruction
	 * @param validator
	 * @return
	 */
	public XCFProcessingInstruction addValidator(XCFValidator validator) {
		XCFProcessingInstruction instruction = new INSTRUCTION_Validator(getName(), validator);
		addInstruction(instruction);
		return instruction;
	}
}

The Base Command: INSTRUCTION_Command

The bread and butter of XCF based applications are command objects. Command objects are responsible for implementing the core behavior of request processing. XCF application developers will spend most of their time implementing or maintaining these objects. Rather than implementing process, command objects implement an execute method. This execute method has the same interface as process with one important distinction: it is a void method. This is done because it is very rare to want to interupt request processing after executing a command. If that behavior is needed, simply create your own concrete implementation of XCFProcessingInstruction.


package com.eternal.xcf.request.processor.instructions;

import com.eternal.xcf.core.XCFException;
import com.eternal.xcf.core.XCFRequest;

/**
 * This is a base utility class for by command objects.  Processing instructions which simply want to 'execute'
 * and never interupt processing should extend this class.
 * 
 * This class is also provided as a bridge to XCF5 to ease code migration.
 * 
 * @author sonjaya
 *
 */
public abstract class INSTRUCTION_Command extends INSTRUCTION_Base {

	/**
	 * Child classes implment this method to provide request handling logic.
	 * @param request
	 * @throws XCFException
	 */
	public abstract void execute(XCFRequest request) throws XCFException;
	
	/**
	 * Process the request by calling the execute method.  Never interupt
	 * processing by returning true.
	 */
	public final boolean process(XCFRequest request) throws XCFException {
		execute(request);
		
		return true;
	}
}

Testing it out

To test this out, you will need to build a facade object (as depicted in the object diagram above) that contains and “account” module with one operation handler called “login”. This “login” handler will be an instance INSTRUCTION_Composite and contain a parameter processor for “userName” and one for “password”. It will also contain an instance of a test command class called CMD_PrintRequest.

CMD_PrintRequest


package com.eternal.stubs;

import java.util.Iterator;

import com.eternal.xcf.core.XCFException;
import com.eternal.xcf.core.XCFFacade;
import com.eternal.xcf.core.XCFRequest;
import com.eternal.xcf.request.processor.instructions.INSTRUCTION_Command;

public class CMD_PrintRequest extends INSTRUCTION_Command {

	/**
	 * Execute the command by logging out:
	 *   [module].[operation]([paramName1]=[paramValue1],...)
	 */
	public void execute(XCFRequest request) throws XCFException {
		XCFFacade facade = request.getContext().getFacade();
		
		StringBuffer buf = new StringBuffer();
		buf.append("PROCESSING " + request.getModule() + "." + request.getOperation() + "(");
		Iterator paramNames =  request.getParameterNames();
		
		String separator = "";
		while (paramNames.hasNext()) {
			String paramName = paramNames.next();
			buf.append(separator);
			buf.append(paramName + "=" + request.getParameter(paramName));
			separator=", ";
		}
		buf.append(")");
		facade.logInfo(buf.toString());

	}
}

CONTEXT_Simple

Because CMD_PrintRequest accesses the facade via the context, you will need to provide a context object in your test. For now, you only need to implement the getter and setter on the facade.


package com.eternal.stubs;

import com.eternal.xcf.core.XCFContext;
import com.eternal.xcf.core.XCFException;
import com.eternal.xcf.core.XCFFacade;

public class CONTEXT_Simple implements XCFContext {
	XCFFacade facade;

	// for the purposes of this example, we only need
	// a get/set facade.
	public void setFacade(XCFFacade facade) {
		this.facade = facade;
	}

	public XCFFacade getFacade() throws XCFException {
		return facade;
	}

	public Object getValue(String address) throws XCFException {
		
		return null;
	}

	public void putValue(String address, Object value) throws XCFException {
	}
}

MODULE_Instructions

In the last lesson you created a module class that processed every request by printing out that request to the console. This time you want to test out the XCFProcessingInstruction design. Implement process such that it now looks up an operation handler and invoke that handlers process method.


package com.eternal.stubs;

import java.util.HashMap;

import com.eternal.xcf.core.XCFException;
import com.eternal.xcf.core.XCFFacade;
import com.eternal.xcf.core.XCFModule;
import com.eternal.xcf.core.XCFRequest;
import com.eternal.xcf.request.processor.XCFProcessingInstruction;

public class MODULE_Instructions implements XCFModule {

	String name;
	String defaultOperation;
	XCFFacade facade;
	HashMap operations = new HashMap();
	
	public void addOperationHandler(String operation, XCFProcessingInstruction handler) {
		operations.put(operation, handler);
	}

	public String getDefaultOperation() {
		return defaultOperation;
	}

	public String getName() {
		return name;
	}

	public void setDefaultOperation(String defaultOperation) {
		this.defaultOperation = defaultOperation;
	}

	public void setFacade(XCFFacade facade) {
		this.facade = facade;
	}

	public void setName(String name) {
		this.name = name;
	}

	/**
	 * Process the request looking up the operation handler and invoking
	 * the process method on that handler.
	 */
	public void process(XCFRequest req) throws XCFException {
		XCFProcessingInstruction handler = operations.get(req.getOperation());
		handler.process(req);

	}
}

TestSimpleFacade

Test out the XCFProcessingInstruction design with this simple JUnit class.


package com.eternal.xcf.request.test;

import com.eternal.stubs.CMD_PrintRequest;
import com.eternal.stubs.CONTEXT_Simple;
import com.eternal.stubs.MODULE_Instructions;
import com.eternal.stubs.REQUEST_SimpleTest;
import com.eternal.xcf.core.XCFFacade;
import com.eternal.xcf.request.parameter.XCFValidator;
import com.eternal.xcf.request.parameter.validator.VALIDATOR_Required;
import com.eternal.xcf.request.processor.XCFProcessingInstruction;
import com.eternal.xcf.request.processor.instructions.INSTRUCTION_Composite;
import com.eternal.xcf.request.processor.instructions.INSTRUCTION_ParameterProcessor;

import junit.framework.TestCase;

public class TestSimpleFacade extends TestCase {
	private XCFFacade facade;

	protected void setUp() throws Exception {
		super.setUp();

		// Build the facade.  The facade will support 
		// requests to an account module.
		facade = new XCFFacade();
		MODULE_Instructions accountModule = new MODULE_Instructions();
		
		// create the "required" flyweight validation object
		XCFValidator required = new VALIDATOR_Required();
		
		//////////////////////////////////////////////////////////////////////////////////////////
		// create the request handler for the "login" operation using the following specification
		//   operation name: login
		//     parameter 1: userName
		//      validation: required
		//     parameter 2: password
		//      validation: required
		//   execute: CMD_PrintRequest
		INSTRUCTION_Composite handler = new INSTRUCTION_Composite("login");
		
		// create the "userName" parameter processor
		INSTRUCTION_ParameterProcessor paramSpecification = new INSTRUCTION_ParameterProcessor("userName");
		paramSpecification.addValidator(required);
		handler.addInstruction(paramSpecification);
		
		// create the "password" parameter processor
		paramSpecification = new INSTRUCTION_ParameterProcessor("password");
		paramSpecification.addValidator(required);
		handler.addInstruction(paramSpecification);
		
		// create the command we will execute
		XCFProcessingInstruction command = new CMD_PrintRequest();
		handler.addInstruction(command);
		
		// add the operation handler to the module
		accountModule.addOperationHandler("login", handler);
		//////////////////////////////////////////////////////////////////////////////////////////

		// add the account module to the facade
		facade.putModule("account", accountModule);
	}

	protected void tearDown() throws Exception {
		super.tearDown();
	}
	
	public void testSimpleRequestProcess() throws Exception {
		// create the request account.login(userName=myloginname, password=mypassword)
		REQUEST_SimpleTest request = new REQUEST_SimpleTest("account", "login");
		request.setContext(new CONTEXT_Simple());
		request.getContext().setFacade(facade);
		
		request.setParameter("userName", "myloginname");
		request.setParameter("password", "mypassword");
		
		// process the request
		facade.process(request);
	}
}

Response Generation

One thing that we did not talk about in this lesson is response generation. Validations likly send user readable messages back to the request sender. A request may result in the display of a new screen or update of an existing screen. Response generation is highly decoupled from request processing and requires its own lesson. Designing for this decoupling makes the path of least resistance one where request processing is asynchronous message safe.

Conclusion

In the last lesson you built an infastructure that allowed you to decouple sub-systems from their clients and each others by interfacing via a request based interface. In this lesson you built the supporting interfaces and classes needed to model request processing. It is important to note that the goal here is not to create a java replacement or a scripting language. Though you could add instructions that support the notion of branching and looping, I would not recommend it. Use Java where java is strong. If you have complex logic, encapsulate that in a command. Use the composite XCFProcessingInstructions to model how to process requests. The design is very flexible and extensible and in future lessons I will show you how to extend it to model complex control logic and how to define builder models in XML.

Entry Filed under: Software Development,XCF

Trackbacks

4 Comments Add your own

Leave a Comment

You must be logged in to post a comment.

Trackback this post  |  Subscribe to the comments via RSS Feed


Calendar

October 2006
M T W T F S S
« Sep   Nov »
 1
2345678
9101112131415
16171819202122
23242526272829
3031  

Most Recent Posts