FRAMEWORK 101: Building XCF – Lesson 05 – Building the facade

January 7th, 2007

In all the prior lessons, I showed you how to create the building blocks of the framework. Today, I finally show you how to use those building blocks to define and create facades. The first lesson introduced the core objects and interfaces of the facade, the second lesson introduced objects that model controller behavior, the third lesson introduced a flyweight factory you will use to model convention (and thereby simplify configuration), and the last article introduced a mechanism to create and XML interpreter. Today you will finally be able to define your facades in XML for in this article you will create an XML interpreter that builds system facades.

Course outline:

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

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

What you will be building today

Today you will build the very same facade that you have been building for each of the lessons. Namely, it is a facade with one module (account) that has one supported operation (login). This operation is bound to a command that simply prints out the request. You will test your facade with familiar code:


 UTIL_Helper.interpretLocalFile(this, "simple.xml", facade, "facade");
		
 // 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);

With the exception of the first line, this is exactly like all your other tests. The difference today is that you won’t have to explicitly build the facade in java. Instead, you will define the facade in XML:



    
    	com.eternal.stubs
    
    
    	command,.CMD_
    

	
		
		    
		    	
		    
			
			 
			
  		   
		
	


Builder Pattern

You use the builder pattern when you want to separate the construction of a complex object from its representation so that the same construction process can create different representations.

XCF places construction logic in the SAXELs, and uses the INSTRUCTION objects to represent the processing logic of the facade. The XML file, in conjuction with SAX handler act as the director. Each SAXEL acts as a builder. And the product is the facade and all its sub-components (e.g. INSTRUCTION objects, validators, XCFService objects).

The builder pattern is a good fit because:

  • the algorithm for constructing the facade is independent of the parts that make up the facade and from how those parts are assembled.
  • the construction process easily tolerates different representations for the facade and its sub-components – this is important because one of the goals is to make a technology stack neutral framework – that means we need to support different representations for the same type of functionality.
  • it provides a fine grain of control over the construction process

A walk through building

Normally, at this point, I go over the implementation. I think today, however, it would be better if I step through the execution of the building process. You can refer to the implementation below for each of the SAXELs as I cover them in the walk through.

This walk through will take you through the process of building a facade that will support the request: account.login(user-name, password). The facade you build will handle this request by first checking to make sure user-name and password were supplied, and then invoke a command that simply prints the request name and the parameter values.

Like before, when interpreting starts, the first tag encountered is “facade”. At this point in time, the interpreter stack contains SAXEL_Root. The handler will invoke “startChildElement” for that class. This will result in an instance of SAXEL_Composite being pushed onto the stack.

The next tag encountered is “include-packages”. The handler will use the top of stack object (an instance of SAXEL_Composite) to handle the tag. SAXEL_Composite will handle this by pushing a SAXEL_Literal onto the stack.

The interpreter then encounters the text between the “include-packages” tag. This text is sent to the top of stack, which is SAXEL_Literal.

The interpreter then encounters the close tag for “include-packages”. This results in SAXEL_Literal being popped from the stack and a handleEndChildElement being called on the new top of stack object, which is an instance of SAXEL_Composite. That class, in turn, will grab the literals text and use it to add packages into the search space for the flyweight factory.

The next tag encountered is “add-convention” (I updated the implementation of the flyweight factory and SAXEL_Composite to support this functionality – see the implementations below). The handler will use the top of stack object (an instance of SAXEL_Composite) to handle the tag. SAXEL_Composite will handle this by pushing a SAXEL_Literal onto the stack.

The interpreter then encounters the text between the “add-convention” tag. This text is sent to the top of stack, which is SAXEL_Literal.

The interpreter then encounters the close tag for “add-convention”. This results in SAXEL_Literal being popped from the stack and a handleEndChildElement being called on the new top of stack object, which is an instance of SAXEL_Composite. That class, in turn, will grab the literals text and use it to add conventions into the current scope for the flyweight factory.

The next tag encountered is “module”. The handler will use the top of stack object (an instance of SAXEL_Composite) to handle the tag. SAXEL_Composite will handle this by using the flyweight factory to find the class com.eternal.xcf.common.builder.SAXEL_Module (see implementation below) and pushing an instance of that class onto the stack. The handler will invoke “startChildElement” for that class. That results in an instance of MODULE_Instruction being instantiated and put onto the handler’s context under the tag “module”.

You might be wondering how the flyweight factory was able to find com.eternal.xcf.common.builder.SAXEL_Module. If you check the implementation of the flyweight factory you will see that there is a default convention for “saxel” and a couple of default packages in the search path: com.eternal.xcf.common is one of those packages.

The next tag encountered is “operation”. The handler will use the top of stack object (an instance of SAXEL_Module) to handle the tag. As that class is an extension of SAXEL_Composite, it will handle this by using the flyweight factory to find the class com.eternal.xcf.common.builder.SAXEL_Operation (see implementation below) and pushing an instance of that class onto the stack. The handler will invoke “startChildElement” for that class. That results in an instance of INSTRUCTION_Composite being instantiated and put onto the handler’s context under the tag “container”.

The next tag encountered is “do”. The handler will use the top of stack object (an instance of SAXEL_Operation) to handle the tag. As that class is an extension of SAXEL_Composite, it will handle this by using the flyweight factory to find the class com.eternal.xcf.common.builder.SAXEL_Do (see implementation below) and pushing an instance of that class onto the stack. The handler will invoke “startChildElement” for that class. That results in the SAXEL grabbing the INSTRUCTION_Composite “container” from the handler’s context. It then creates an instance of INSTRUCTION_ParameterProcessor and pushes it onto the handler’s context under the tag “container”. It adds the parameter processor as a child instruction to the composite instruction.

At this point, you can see the beginnings of the facade. The SAXELs have created an INSTRUCTION_Composite object to handle the “login” operation. Currently that object will process a single parameter, “user-name” (but we don’t have any processing logic yet!).

The next tag encountered is “validate”. The handler will use the top of stack object (an instance of SAXEL_Do) to handle the tag. As that class is an extension of SAXEL_Composite, it will handle this by using the flyweight factory to find the class com.eternal.xcf.common.builder.SAXEL_Validate (see implementation below) and pushing an instance of that class onto the stack. The handler will invoke “startChildElement” for that class. That results in the SAXEL grabbing the INSTRUCTION_ParameterProcessor “container” from the handler’s context. It then creates an instance of VALIDATOR_Required and adds the validator to the parameter processor.

As a result of the last tag, the facade sub-component will now require the “user-name” parameter when processing a request for the “login” operation.

The next tag encountered is the closing tag for “do”. This results in the user-name parameter process being popped from the “container” tag in the handler’s context and SAXEL_Do being popped from the handler stack.

The next tag encountered is “do”. The handler will use the top of stack object (an instance of SAXEL_Operation) to handle the tag. As that class is an extension of SAXEL_Composite, it will handle this by using the flyweight factory to find the class com.eternal.xcf.common.builder.SAXEL_Do and pushing an instance of that class onto the stack. The handler will invoke “startChildElement” for that class. That results in the SAXEL grabbing the INSTRUCTION_Composite “container” from the handler’s context. It then creates an instance of INSTRUCTION_ParameterProcessor and pushes it onto the handler’s context under the tag “container”. It adds the parameter processor as a child instruction to the composite instruction.

The last tag resulted in a parameter process for the “password” parameter being added to the “login” operation.

The next tag encountered is “validate”. The handler will use the top of stack object (an instance of SAXEL_Do) to handle the tag. As that class is an extension of SAXEL_Composite, it will handle this by using the flyweight factory to find the class com.eternal.xcf.common.builder.SAXEL_Validate and pushing an instance of that class onto the stack. The handler will invoke “startChildElement” for that class. That results in the SAXEL grabbing the INSTRUCTION_ParameterProcessor “container” from the handler’s context. It then creates an instance of VALIDATOR_Required and adds the validator to the parameter processor.

As a result of the last tag, the facade sub-component will now require the “password” parameter when processing a request for the “login” operation.

The next tag encountered is the closing tag for “do”. This results in the password parameter process being popped from the “container” tag in the handler’s context and SAXEL_Do being popped from the handler stack.

The next tag encountered is the “do” tag. As this particular tag is for a “command” object, the behavior is a little different. The SAXEL will create an instance of com.eternal.stubs.CMD_PrintRequest and add an instance of that object as a child instruction to the “login” operation. The SAXEL was able to find this class because com.eternal.stubs was added to the search path earlier on and because of the “command” convention defined.

So after the last tag, you can now see a fully defined request handler for the “login” operation.

The next tag encountered is the closing tag for “operation”. This results in the SAXEL adding the operation to the module that is on the handler’s context (account). It then pops the operation off of the “container” tag in the context and also pops SAXEL_Operation off of the handler stack.

As a result, the “account” module facade sub-object is now fully defined.

The next tag encountered is the closing tag for “module”. The results in the SAXEL adding the module to the facade. It removes the module from the handler’s context and pops SAXEL_Module from the handler’s stack.

As a result, we have a fully built facade! This is the same facade object you created by hand in Lesson 02. Refer to that lesson for a description of how the facade object processes the login request.

Finally the closing “facade” tag is encountered and interpreting is completed.

Implementation

SERVICE_FlyweightFactory

There are two changes you will need for the flyweight factory. The first change is that you need to move the concept of “conventions” into the scope. That way, each scope can override or add new conventions. The second change is to add a getNewInstance method. Previously, the flyweight factory returned a singleton instance of the type. That means every client accessed the same object. With this new method, clients can specifically request a new instance for that type.

Please note that the current implementation is not the final one. In fact, it has bugs. There are definite combinations of package paths and conventions that it should find, but won’t. As this is an open source framework, if you feel industrious :), please feel free to create an alternate and fully functioning implementation. Just let me know via this blog and I can get you added to the source forge project.

Also, if you need to send me an email, my email is my first name at sonjayatandon.com.


package com.eternal.xcf.common.service;

import java.util.HashMap;
import java.util.Stack;

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

/**
 * Client classes use this class to get a reference to a flyweight instance.  
 * The flyweight factory provides method that takes as input a string description 
 * of an object type.  It then does the work of mapping that type to a concrete class 
 * and determining whether or not a new instance of that class needs to be created.
 * 
 * @author sonjaya
 *
 */
public final class SERVICE_FlyweightFactory implements XCFService {
	public static String XCF_TAG = "flyweight-factory";
	private String name = XCF_TAG;
	
	private XCFFacade facade;
	
	private Stack scopeStack = new Stack();

	/**
	 * Returns services name
	 */
	public String getName() {
		return name;
	}

	/**
	 * Set the services facade
	 */
	public void setFacade(XCFFacade facade) {
		this.facade = facade;
		
	}

	/**
	 * Set the services name
	 */
	public void setName(String name) {
		this.name = name;
	}

	/**
	 * Start out the service by defining some conventions
	 * and add com.eternal.xcf.common to the search path
	 */
	public void start() throws XCFException {
		pushScope();
		addConvention("validator", ".request.parameter.VALIDATOR_");
		addConvention("setter", ".request.parameter.SETTER_");
		addConvention("saxel", ".builder.SAXEL_");
		addConvention("instruction", ".request.processor.instructions.INSTRUCTION_");
		
		addPackage("com.eternal.xcf");
		addPackage("com.eternal.xcf.common");
	}

	/**
	 * Stop by poping the scope
	 */
	public void stop() throws XCFException {
		popScope();
	}
	
	/**
	 * Returns the instance associated w/the tag and type
	 * This method will first look to see if the instance exists in the current scope
	 * If a flyweight exists, it will return that, otherwise it will instantiate a new one.
	 * It repeats this process for each scope on the stack.
	 *  
	 * @param type identifies the type of the flyweight -- e.g. validator, saxel
	 * @param tag identifies the flyweight in that type
	 * @return the flyweight object
	 * @throws XCFException thrown if it is unable to create a flyweight object
	 */
	public Object getInstance(String type, String tag) throws XCFException {
		return getInstance(type, tag, true);
	}
	
	/**
	 * This is similar to getInstance.  The only difference is that this 
	 * method always returns a new instance of the desired type.
	 * @param type
	 * @param tag
	 * @return
	 * @throws XCFException
	 */
	public Object getNewInstance(String type, String tag) throws XCFException {
		return getInstance(type, tag, false);
	}
	
	/**
	 * This is the actual work horse method for both getInstances.
	 * @param type
	 * @param tag
	 * @param isSingleton
	 * @return
	 * @throws XCFException
	 */
	public Object getInstance(String type, String tag, boolean isSingleton) throws XCFException {
		// get the sub-package and class name prefix for this type
		String prefix = getConvention(type);
		if (prefix == null) throw new XCFException("No convention for type:" + type);
		
		// for each scope started with the top of stack, 
		// find our create the flyweight
		for (int i = scopeStack.size() - 1; i >= 0; i--) {
			Scope scope = scopeStack.elementAt(i);
			
			if (isSingleton) {
				// first see if we have already created the flyweight
				Object flyweight = scope.getFlyweight(type, tag);
				// if flyweight is this object, then it meant we
				// already looked and couldn't find it for this scope
				// no need to look again, so move on to the next scope
				if (flyweight == this) continue;
				// if we got a non-null flyweight that was not this object
				// we have what we need and can return it
				if (flyweight != null) return flyweight;
				
				// ok, we don't have a flyweight yet for this type,tag and we haven't
				// looked yet.  Search through all the packages to see if we can find 
				// the flyweight class
			}
			
			Stack searchPath = scope.searchPath;
			
			for (int j = searchPath.size()-1; j>=0; j--) {
				// create the class name by appending [path][prefix][normalized name]
				String path = searchPath.elementAt(j);
				String name = normalizeName(tag);
				String className = path + prefix + name;
				facade.logDebug("Searching for: " + className);
				
				// try to load it and create an instance
				try {
					Class aClass = Class.forName(className);
					Object anInstance = aClass.newInstance();
					scope.putFlyweight(type, tag, anInstance);
					return anInstance;
				} catch (ClassNotFoundException cnf) {
					facade.logDebug(className + " not in classpath");
				} catch (IllegalAccessException ia) {
					facade.logDebug(className + " does not have a public constructor");
				} catch (InstantiationException ie) {
					facade.logDebug(className + " does not have a null constructor");
				}
				
				// if we got here, we didn't find it in this path, go on to the next one
			}
			
			if (isSingleton) {
				// there is no flyweight for this context, mark it so
				// we don't bother searcing on the next request
				scope.putFlyweight(type, tag, this);
			}
		}
		
		// if we get here, we can't find the flyweight class and that is a problem
		throw new XCFException("Unable to find the class for type: " + type + " and tag: " + tag);
		
	}
	
	/**
	 * Looks up the convention for the type
	 * @param type
	 * @return
	 */
	String getConvention(String type) {
		for (int i = scopeStack.size() - 1; i >= 0; i--) {
			Scope scope = scopeStack.elementAt(i);
			String prefix = scope.getConvention(type);
			if (prefix != null) return prefix;
		}
		
		return null;
	}
	
	/**
	 * Convert the tag to a name that follows the conventions.  
	 * Upercase word boundaries and remove -'s and _'s
	 * @param tag
	 * @return the converted name
	 */
	String normalizeName(String tag) {
		StringBuffer buf = new StringBuffer();
		
		boolean newWord = true;
		//  capitalize first letter of each word
		//  remove -'s and _'s
		for (int i = 0; i < tag.length(); i++) {
			char c = tag.charAt(i);
			if (c == '-' || c == '_') {
				newWord=true;
			} else if (newWord) {
				buf.append(Character.toUpperCase(c));
				newWord = false;
			} else {
				buf.append(c);
				newWord = false;
			}
		}
		return buf.toString();
	}
	
	/**
	 * Push a new scope onto the scope stack
	 *
	 */
	public void pushScope() {
		scopeStack.add(new Scope());
		
	}
	
	/**
	 * Pop a scope off of the scope stack
	 *
	 */
	public void popScope() {
		scopeStack.pop();
	}

	/**
	 * Add a sub-package, prefix convention for the type
	 * @param type
	 * @param prefix  this should be of the form .[package(s)].[prefix (in caps)]. e.g. .request.processors.VALIDATOR_
	 */
	public void addConvention(String type, String prefix) {
		scopeStack.peek().addConvention(type, prefix);
		
	}

	/**
	 * Add a package to the search path stack for the current scope.  The search path search starts from last first
	 * @param path
	 */
	public void addPackage(String path) {
		scopeStack.peek().addPath(path);
	}
	
	/**
	 * A scope contains a stack of packages (called the search path)
	 * and a cache of flyweights bound to a type,tag pair
	 * 
	 * @author sonjaya
	 *
	 */
	final class Scope {
		Stack searchPath = new Stack();
		HashMapcache = new HashMap();
		private HashMapconventions = new HashMap();
		
		/**
		 * Add a sub-package, prefix convention for the type
		 * @param type
		 * @param prefix  this should be of the form .[package(s)].[prefix (in caps)]. e.g. .request.processors.VALIDATOR_
		 */
		public void addConvention(String type, String prefix) {
			conventions.put(type, prefix);
			
		}
		
		/**
		 * Get the sub-package, prefix convention for the type
		 * @param type
		 * @return
		 */
		public String getConvention(String type) {
			return conventions.get(type);
		}

		/**
		 * Add a path to search path stack
		 * @param path
		 */
		void addPath(String path) {
			searchPath.add(path);
		}
		
		/**
		 * Lookup the flyweight object bound to type,tag return null 
		 * if it doesn't exist
		 * @param type
		 * @param tag
		 * @return
		 */
		Object getFlyweight(String type, String tag) {
			return cache.get(type + "." + tag);
		}
		
		/**
		 * Bind a flyweight object to a type,tag
		 * @param type
		 * @param tag
		 * @param flyweight
		 */
		void putFlyweight(String type, String tag, Object flyweight) {
			cache.put(type + "." + tag, flyweight);
		}
	}
}

SAXEL_Composite

The only change to this class you need to make is to add support for the “add-convention” tag. You will need to make a change to handleStartChildElement so that the SAXEL pushes a “add-convention” literal on the stack. And then you will need to make a change to handleEndChildElement so that when the “add-convetion” tag pops, you parse the literal text and add each convention to the flyweight factory.


package com.eternal.xcf.sax;

import java.util.HashMap;
import java.util.StringTokenizer;

import org.xml.sax.Attributes;

import com.eternal.xcf.common.service.SERVICE_FlyweightFactory;
import com.eternal.xcf.core.XCFException;

/**
 * Acts as the base class for any SAXEL used to interpret composite expressions.
 * Composite expressions are represented by XML tags which will have child tags.
 * 
 * You will typically override build, startInterpreting, and endInterpreting.
 * If you override startChildElement OR endChildElement make sure to call the super method.
 * 
 * Creation date: (9/6/2001 5:29:29 PM)
 * 
 * @author: Sonjaya Tandon
 */
public class SAXEL_Composite extends SAX_Element {
	private static String EL_ADD_TAG = "add-tag";
	private static String EL_INCLUDE_PACKAGES = "include-packages";
	private static String EL_ADD_CONVENTION = "add-convention";
	
	private static SAXEL_Literal ADD_TAG = new SAXEL_Literal(EL_ADD_TAG);
	private static SAXEL_Literal INCLUDE_PACKAGES_TAG = new SAXEL_Literal(EL_INCLUDE_PACKAGES);
	private static SAXEL_Literal ADD_CONVENTION_TAG = new SAXEL_Literal(EL_ADD_CONVENTION);
	
	protected HashMap children = new HashMap();
	protected SERVICE_FlyweightFactory flyweightFactory;
	
	///////////////////////////////////////////////////////
	// Creational methods
	///////////////////////////////////////////////////////
	/**
	 * Default constsructor
	 * @param tag java.lang.String
	 * @param parser com.dataskill.standon.xml.message.XmlMessageParser
	 */
	public SAXEL_Composite(String tag) {
		super(tag);
	}
	
	/**
	 * Hooks onto the setHandler method to grab the flyweightFactory
	 */
	public final void setHandler(SAX_Handler handler) {
		super.setHandler(handler);
		assert handler.getFacade() != null;
		flyweightFactory = (SERVICE_FlyweightFactory)handler.getFacade().getService(SERVICE_FlyweightFactory.XCF_TAG);
		assert flyweightFactory != null;
	}
	
	/**
	 * Associates an xml tag with the SAXEL that will interpret it
	 * @param tag
	 * @param el
	 */
	public final void addTag(String tag, SAX_Element el) {
		el.setTag(tag);
		el.setHandler(getHandler());
		children.put(tag, el);
	    el.build();
	}
	
	///////////////////////////////////////////////////////
	// Accessor methods
	///////////////////////////////////////////////////////
	/**
	 * Returns the children.
	 * @return HashMap
	 */
	public final HashMap getChildren() {
		return children;
	}

	/**
	 * Sets the children.
	 * @param children The children to set
	 */
	public final void setChildren(HashMap children) {
		this.children = children;
	}
	///////////////////////////////////////////////////////
	// SAX handler methods
	///////////////////////////////////////////////////////
	/**
	 * Finds the SAXEL associated with the tag, pushes it onto the interpreter stack and 
	 * calls startInterpreting for that SAXEL
	 * 
	 * There are two reserved tags:
	 *   add-tag: in this case, a literal is pushed onto the stack to grab the class name in the tag body
	 *            that SAXEL class is then assocated to the tag name
	 *   include-packages: in this case a literal is pushed onto the stack to grab the package list
	 *            those packages are then added to the flyweight factory.
	 *            
	 * If there is no SAXEL associated with the tag, attempt to find one using the flyweight factory
	 * If the flyweight factory returns a SAXEL, associate it witht the tag, push it on the stack
	 * and startInterpreting.  If not, nothing more can be done, tag is unexpected.
	 * 
	 * Creation date: (9/6/2001 5:47:32 PM)
	 * @param uri java.lang.String
	 * @param name java.lang.String
	 * @param rawName java.lang.String
	 * @param attributes org.xml.sax.Attributes
	 */
	public void handleStartChildElement(String uri, String name, String rawName, Attributes attributes) {
		// handle the reserved tags first
		if (name.equals(EL_ADD_TAG)) {
			// tag is "add-tag" -- name attribute is the tag we are adding
			// body of the tag is the class name to bind to that tag
			String tagName = attributes.getValue("name");
			
			// push a literal to grab the class name
			handler.put("tag-name", tagName);
			ADD_TAG.setHandler(getHandler());
			handler.push(ADD_TAG);		
			return;
		} else if (name.equals(EL_INCLUDE_PACKAGES)) {
			// tag is include packages, push a literal to grab the package list
			INCLUDE_PACKAGES_TAG.setHandler(getHandler());
			handler.push(INCLUDE_PACKAGES_TAG);		
			return;			
		} else if (name.equals(EL_ADD_CONVENTION)) {
			// tag is add convention, push a literal to grab the conventions
			ADD_CONVENTION_TAG.setHandler(getHandler());
			handler.push(ADD_CONVENTION_TAG);
			return;
		} else {
			// check to see if we have a SAXEL for this tag
			SAX_Element el = (SAX_Element)children.get(name);
			
			if (el == null) {
				// we don't, use the flyweightFactory to get one
				try {
					el = (SAX_Element)flyweightFactory.getInstance("saxel", name);
					if (el != null) {
						addTag(name, el);
					}
				} catch (XCFException e) {
					handleException(e);
				}
			}
			
			// if we have a SAXEL for the tag, push it and start interpreting
			if (el != null) {
				handler.push(el);
				el.startInterpreting(uri, name, rawName, attributes);
			} else {
				handleUnexptectedTag(name);
			}
		}
	}
	
	/**
	 * Invoked whenever a child tag completes interpreting.
	 * 
	 * Handles the post-processing for the two reserved tags: add-tag and include-packages
	 */
	public void handleEndChildElement(String interpreterTag) {
		
		if (interpreterTag.equals(EL_ADD_TAG)) {
			// grab the tag name and tag SAXEL class name off the handler
			// instantiate the class and bind it to the tag
			String tagName = (String)handler.consume("tag-name");
			String tagClass = (String)handler.consume(EL_ADD_TAG);
			
			try {
				Class intrClass = Class.forName(tagClass);
				SAX_Element el = (SAX_Element)intrClass.newInstance();
				addTag(tagName, el);
			} catch (Exception e) {
				handleException(e);
			}
		} else if (interpreterTag.equals(EL_INCLUDE_PACKAGES)) {
			// grab the include list off the handler and add each 
			// package in the list to the flyweight factory's search path
			String includeList = (String)handler.consume(EL_INCLUDE_PACKAGES);
			StringTokenizer iterator = new StringTokenizer(includeList);
			while (iterator.hasMoreTokens()) {
				String includePackage = iterator.nextToken();
				flyweightFactory.addPackage(includePackage);
			}
		} else if (interpreterTag.equals(EL_ADD_CONVENTION)) {
			// grab the conventions off the handler and add each one
			// to the flyweight factory
			String conventions = (String)handler.consume(EL_ADD_CONVENTION);
			StringTokenizer iterator = new StringTokenizer(conventions, "\n");
			while (iterator.hasMoreTokens()) {
				String convention = iterator.nextToken().trim();
				
				if (convention.length() == 0) continue;
				
				int i = convention.indexOf(',');
				if (i < 0 || i >= convention.length()-1) {
					handleError("BAD CONVENTION FORMAT.  Convention must be of the form: type,prefix.  Instead, convention was: " + convention);
					continue;
				}
				String type = convention.substring(0, i).trim();
				String prefix = convention.substring(i+1).trim();
				flyweightFactory.addConvention(type, prefix);
			}
		}
	}
}

SAXEL_Module

This is a new SAXEL. It interprets the “module” tag. To start interpreting, you will want to push a new scope onto the flyweight factory. That way the module can have its own conventions and search path. You will then need to create the module object itself, and put it on the handler context so that the operations, that are child tags, will be able to find the module.

Finishing interpreting is even simpler. Consume the module off of the handler context and add it to the facade. Also, make sure to pop the flyweight scope you added.


package com.eternal.xcf.common.builder;

import org.xml.sax.Attributes;

import com.eternal.xcf.common.request.processor.MODULE_Instructions;
import com.eternal.xcf.common.service.SERVICE_FlyweightFactory;
import com.eternal.xcf.core.XCFFacade;
import com.eternal.xcf.core.XCFModule;
import com.eternal.xcf.core.XCFStrings;
import com.eternal.xcf.sax.SAXEL_Composite;

/**
 * This SAXEL creates a MODULE_Instruction and adding it to the facade.  It will put that module
 * onto the handler so that child SAXELs can configure it.
 * 
 * The syntax is of the form:
 *  
 *  
 * @author sonjaya
 *
 */
public class SAXEL_Module extends SAXEL_Composite {
	public static String XCF_TAG = XCFStrings.MODULE;
	
	public SAXEL_Module() {
		super(XCF_TAG);
	}

	/**
	 * Creates a new flyweight scope 
	 * Creates the module
	 * Puts the module on the handler
	 */
	public void startInterpreting(String uri, String name, String rawName, Attributes attributes) {
		XCFFacade facade = getHandler().getFacade();
		SERVICE_FlyweightFactory flyweightFactory = (SERVICE_FlyweightFactory)facade.getService(SERVICE_FlyweightFactory.XCF_TAG);

		// grab the module name
		String moduleName = attributes.getValue(XCFStrings.NAME);
		
		// it's required, so make sure we have it
		if (moduleName == null) {
			handleError(" must have a name attribute: e.g., ");
			return;
		}
		
		// push the scope so that child tags of this module can have their own search space/conventions
		flyweightFactory.pushScope();
		
		// create the module
		facade.logDebug("===================" + " BEGIN MODULE " + moduleName + "===================");
		XCFModule module = new MODULE_Instructions();
		module.setName(moduleName);
		
		// put it on the handler
		getHandler().put(XCF_TAG, module);
	}

	/**
	 * Finish interpreting by adding the newly configured module to the facade and poping the
	 * flyweight scope we pushed earlier.
	 */
	public void endInterpreting(String uri, String name, String qName) {
		XCFFacade facade = getHandler().getFacade();
		SERVICE_FlyweightFactory flyweightFactory = (SERVICE_FlyweightFactory)facade.getService(SERVICE_FlyweightFactory.XCF_TAG);
		XCFModule module = (XCFModule)getHandler().consume(XCF_TAG);
		if (module == null) return;
		
		// add the module to the facade
		facade.putModule(module.getName(), module);
		
		// pop the flyweight scope
		flyweightFactory.popScope();
		facade.logDebug("===================" + "END MODULE " + module.getName() + "===================");
	}
}

SAXEL_Operation

This is a new SAXEL. It interprets the “operation” tag. Each operation will be bound to an INSTRUCTION_Composite as that object has all the functionality you need. So, you start interpreting by creating an instance of this object and pushing it onto the handler context under the tag “container”. This is different from “putting” it in that putting will replace any current object under that tag. Pushing will create a stack for that tag. A “get” on “container” will always return the most recently pushed “container”. This is very useful as INSTRUCTION objects are composite in nature and may have many level of containers (e.g. operation->parameter processor)

You finish the interpreting by popping the “container” off of the handler context. This object is your operation. Add it to your module.


package com.eternal.xcf.common.builder;

import org.xml.sax.Attributes;

import com.eternal.xcf.common.request.processor.MODULE_Instructions;
import com.eternal.xcf.core.XCFFacade;
import com.eternal.xcf.core.XCFStrings;
import com.eternal.xcf.request.processor.XCFProcessingInstruction;
import com.eternal.xcf.request.processor.instructions.INSTRUCTION_Composite;
import com.eternal.xcf.sax.SAXEL_Composite;

/**
 * This SAXEL creates an INSTRUCTION_Composite and adds it to the module defined
 * in the parent tag as an operation handler
 * 
 * This SAXEL assumes it is a child tag of .
 * This SAXEL will push the instruction it creates onto the handler as an instruction container.
 * 
 * The syntax is of the form:
 *  
 * @author sonjaya
 *
 */
public class SAXEL_Operation extends SAXEL_Composite {
	public static final String XCF_TAG = XCFStrings.OPERATION;
	
	public SAXEL_Operation() {
		super(XCF_TAG);
	}

	/**
	 * Creates an INSTRUCTION_Composite and adds it to the handler as an instruction container
	 */
	public void startInterpreting(String uri, String name, String rawName, Attributes attributes) {
		XCFFacade facade = getHandler().getFacade();

		// grab the operation name
		String operationName = attributes.getValue(XCFStrings.NAME);
		
		// it's required
		if (operationName == null) {
			handleError(" must have a name attribute: e.g., ");
			return;
		}
		
		// make sure we have a module
		MODULE_Instructions module = (MODULE_Instructions)getHandler().get(XCFStrings.MODULE); 
		if (module == null) return;
		
		// create the composite instruction and push it as the instruction container
		// for any child tags
		INSTRUCTION_Composite operation = new INSTRUCTION_Composite(operationName);
		getHandler().pushValue(XCFProcessingInstruction.XCF_TAG, operation);
		
		facade.logDebug("|----" + " BEGIN OPERATION " + operationName + "----------");
	}

	/**
	 * Finish interpreting by grabbing the composite instruction off the handler
	 * and adding it to the module as an operation handler.
	 */
	public void endInterpreting(String uri, String name, String qName) {
		XCFFacade facade = getHandler().getFacade();
		MODULE_Instructions module = (MODULE_Instructions)getHandler().get(XCFStrings.MODULE); 
		if (module == null) return;
		INSTRUCTION_Composite operation = (INSTRUCTION_Composite)getHandler().popValue(XCFProcessingInstruction.XCF_TAG);
		if (operation == null) return;
		
		module.addOperationHandler(operation.getName(), operation);
		facade.logDebug("|----" + " END OPERATION " + operation.getName() + "----------");
	}
}

SAXEL_Do

This is a new SAXEL. It interprets the “do” tag. The “do” tag is a general purpose tag that should support the building of most instruction types. I will be upfront with you. This is not my best design work. I am playing a dangerous game with cohesion and it will bit me in the ass somewhere down the road.

Start interpreting by getting the container instruction from the handler’s context. Then, use the flyweight factory to get an instance of the instruction object specified in the tag and push it as a “container” (that way any child instructions will attach to it) and add it to the container that was present when interpreting started.

Note how I use the tag attributes a bit different for “commands”. Also note how I assume that if the instruction is a command a singleton instance of the flyweight is what’s needed. And that if it is any other type of instruction a new instance is needed. As I said, this is not my best work ;), but it will do for the purposes of this article.

Finish interpreting by simpling popping the “container”. This will remove the instruction you pushed earlier.


package com.eternal.xcf.common.builder;

import org.xml.sax.Attributes;

import com.eternal.xcf.common.service.SERVICE_FlyweightFactory;
import com.eternal.xcf.core.XCFException;
import com.eternal.xcf.core.XCFFacade;
import com.eternal.xcf.core.XCFStrings;
import com.eternal.xcf.request.processor.XCFProcessingInstruction;
import com.eternal.xcf.request.processor.instructions.INSTRUCTION_Composite;
import com.eternal.xcf.sax.SAXEL_Composite;

/**
 * This SAXEL will lookup and create an processing instruction and add it to the composite
 * instruction in the handler.
 * 
 * This SAXEL assumes an INSTRUCTION_Composite exists in handler under the tag XCFProcessingInstruction.XCF_TAG
 * The instruction object created must be a decendant of INSTRUCTION_Composite.
 * 
 * The syntax is of the form:
 * 
 * 
 * This will result in the instruction being created with the flyweight factory as follows:
 *  instruction = flyweightFactory.getInstance("instruction", instruction-type);
 *  instruction.setName(instruction-name)
 *  
 * However, if instruction-type = "command", this will result in the following:
 *  instruction = flyweightFactory.getInstance("command", instruction-name);
 * 
 * In other words, in most cases, intruction-type is used to bind to the actually class name of the instruction.
 * In the case where instruction-type is command, instruction-name is instead used to find the class name.
 * 
 * @author sonjaya
 *
 */
public class SAXEL_Do extends SAXEL_Composite {
	public static final String XCF_TAG = "do";
	
	public SAXEL_Do() {
		super(XCF_TAG);
	}

	/**
	 * Starts interpreting the 'do' tag
	 * Creates a new instruction
	 * Adds that instruction to the container on the handler
	 * Pushes that instruction as the new container
	 */
	public void startInterpreting(String uri, String name, String rawName, Attributes attributes) {
		XCFFacade facade = getHandler().getFacade();
		SERVICE_FlyweightFactory flyweightFactory = (SERVICE_FlyweightFactory)facade.getService(SERVICE_FlyweightFactory.XCF_TAG);
		String instructionType = attributes.getValue(XCFProcessingInstruction.INSTRUCTION);
		String instructionName = attributes.getValue(XCFStrings.NAME);
		
		// if instructionType is not provided, default to a composite instruction
		if (instructionType == null) {
			facade.logDebug(" usually has an instruction type, defaulting to composite.");
			instructionType = "composite";
		}
		
		// instructionName is required
		if (instructionName == null) {
			handleError(" must have a name attribute: e.g., ");
			return;
		}
		
		// get the instruction container
		INSTRUCTION_Composite container = null;
		try {
			container = (INSTRUCTION_Composite)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_Composite.");
			return;
		}
		
		try {
			// create the instruction using the flyweight factory.  Normally we use instructionType as the flyweightTag and "instruction"
			// as the flyweight type.  Check if it is a command, where we use "command" as the flyweightType and instructionName as the
			// flyweight tag.
			String flyweightType = XCFProcessingInstruction.INSTRUCTION;
			boolean isSingleton = false;
			if (instructionType.equals(XCFProcessingInstruction.COMMAND)) {
				flyweightType = XCFProcessingInstruction.COMMAND;
				instructionType = instructionName;
				isSingleton = true;
			}
			XCFProcessingInstruction instruction = (XCFProcessingInstruction)flyweightFactory.getInstance(flyweightType, instructionType, isSingleton);
			instruction.setName(instructionName);

			// make this object the new container and add it to the old one
			getHandler().pushValue(XCFProcessingInstruction.XCF_TAG, instruction);
			container.addInstruction(instruction);	
			facade.logDebug("| ---   BEGIN " + instruction.getName() + "(" + instruction.getClass().getName() +")");
		} catch (XCFException e) {
			handleException(e);
		}
	}

	/**
	 * Finishes up interpreting by popping the instructions from the handler
	 */
	public void endInterpreting(String uri, String name, String qName) {
		XCFFacade facade = getHandler().getFacade();
		XCFProcessingInstruction instruction = (XCFProcessingInstruction)getHandler().popValue(XCFProcessingInstruction.XCF_TAG);
		if (instruction == null) return;
		
		facade.logDebug("| ---   BEGIN " + instruction.getName() + "(" + instruction.getClass().getName() +")");
	}
}

SAXEL_Validate

This is a new SAXEL. It interprets the “validate” tag. Start interpreting by getting the “container” and assuming it is a parameter processor. Use the flyweight factory to get a singleton instance to the validator object and add it to the parameter processor. You don’t need to do anything to finish up interpreting.


package com.eternal.xcf.common.builder;

import org.xml.sax.Attributes;

import com.eternal.xcf.common.service.SERVICE_FlyweightFactory;
import com.eternal.xcf.core.XCFException;
import com.eternal.xcf.core.XCFFacade;
import com.eternal.xcf.core.XCFStrings;
import com.eternal.xcf.request.parameter.XCFValidator;
import com.eternal.xcf.request.processor.XCFProcessingInstruction;
import com.eternal.xcf.request.processor.instructions.INSTRUCTION_Composite;
import com.eternal.xcf.request.processor.instructions.INSTRUCTION_ProcessParameter;
import com.eternal.xcf.sax.SAXEL_Composite;

/**
 * This SAXEL uses the flyweight factory to create a validator and adds that validator
 * to the INSTRUCTION_Processor on the handler.
 * 
 * This SAXEL assumes it is a child tag of a parameter processing definition.
 * 
 * The syntax is of the form:
 *  
 * 
 * The flyweight factory will use [validator-name] as the flyweight tag.
 *  
 * @author sonjaya
 *
 */
public class SAXEL_Validate extends SAXEL_Composite {
	public static final String XCF_TAG = XCFProcessingInstruction.VALIDATE;
	
	public SAXEL_Validate() {
		super(XCF_TAG);
	}

	/**
	 * Make sure the container is a parameter processor
	 * Create the validator and add it to the parameter processor
	 */
	public void startInterpreting(String uri, String name, String rawName, Attributes attributes) {
		XCFFacade facade = getHandler().getFacade();
		SERVICE_FlyweightFactory flyweightFactory = (SERVICE_FlyweightFactory)facade.getService(SERVICE_FlyweightFactory.XCF_TAG);
		
		// grab the validator name
		String validatorName = attributes.getValue(XCFStrings.WITH);
		
		// it's required
		if (validatorName == null) {
			handleError(" must have a with attribute: e.g., ");
			return;
		}
		
		// get the instruction container
		INSTRUCTION_Composite container = null;
		try {
			container = (INSTRUCTION_Composite)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_ProcessParameter.");
			return;
		}
		
		// make sure the container is an INSTRUCTION_ProcessParameter
		if (!(container instanceof INSTRUCTION_ProcessParameter)) {
			handleError(" failed because the container was not an INSTRUCTION_ProcessParameter.");
			return;
		}		
		
		INSTRUCTION_ProcessParameter pp = (INSTRUCTION_ProcessParameter)container;

		// get the validator and add it to the parameter processor
		try {
			XCFValidator validator = (XCFValidator)flyweightFactory.getInstance(XCFProcessingInstruction.VALIDATOR, validatorName);
			pp.addValidator(validator);
			facade.logDebug("|      ADDED validator: " + validatorName + "(" + validator.getClass().getName() + ")");
		} catch (XCFException e) {
			handleException(e);
		}
	}	
}

Testing it out

Create the following XML and save it as simple.xml in com.eternal.xcf.builder



    
    	com.eternal.stubs
    
    
    	command,.CMD_
    

	
		
		    
		    	
		    
			
				
			
  		   
		
	


Also, create the following JUnit test class.


package com.eternal.xcf.builder;

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 TestCoreSax 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");
		
		// 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);
	}
}

To test, simply run the JUnit test class. It should output the following:


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:::| --- BEGIN user-name(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:::| --- BEGIN 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:::| --- BEGIN print-request(com.eternal.stubs.CMD_PrintRequest)
DEBUG:::main:::|---- END OPERATION login----------
DEBUG:::main:::===================END MODULE account===================
INFO:::main:::PROCESSING account.login(password=mypassword, user-name=myloginname)

Conclusion

This lesson concludes the first “section” of articles. You have built enough to model several types of applications. You are now in a position to start using XCF to prototype functionality. Much of the code you write will tolerate significant changes in design and implementation decisions because of the coupling and cohesion standards employed thus far. Enjoy!

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

January 2007
M T W T F S S
« Dec   Feb »
1234567
891011121314
15161718192021
22232425262728
293031  

Most Recent Posts