FRAMEWORK 101: Building XCF – Lesson 06 – Rendering Responses

February 10th, 2007

All along, the goal of this framework has been to insure that objects inside the framework are completely decoupled from the technology stack. Adding AJAX or switching to a thick client should not result in changes to existing objects. When the object is model oriented, this is easily accomplished. By their very nature, model objects are decoupled from the views and the controllers that select the views. However, when the object in question is a controller object, the difficulty of the goal increases. Controllers are responsible for selecting which views to display and can end up being coupled to the rendering mechanism as a result. In today’s lesson, I will show you how to achieve this goal by abstracting the response rendering mechanism.

Course outline:

NOTE: You can download the source code for today’s lesson here: Lesson 06 Source Code

Or, you can get the most up to date version of XCF available on sourceforge.

What you will be building today

Once again you will build the simple login example that you have been building in all the other lessons. The difference today, however, is that you will finally render a response. Furthermore, you will simulate your example working in an HTML based web-application and an NIO based thick client application. The XML configuration that will define your example is:


 
 
   com.eternal.stubs
 
 
   command,.CMD_
   response,.RESPONSE_
  
 
 
   
      
       
     
     
       
     
     
       
         
           
         
         
           
         
       
   
 
 
   
     /account/home.jsp
   
   
     /account/login.jsp
   
 
 
   
     account.display-home
   
   
     account.handle-login-failed
   
 


A walk through our example

The last lesson walked you through building the facade. Today I will walk you through execution of built facade. The above xml builds the following facade:

For the walk through, I will step through the processing of the request account.login(user-name="aname", password="apassword"). I will then step through rendering an html response. When you submit the request to the facade, the first thing the facade does is ask the account module to process the request.

The account module, in turn, will lookup the INSTRUCTION_Composite bound to the login operation and ask that instruction to process the request.

The first instruction is a parameter processor that validates that the user-name parameter exists.

In our example, it does. The next instruction validates that password parameter exists, which it does. Next, the composite asks CMD_PrintRequest to process the request.

Now, if this were an actual application, you would probably execute a command called CMD_Login. This command would use a model object to attempt to login the user. If the login were successful, it would put the context into a “success” state. If it failed, it would put the context into a “failure” state. But, this is not a real application. It is an example and all CMD_PrintRequest does is print out the request. After it does that, the next composite asks the next instruction to process the request.

That instruction is INSTRUCTION_On. This instruction checks the result state and executes the child instruction bound to that state. If no result state has been set, the result state is assumed to be “success”; which is how we get to our next instruction.

That instruction simple selects the response named account.home. At this point request processing is complete. This is where things get a little interesting. The request processing login has executed its behavior and selected a response identifier. Now, you need only ask for the response renderer. Your facade, however, supports two different types of response renderers: one that renders html to a browser and one that sends TCP/IP (using NIO) messages to a thick client. For the purposes of this walk through, I will assume you ask for the html renderer for account.home.

Again, since this is an example, the response is rendered by RESPONSE_JspStub. This stub class simply prints out the name of the JSP file you would use to render the response. If this were an actual application, the renderer class would use the underlying servlet to execute the JSP.

The problem you are solving

The goal has been to decouple the processing logic from the technology stack. So, for example, if your application is a servlet, you don’t want your processing logic to really know it is in a servlet. That way, if you need to make the same logic avaible to another service, like SOAP, for example, it is simply a matter of reconfiguring vs. rewriting.

By abstracting request processing, you moved towards this goal by removing any coupling the processing logic might have from the request invocation. The problem you are solving today is to remove the coupling your processing logic might have from response generation.

The simple login functionality is a good illustration of what you are trying to achieve. In a servlet application, it is not uncommon to have the following login processing logic:


   if (User.login(userName, password)) {
      // select application home page jsp
   } else {
      // select can't login jsp 
   }

However, if your login application was part of a simple tcp/ip application using asynchronous communication, the logic would be:


   if (User.login(userName, password)) {
      // send login.successful message
   } else {
      // send login.failed message
   }

Because of this slight change in response generation, when you switch technology stacks, you have to make minor modifications to your processing logic. This can be simplified if your processing logic selects a response using an identifier. Then have a general purpose object render the selected response. In that case your processing logic can change to:


   if (User.login(userName, password)) {
      // select login-successful id
   } else {
      // select login-failed id
   }

This is a pretty good improvement as the processing logic doesn’t need to change as you change technology stacks, but you can do even better. In a servlet, after a successful login, you typically want to launch the main application page. On the other hand, if this processing logic was used as a service to support single sign on, then sending success and failure messages is probably sufficient. In the servlet case you would map the login-successful id to the application’s main page and in the single sign on service, you would map it to a renderer that simply sends a message. While this achieves our goal, the mapping of “login-success” to the invocation of the application home might be confusing and potentially troublesome if that home page requires some pre-processing logic. If you look at your login logic, there is an abstract form that will allow that logic to be used an any application:


   if (User.login(userName, password)) {
      // put the context in a success state
   } else {
      // put the context in a failure state
   }

Now, with a matter of simple configuration, you can alter the response renderning, but still retain the fundamental processing logic for login.

Strategy Pattern

You use the strategy pattern when you have a family of algorithms and want to make the algorithms interchangeable. The patterns lets you vary the algorithm independently from the clients that use them.

Strategy is a good fit because:

  • It allows multiple implementations for the same response.
  • Changes in the response implementation do not require any changes to the processing logic

Response Rendering Design

I haven’t talked about it much yet, but one of the most important methods in XCFRequest is getContext(). Each request contains a context object; think of it as a white board, that you read and write to. It is the mechanism that you use to pass information back and forth to other objects.

In the strategy design pattern, you store the concrete strategy you will use in the context. In your application, a single strategy is a specific response. The concrete strategy is the various objects that render that same response.

The design is simple. You break request processing into three steps:

  • process the request: You will process the request with your command objects. The command objects are responsible for setting a result in the context. Results are things like: success, failure, or abort.
  • map the result to an response id: By convention, the last thing request processing should do is look up the result and perform the appropriate processing for that result. At the very least, this processing should write a response identifier to the context.
  • render the response: Once request processing is done, you can use a render manager to lookup the response renderer. As long as you have written the response type to the context, the response manager can use the information in the context to find the appropriate response object for the response identifier and response type.

Response Rendering Implementation

XCFResponse

This interface represents the rendering strategy. The idea is that during configuration you add properties to the renderer, such as the name of a jsp file or parameters to populate, and then during request processing, you use the render method to render your response.


package com.eternal.xcf.core.response;


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


/**
 * Renders a specific response for a response type.
 * For example, there may be an response id called sec-dsp-home.
 * If the server supported three different clients (cell, browser, and 2D)
 * then there would be three XCFResponse objects defined for sec-dsp-home --
 * one for each client.
 * @author Sonjaya Tandon
 */
public interface XCFResponse {
	public static final String XCF_TAG = "response";

	public static String XCF_RESPONSE_TYPE = "request.xcf-client-type";
	public static String XCF_RESPONSE_ID = "request.xcf-response-id";
	
	public static String SUCCESS = "success";
	public static String FAILURE = "failure";
	
	
	/**
	 * Returns the response name.  
	 * @return
	 */
	String getName();

	/**
	 * Sets the response's name
	 */
	void setName(String responseId);
	
	/**
	 * Returns the property value associated with propertyName
	 * @param propertyName name of property
	 */
	Object getProperty(String propertyName);
	
	/**
	 * Sets a property value for the output.  For example, a typical property
	 * is the device the render uses to render the output (e.g. name
	 * of jsp file).
	 * @param propertyName the property name
	 * @param value the value stored in the property
	 */
	void setProperty(String propertyName, Object value);
	
	/**
	 * Generates the specifed response.  It is expected that the 
	 * nativeRequestInfo array contains the device used to render
	 * the output.
	 * @param req the request whose results need to be rendered
	 * @param nativeRequestInfo information from the native request which may be
	 * needed to render the output.
	 */
	public void render(XCFRequest req, Object[] nativeRequestInfo) throws XCFException;
}

To get an idea of what a concrete implementation would do, the following is the implementation of the render method from XCF 5:


	/**
	 * Renders the response by calling a JSP file.  It is expected that the response
	 * have a property that contains the location of the jsp file used to
	 * render the response.
	 */
	public void render(XCFRequest req, Object[] nativeRequestInfo) throws XCFException {
	    ServletContext servletContext = (ServletContext)nativeRequestInfo[XCFRequestFactory.NATIVE_CONTEXT];
		HttpServletRequest httpReq = (HttpServletRequest)nativeRequestInfo[XCFRequestFactory.NATIVE_REQ];
		HttpServletResponse httpResp = (HttpServletResponse)nativeRequestInfo[XCFRequestFactory.NATIVE_RES];
		
		String jspRenderer = (String)getProperty(JSP_FILE_NAME);
		Iterator params = getParameterNames();
		
		// make sure we have a rendering device
		if (jspRenderer == null) {
			throw new XCFException("OUTPUT CONFIGURATION ERROR: jsp property not supplied");
		}
		
		// for each parameter in the parameter list, fetch the value
		// from the context and set in the native httpResp for the
		// jsp to use as a bean.
		while (params.hasNext()) {
			String parameter = (String)params.next();
			String src = getParameterSource(parameter);
			
			// make sure we have a src for the parameter
			if (src == null) {
				throw new XCFException("OUTPUT CONFIGURATION ERROR: src not defined for " + parameter);
			}

			// lookup the value, if the src is blank, then
			// set the value to the request context
			Object value;
			if (src.trim().length() > 0) {
				value = req.getContext().getValue(src);
			} else {
				value = req.getContext();
			}
			
			// set the parameter in the response
			httpReq.setAttribute(parameter, value);
			
		}
		
		// Fetch the JSP rendering device
	    RequestDispatcher dispatcher = servletContext.getRequestDispatcher(jspRenderer);
	
		// make sure the device (jsp file) actually exists
	    if (dispatcher == null)
	    {
	       throw new XCFException("jsp output file not found");
	    }
	    
	    // use the jsp to render the output
	    try
	    {
	        dispatcher.include(httpReq,httpResp);
	    } catch (Exception e) {
	       throw new XCFException(e.getMessage());
	    }
	}

SERVICE_ResponseManager

The response rendering objects are actually flyweight objects. The SERVICE_ResponseManager object acts as the flyweight factory. It provides the ability to register an particular response and bind it to an identifier and a response type. It then provides the ability to fetch the response by only passing in the request object. It uses the information in the request object’s context to find the appropriate response renderer.


package com.eternal.xcf.core.response;

import java.util.HashMap;

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

/**
 * The response manager service stores all the response renderers used in the system.
 * @author Sonjaya Tandon
 */
public class SERVICE_ResponseManager implements XCFService {
	public static final String XCF_TAG = "xcf-renderer";
	private XCFFacade facade;
	private HashMap> responseRenderersByType = new HashMap>();

	public String getName() {
		return XCF_TAG;
	}

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

	public void setName(String name) {
	}

	public void start() throws XCFException {
	}

	public void stop() throws XCFException {
	}
	
	/**
	 * Looks up the response type and response id stored in the request and 
	 * returns the associated response render.  It is assumed that this method
	 * is ONLY called AFTER the request has been processes
	 * @param req a request sent to the server for processing
	 * @return the response render identified by the request
	 */ 
	public XCFResponse getResponse(XCFRequest req) throws XCFException {
		String responseType = (String)req.getContext().getValue(XCFResponse.XCF_RESPONSE_TYPE);
		String responseId = (String)req.getContext().getValue(XCFResponse.XCF_RESPONSE_ID);
		
		if (responseType == null) {
			throw new XCFException("Unable to render response: " + responseId + ".  Response Type never set.");
		}
		
		// get all the response renderes for this response type
		HashMap responseRenderers = responseRenderersByType.get(responseType); 
		if (responseRenderers == null) {
			throw new XCFException("Unable to render response: " + responseId + ".  No responses registered for: " + responseType);
		}
		
		// look up the response renderer and return it
		XCFResponse response = responseRenderers.get(responseId);
		
		if (response == null) {
			throw new XCFException("Unable to render response: " + responseId + ".  Response not registered for: " + responseType);
		}
		
		return response;
	}
	
	/**
	 * Registers a response into the system and associates it with a response type.
	 * @param responseType identifies the type of response the renderer will render to.
	 * responseType can be things like html, nio, 2D, 3D or cell
	 */
	public void registerResponse(String responseType, XCFResponse response) throws XCFException {
		HashMap responseRenderers = responseRenderersByType.get(responseType); 
		if (responseRenderers == null) {
			responseRenderers = new HashMap();
			responseRenderersByType.put(responseType, responseRenderers);
		}
		facade.logDebug("Registering " + response.getName() + " for type " + responseType);
		responseRenderers.put(response.getName(), response);
	}
	
}

UTIL_Helper

To remove the burden of having to remember where in the context to set and fetch things, it is useful to write a helper class.


package com.eternal.xcf.core.response;

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

/**
 * A set of static convinience methods used to aid processing of responses
 * @author sonjaya
 *
 */
public class UTIL_Helper {
	static final String ADDR_RESULT = "request.result-code";
	
	/**
	 * Sets a result in the request context
	 * @param request
	 * @param resultCode
	 * @throws XCFException
	 */
	public static void setResult(XCFRequest request, String resultCode) throws XCFException {
		request.getContext().putValue(ADDR_RESULT, resultCode);
	}
	
	/**
	 * Gets a result in the request context.  If there is no result there,
	 * return "success" as the result.
	 * @param request
	 * @return
	 * @throws XCFException
	 */
	public static String getResult(XCFRequest request) throws XCFException {
		String result = (String)request.getContext().getValue(ADDR_RESULT);
		
		if (result == null) {
			// assume if a result wasn't set, things were successful
			return XCFResponse.SUCCESS;
		}
		
		return result;
	}
	
	
	/**
	 * Sets the output in the request context.
	 * @param req
	 * @param outputId
	 * @throws XCFException
	 */
	public static void setResponse(XCFRequest req, String responseId) throws XCFException {
		req.getContext().putValue(XCFResponse.XCF_RESPONSE_ID, responseId);
	}
	
	/**
	 * Sets the current response type.
	 * @param req
	 * @param responseType
	 * @throws XCFException
	 */
	public static void setResponseType(XCFRequest req, String clientType) throws XCFException {
		req.getContext().putValue(XCFResponse.XCF_RESPONSE_TYPE, clientType);
	}

}

Changes to INSTRUCTION_Composite

Change INSTRUCTION_Composite so that it the result is set to failure if one of the child instructions fails to process:


	/**
	 * 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.
	 * 
	 */
	public boolean process(XCFRequest request) throws XCFException {
		for (XCFProcessingInstruction instruction : instructions) {
			if (instruction.process(request) == false) {
				UTIL_Helper.setResult(request, XCFResponse.FAILURE);
				return false;	
			}
		}
		return true;
	}

INSTRUCTION_On

Since you have gone through all the trouble of setting a response in the context, it will be useful to have an instruction that uses that response value. This instruction is very much like a composite in that it has multiple child instructions. Unlike the composite, however, it will only process the child instruction associated with the result set in the context.


import java.util.HashMap;

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

/**
 * This instruction checks the "result" value in the request context.
 * It will then execute the instruction associated with that value.
 * By default, if no "result" has been set, then the value will be 
 * "success".
 * @author sonjaya
 *
 */
public class INSTRUCTION_On extends INSTRUCTION_Composite {
	private HashMap instructionsByResult = new HashMap();
	private String result;

	////////////////////////////////////////////////
	// MEHTODS USED DURING BUILDING
	/**
	 * Sets the result currently being configured
	 */
	public void setCurrentResult(String result) {
		this.result = result;
	}

	/**
	 * Overide addInstruction by assoicating the instruction
	 * to the current result being configured.
	 */
	@Override
	public void addInstruction(XCFProcessingInstruction instruction) {
		assert result != null;
		instructionsByResult.put(result, instruction);
	}	

	////////////////////////////////////////////////
	// MEHTODS USED INSTRUCTION PROCESSING
	/**
	 * Look up the result and process the instruction associated 
	 * with that result.
	 */
	public boolean process(XCFRequest request) throws XCFException {
		String result = UTIL_Helper.getResult(request);
		XCFProcessingInstruction instruction = instructionsByResult.get(result);
		if (instruction == null) {
			throw new XCFException("There is no processing instruction for result: " + result);
		}
		return instruction.process(request);
	}
	
}

INSTRUCTION_SelectResponse

To make configuration easier, you will find it useful to have an instruction that simply selects a response using its name as the response identifier. You can see it in action in the XML configuration at the beginning of this lesson.


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

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

/**
 * This instruction sets the response id in the request context.
 * The instruction was configured such that it's name is the
 * desired response id.
 * @author sonjaya
 *
 */
public final class INSTRUCTION_SelectResponse extends INSTRUCTION_Base {

	/**
	 * Process the request by simply setting the response id to 
	 * this instructions name
	 */
	public boolean process(XCFRequest request) throws XCFException {
		UTIL_Helper.setResponse(request, getName());
		return true;
	}
}

SAXEL_On

This builder configures an “On” instruction. See the XML at the start of this lesson for an example of how that tag is used.


import org.xml.sax.Attributes;

import com.eternal.xcf.request.processor.XCFProcessingInstruction;
import com.eternal.xcf.request.processor.instructions.INSTRUCTION_On;
import com.eternal.xcf.request.processor.instructions.INSTRUCTION_Operation;
import com.eternal.xcf.sax.SAXEL_Composite;

/**
 * This SAXEL configures an "On" instruction.
 * 
 * This SAXEL assumes an INSTRUCTION_Composite exists in handler under the tag XCFProcessingInstruction.XCF_TAG
 * The sub tags under each result are intended to create instruction objects.
 * 
 * The syntax is of the form:
 *  
 *    <[result]>..sub tags...
 *    <[result]>..sub tags...
 *    ...
 *  
 *  
 * @author sonjaya
 *
 */
public class SAXEL_On extends SAXEL_Composite {
	public final static String XCF_TAG = "on";
	
	public SAXEL_On() {
		super(XCF_TAG);
	}

	/**
	 * Create an On instruction and add it to the current container.
	 * Maike the On instruction the container for all child tags
	 */
	@Override
	public void startInterpreting(String uri, String name, String rawName, Attributes attributes) {
		// get the instruction container
		INSTRUCTION_Operation container = null;
		try {
			container = (INSTRUCTION_Operation)getHandler().peekValue(XCFProcessingInstruction.XCF_TAG);
			if (container == null) {
				handleError(" failed because there was no container.");
				return;
			}
		} catch (ClassCastException e) {
			handleError(" failed because the container was not an INSTRUCTION_Operation.");
			return;
		}
		
		// create an On instruction, add it to the container
		INSTRUCTION_On instruction = new INSTRUCTION_On();
		container.addInstruction(instruction, true);
		getHandler().pushValue(XCFProcessingInstruction.XCF_TAG, instruction);
	}

	/**
	 * The first child tag will actually identify a result.  All child tags of that
	 * result are intended to build a composite instruction that is supposed to
	 * be executed when that result is set.
	 * 
	 * To do this, inject a SAXEL_Composite into the stack to represent the result.
	 * Set the current result for the On instruction.  This insures all instructions
	 * created by the child tags of the result will be bound to the result.
	 */
	@Override
	public void handleStartChildElement(String uri, String name, String rawName, Attributes attributes) {
		try {
			INSTRUCTION_On instruction = (INSTRUCTION_On)getHandler().peekValue(XCFProcessingInstruction.XCF_TAG);
			instruction.setCurrentResult(name);
			SAXEL_Composite saxel = new SAXEL_Composite(name);
			saxel.setHandler(getHandler());
			getHandler().push(saxel);
		} catch (ClassCastException e) {
			// we might get here if there were errors earlier -- since those error were reported,
			// the only thing to do is return.
			return;
		}		
	}
	
	/**
	 * Cleaning up popping the On instruction off of the container stack.
	 */
	@Override
	public void endInterpreting(String uri, String name, String qName) {
		XCFProcessingInstruction instruction = (XCFProcessingInstruction)getHandler().popValue(XCFProcessingInstruction.XCF_TAG);
		if (instruction == null) return;
	}
}

SAXEL_Response

Just as it is useful to organize requests into modules, it is also useful to organize responses into families. The response tag is used to define a family of related responses. You will need to define that entire family once for each response type you are supporting.


import org.xml.sax.Attributes;

import com.eternal.xcf.core.XCFException;
import com.eternal.xcf.core.XCFFacade;
import com.eternal.xcf.core.XCFStrings;
import com.eternal.xcf.core.response.SERVICE_ResponseManager;
import com.eternal.xcf.core.response.XCFResponse;
import com.eternal.xcf.sax.SAXEL_Composite;

/**
 * This SAXEL begins the definition of a family of response objects.  All
 * response renderers defined under this tag will be bound to the same
 * response type and use the same concrete respones rendering class.
 * 
 * The renders full id will be [response-name].[render-name]
 * The builder will use the flyweight factory to look up the renderClass.
 * 
 * The syntax is of the form:
 *  
 *	   
 *	     <[property-name>[property-value]
 *	     ...
 *	   
 *  
 * @author sonjaya
 *
 */
public class SAXEL_Response extends SAXEL_Composite {
	public static final String XCF_TAG = XCFResponse.XCF_TAG;
	
	static final String RESPONSE_TYPE = "responseType";
	static final String RENDER_CLASS = "renderClass";
	static final String RESPONSE_NAME = "responseName";
	
	public SAXEL_Response() {
		super(XCF_TAG);
	}

	@Override
	public void startInterpreting(String uri, String name, String rawName, Attributes attributes) {
		XCFFacade facade = getHandler().getFacade();
		
		SERVICE_ResponseManager responseManager =  (SERVICE_ResponseManager)facade.getService(SERVICE_ResponseManager.XCF_TAG);
		if (responseManager == null) {
			responseManager = new SERVICE_ResponseManager();
			try {
				facade.putService(SERVICE_ResponseManager.XCF_TAG, responseManager);
			} catch (XCFException e) {
				handleException(e);
				return;
			}
		}
		
		String responseName = attributes.getValue(XCFStrings.NAME);
		String clientType = attributes.getValue(RESPONSE_TYPE);
		String renderClass = attributes.getValue(RENDER_CLASS);
		
		if (responseName == null) {
			handleError(" must have a name attribute: e.g., ");
			return;
		}
		
		if (clientType == null) {
			handleError(" must have a clientType attribute: e.g., ");
			return;
		}
		
		if (renderClass == null) {
			handleError(" must have a renderClass attribute: e.g., ");
			return;
		}
		
		getHandler().put(RESPONSE_NAME, responseName);
		getHandler().put(RENDER_CLASS, renderClass);
		getHandler().put(RESPONSE_TYPE, clientType);
	}

	@Override
	public void endInterpreting(String uri, String name, String qName) {
		getHandler().consume(RESPONSE_NAME);
		getHandler().consume(RENDER_CLASS);
		getHandler().consume(RESPONSE_TYPE);
	}
}

SAXEL_Renderer

And finally, you need a class that will configure a specific renderer.


package com.eternal.xcf.common.builder;

import org.xml.sax.Attributes;

import com.eternal.xcf.core.XCFException;
import com.eternal.xcf.core.XCFFacade;
import com.eternal.xcf.core.XCFStrings;
import com.eternal.xcf.core.response.SERVICE_ResponseManager;
import com.eternal.xcf.core.response.XCFResponse;
import com.eternal.xcf.sax.SAXEL_Composite;
import com.eternal.xcf.sax.SAXEL_Literal;

/**
 * This SAXEL instantiates a response renderer and sets is property values.
 * 
 * This SAXEL assumes it is a child tag of .
 * When this SAXEL is done interpreting it will register the newly configured 
 * reponse renderer with the response manager.
 * 
 * The renders full id will be [response-name].[render-name]
 * The builder will use the flyweight factory to look up the renderClass.
 * 
 * The syntax is of the form:
 *  
 *    <[property-name>[property-value]
 *    ...
 *  
 * @author sonjaya
 *
 */
public class SAXEL_Renderer extends SAXEL_Composite {
	public static String XCF_TAG = "renderer";
	
	public SAXEL_Renderer() {
		super(XCF_TAG);
	}

	/**
	 * Instantiates the response renderer and puts it on the handler context.
	 */
	@Override
	public void startInterpreting(String uri, String name, String rawName, Attributes attributes) {
		XCFFacade facade = getHandler().getFacade();
		String responseName = (String)getHandler().get(SAXEL_Response.RESPONSE_NAME);
		String rendererName = attributes.getValue(XCFStrings.NAME);
		
		if (responseName == null) return;
		
		if (rendererName == null) {
			handleError(" must have a name attribute: e.g., ");
			return;
		}

		// we can assume the parent interpreter put the render class in the handler context 
		String renderClass = (String)getHandler().get(SAXEL_Response.RENDER_CLASS);
		
		try {
			// use the flyweight factory to get a new instance of that class.
			// put that instance on the handler context
			XCFResponse response = (XCFResponse)flyweightFactory.getNewInstance(XCFResponse.XCF_TAG, renderClass);
			response.setName(responseName + "." + rendererName);
			getHandler().put(XCFResponse.XCF_TAG, response);
			facade.logDebug("|----" + " BEGIN RENDERER " + responseName + "." + rendererName + "----------");
		} catch (XCFException e1) {
			handleException(e1);
			return;
		} catch (ClassCastException e2) {
			handleException(e2);
			return;
		}
	}

	/**
	 * Pushes a literal on the handler stack to grab the property value.
	 */
	@Override
	public void handleStartChildElement(String uri, String name, String rawName, Attributes attributes) {
		SAXEL_Literal propertySaxel = new SAXEL_Literal(name);
		propertySaxel.setHandler(getHandler());
		getHandler().push(propertySaxel);
	}

	/**
	 * Grabs the property value from the handler context and sets
	 * the property in the response renderer.
	 */
	@Override
	public void handleEndChildElement(String interpreterTag) {
		XCFFacade facade = getHandler().getFacade();
		XCFResponse response = (XCFResponse)getHandler().get(XCFResponse.XCF_TAG);
		String propertyValue = (String)handler.consume(interpreterTag);
		
		if (response == null) return;
		response.setProperty(interpreterTag, propertyValue);
		facade.logDebug("|---- " + interpreterTag + " = " + propertyValue);
	}

	/**
	 * Registers the response renderer with the response manager.
	 */
	@Override
	public void endInterpreting(String uri, String name, String qName) {
		XCFFacade facade = getHandler().getFacade();
		SERVICE_ResponseManager responseManager =  (SERVICE_ResponseManager)facade.getService(SERVICE_ResponseManager.XCF_TAG);
		XCFResponse response = (XCFResponse)getHandler().consume(XCFResponse.XCF_TAG);
		String clientType = (String)getHandler().get(SAXEL_Response.RESPONSE_TYPE);
		
		if (response == null) return;
		
		try {
			responseManager.registerResponse(clientType, response);
			facade.logDebug("|----" + " END RENDERER " + response.getName() + "----------");
		} catch (XCFException e) {
			handleException(e);
		}
	}
}

Testing it out

To test all this out, under your test folder, create a package called com.eternal.xcf.core.response. In that package folder, create a file called simple.xml and save the XML configuration at the start of this lesson in that file.

Now create the following junit test:


package com.eternal.xcf.core.response;

import com.eternal.stubs.CONTEXT_Simple;
import com.eternal.stubs.REQUEST_SimpleTest;
import com.eternal.xcf.core.XCFFacade;
import com.eternal.xcf.core.XCFLogger;
import com.eternal.xcf.sax.UTIL_Helper;

import junit.framework.TestCase;

public class TestResponse extends TestCase {
	
	public void testSimpleFile() throws Exception {
		XCFFacade facade = new XCFFacade();
		facade.getLogManager().setLogger(XCFLogger.LogTypes.DEBUG, facade.getLogManager().getLogger(XCFLogger.LOG_TO_CONSOLE));
		UTIL_Helper.interpretLocalFile(this, "simple.xml", facade, "facade");
		
		SERVICE_ResponseManager responseManager = (SERVICE_ResponseManager)facade.getService(SERVICE_ResponseManager.XCF_TAG);
		assertNotNull(responseManager);

		// 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("user-name", "myloginname");
		request.setParameter("password", "mypassword");
		
		// process the request
		facade.process(request);
		
		// verify that the request resulted by setting "account.home" as
		// the response id
		String responseId = (String)request.getContext().getValue(XCFResponse.XCF_RESPONSE_ID);
		assertEquals("account.home", responseId);
		
		String[] nativeRequestInfo = new String[1];
		
		// now render an html response - the renderer should
		// write the name of a jsp file to the array we pass in
		com.eternal.xcf.core.response.UTIL_Helper.setResponseType(request, "html");
		XCFResponse response = responseManager.getResponse(request);
		response.render(request, nativeRequestInfo);
		assertEquals("/account/home.jsp", nativeRequestInfo[0]);

		// now render an nio response - the renderer should
		// write the name of a message to the array we pass in
		com.eternal.xcf.core.response.UTIL_Helper.setResponseType(request, "nio");
		response = responseManager.getResponse(request);
		response.render(request, nativeRequestInfo);
		assertEquals("account.display-home", nativeRequestInfo[0]);

		// create a new reqest, but this time don't set the password
		request = new REQUEST_SimpleTest("account", "login");
		request.setContext(new CONTEXT_Simple());
		request.getContext().setFacade(facade);
		
		request.setParameter("user-name", "myloginname");
		
		// process the request and verify that we got
		// the login failed response selected
		facade.process(request);
		responseId = (String)request.getContext().getValue(XCFResponse.XCF_RESPONSE_ID);
		assertEquals("account.login-failed", responseId);
		
		// now render an html response - the renderer should
		// write the name of a jsp file to the array we pass in
		com.eternal.xcf.core.response.UTIL_Helper.setResponseType(request, "html");
		response = responseManager.getResponse(request);
		response.render(request, nativeRequestInfo);
		assertEquals("/account/login.jsp", nativeRequestInfo[0]);

		// now render an nio response - the renderer should
		// write the name of a message to the array we pass in
		com.eternal.xcf.core.response.UTIL_Helper.setResponseType(request, "nio");
		response = responseManager.getResponse(request);
		response.render(request, nativeRequestInfo);
		assertEquals("account.handle-login-failed", nativeRequestInfo[0]);
	}
}

When you run the code, you should get the following output:


INFO:::main:::STARTED SERVICE flyweight-factory USING com.eternal.xcf.common.service.SERVICE_FlyweightFactory
DEBUG:::main:::Searching for: com.eternal.stubs.builder.SAXEL_Module
DEBUG:::main:::com.eternal.stubs.builder.SAXEL_Module not in classpath
DEBUG:::main:::Searching for: com.eternal.xcf.common.builder.SAXEL_Module
DEBUG:::main:::=================== BEGIN MODULE account===================
DEBUG:::main:::Searching for: com.eternal.stubs.builder.SAXEL_Operation
DEBUG:::main:::com.eternal.stubs.builder.SAXEL_Operation not in classpath
DEBUG:::main:::Searching for: com.eternal.xcf.common.builder.SAXEL_Operation
DEBUG:::main:::|---- BEGIN OPERATION login----------
DEBUG:::main:::Searching for: com.eternal.stubs.builder.SAXEL_Do
DEBUG:::main:::com.eternal.stubs.builder.SAXEL_Do not in classpath
DEBUG:::main:::Searching for: com.eternal.xcf.common.builder.SAXEL_Do
DEBUG:::main:::Searching for: com.eternal.stubs.request.processor.instructions.INSTRUCTION_ProcessParameter
DEBUG:::main:::com.eternal.stubs.request.processor.instructions.INSTRUCTION_ProcessParameter not in classpath
DEBUG:::main:::Searching for: com.eternal.xcf.common.request.processor.instructions.INSTRUCTION_ProcessParameter
DEBUG:::main:::com.eternal.xcf.common.request.processor.instructions.INSTRUCTION_ProcessParameter not in classpath
DEBUG:::main:::Searching for: com.eternal.xcf.request.processor.instructions.INSTRUCTION_ProcessParameter
DEBUG:::main:::| ---   BEGIN user-name(com.eternal.xcf.request.processor.instructions.INSTRUCTION_ProcessParameter)
DEBUG:::main:::Searching for: com.eternal.stubs.builder.SAXEL_Validate
DEBUG:::main:::com.eternal.stubs.builder.SAXEL_Validate not in classpath
DEBUG:::main:::Searching for: com.eternal.xcf.common.builder.SAXEL_Validate
DEBUG:::main:::Searching for: com.eternal.stubs.request.parameter.VALIDATOR_Required
DEBUG:::main:::com.eternal.stubs.request.parameter.VALIDATOR_Required not in classpath
DEBUG:::main:::Searching for: com.eternal.xcf.common.request.parameter.VALIDATOR_Required
DEBUG:::main:::|      ADDED validator: required(com.eternal.xcf.common.request.parameter.VALIDATOR_Required)
DEBUG:::main:::| ---   END user-name(com.eternal.xcf.request.processor.instructions.INSTRUCTION_ProcessParameter)
DEBUG:::main:::Searching for: com.eternal.stubs.request.processor.instructions.INSTRUCTION_ProcessParameter
DEBUG:::main:::com.eternal.stubs.request.processor.instructions.INSTRUCTION_ProcessParameter not in classpath
DEBUG:::main:::Searching for: com.eternal.xcf.common.request.processor.instructions.INSTRUCTION_ProcessParameter
DEBUG:::main:::com.eternal.xcf.common.request.processor.instructions.INSTRUCTION_ProcessParameter not in classpath
DEBUG:::main:::Searching for: com.eternal.xcf.request.processor.instructions.INSTRUCTION_ProcessParameter
DEBUG:::main:::| ---   BEGIN password(com.eternal.xcf.request.processor.instructions.INSTRUCTION_ProcessParameter)
DEBUG:::main:::|      ADDED validator: required(com.eternal.xcf.common.request.parameter.VALIDATOR_Required)
DEBUG:::main:::| ---   END password(com.eternal.xcf.request.processor.instructions.INSTRUCTION_ProcessParameter)
DEBUG:::main:::Searching for: com.eternal.stubs.CMD_PrintRequest
DEBUG:::main:::| ---   BEGIN print-request(com.eternal.stubs.CMD_PrintRequest)
DEBUG:::main:::| ---   END print-request(com.eternal.stubs.CMD_PrintRequest)
DEBUG:::main:::Searching for: com.eternal.stubs.builder.SAXEL_On
DEBUG:::main:::com.eternal.stubs.builder.SAXEL_On not in classpath
DEBUG:::main:::Searching for: com.eternal.xcf.common.builder.SAXEL_On
DEBUG:::main:::Searching for: com.eternal.stubs.request.processor.instructions.INSTRUCTION_SelectResponse
DEBUG:::main:::com.eternal.stubs.request.processor.instructions.INSTRUCTION_SelectResponse not in classpath
DEBUG:::main:::Searching for: com.eternal.xcf.common.request.processor.instructions.INSTRUCTION_SelectResponse
DEBUG:::main:::com.eternal.xcf.common.request.processor.instructions.INSTRUCTION_SelectResponse not in classpath
DEBUG:::main:::Searching for: com.eternal.xcf.request.processor.instructions.INSTRUCTION_SelectResponse
DEBUG:::main:::| ---   BEGIN account.home(com.eternal.xcf.request.processor.instructions.INSTRUCTION_SelectResponse)
DEBUG:::main:::| ---   END account.home(com.eternal.xcf.request.processor.instructions.INSTRUCTION_SelectResponse)
DEBUG:::main:::Searching for: com.eternal.stubs.request.processor.instructions.INSTRUCTION_SelectResponse
DEBUG:::main:::com.eternal.stubs.request.processor.instructions.INSTRUCTION_SelectResponse not in classpath
DEBUG:::main:::Searching for: com.eternal.xcf.common.request.processor.instructions.INSTRUCTION_SelectResponse
DEBUG:::main:::com.eternal.xcf.common.request.processor.instructions.INSTRUCTION_SelectResponse not in classpath
DEBUG:::main:::Searching for: com.eternal.xcf.request.processor.instructions.INSTRUCTION_SelectResponse
DEBUG:::main:::| ---   BEGIN account.login-failed(com.eternal.xcf.request.processor.instructions.INSTRUCTION_SelectResponse)
DEBUG:::main:::| ---   END account.login-failed(com.eternal.xcf.request.processor.instructions.INSTRUCTION_SelectResponse)
DEBUG:::main:::|---- END OPERATION login----------
DEBUG:::main:::===================END MODULE account===================
DEBUG:::main:::Searching for: com.eternal.stubs.builder.SAXEL_Response
DEBUG:::main:::com.eternal.stubs.builder.SAXEL_Response not in classpath
DEBUG:::main:::Searching for: com.eternal.xcf.common.builder.SAXEL_Response
INFO:::main:::STARTED SERVICE xcf-renderer USING com.eternal.xcf.core.response.SERVICE_ResponseManager
DEBUG:::main:::Searching for: com.eternal.stubs.builder.SAXEL_Renderer
DEBUG:::main:::com.eternal.stubs.builder.SAXEL_Renderer not in classpath
DEBUG:::main:::Searching for: com.eternal.xcf.common.builder.SAXEL_Renderer
DEBUG:::main:::Searching for: com.eternal.stubs.RESPONSE_JSPStub
DEBUG:::main:::|---- BEGIN RENDERER account.home----------
DEBUG:::main:::|---- jsp = /account/home.jsp
DEBUG:::main:::Registering account.home for type html
DEBUG:::main:::|---- END RENDERER account.home----------
DEBUG:::main:::Searching for: com.eternal.stubs.RESPONSE_JSPStub
DEBUG:::main:::|---- BEGIN RENDERER account.login-failed----------
DEBUG:::main:::|---- jsp = /account/login.jsp
DEBUG:::main:::Registering account.login-failed for type html
DEBUG:::main:::|---- END RENDERER account.login-failed----------
DEBUG:::main:::Searching for: com.eternal.stubs.RESPONSE_NIOStub
DEBUG:::main:::|---- BEGIN RENDERER account.home----------
DEBUG:::main:::|---- client-msg = account.display-home
DEBUG:::main:::Registering account.home for type nio
DEBUG:::main:::|---- END RENDERER account.home----------
DEBUG:::main:::Searching for: com.eternal.stubs.RESPONSE_NIOStub
DEBUG:::main:::|---- BEGIN RENDERER account.login-failed----------
DEBUG:::main:::|---- client-msg = account.handle-login-failed
DEBUG:::main:::Registering account.login-failed for type nio
DEBUG:::main:::|---- END RENDERER account.login-failed----------
INFO:::main:::PROCESSING account.login(password=mypassword, user-name=myloginname)
INFO:::main:::Rendering JSP:/account/home.jsp
INFO:::main:::Sending message:account.display-home
INFO:::main:::Rendering JSP:/account/login.jsp
INFO:::main:::Sending message:account.handle-login-failed

Conclusion

With the abstraction of the request an response processing you have done, you have done the bulk of the work that will decouple your application code from your technology stack. What you haven’t delved into yet, is that context object you keep seeing in the examples. This context object will prove very important as it is the primary mechanism you will use to pass information back and forth between the various processing objects. But the how and why of that will be left for another lesson.

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

February 2007
M T W T F S S
« Jan   Mar »
 1234
567891011
12131415161718
19202122232425
262728  

Most Recent Posts