FRAMEWORK 101: Building XCF – Lesson 03 – Implementing Convention

November 5th, 2006

If you have looked around this blog you know I am a fan of Ruby on Rails. In fact, for most of you, it is how you found this blog. To a large degree version 6 of XCF, the one this series of articles is about, is based on applying my lessons learned from Ruby on Rails. Two things that framework does very is well is its support of DRY (don’t repeat yourself) and its philosophy of “Convention over Configuration”. Today you will add to XCF a class that will help make DRY the path of least resistance by modeling a set of conventions.

Course outline:

NOTE: You can download the source code for today’s lesson here: Lesson 03 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 your first XCFService class. You use this class to create instances of classes that are singleton like classes: that is classes where there will only be one instance. The primary benifit this class provides is that the client will not need to know the package or class name of the object it requests. This will result in the client objects being highly decoupled from the objects created. The following shows the class, SERVICE_FlyweightFactory, in action:


  XCFFacade facade = new XCFFacade();
  SERVICE_FlyweightFactory factory = new SERVICE_FlyweightFactory();
  facade.putService(SERVICE_FlyweightFactory.XCF_TAG, factory);

  factory.addConvention("validator", ".request.parameter.VALIDATOR_");
  factory.pushScope();
  factory.addPackage("com.eternal.xcf.common");

  // this call will return an instance to com.eternal.xcf.common.request.parameter.VALIDATOR_Required
  XCFValidator required = (XCFValidator)factory.getInstance("validator", "required");

  factory.pushScope();
  factory.addPackage("com.eternal.mymodule");

  // this call will return an instance to com.eternal.mymodule.request.parameter.VALIDATOR_Required
  required = (XCFValidator)factory.getInstance("validator", "required");
    

The problem of xml based configuration

Like many java frameworks, version 5 of XCF uses XML for framework configuration. This will not change in the version you are writing (version 6). What will change, however, is the format of that XML configuration. The old XML configuration was light on convention. As a result, terms and classes had to be repeated. This, of course, led to bugs as it was easy to mistype. Many frameworks share this problem. In fact, as soon as you require a class specification in an XML file, you are violating DRY. The reason is that you first specify the package and class when you define it, and then you repeat that in your XML file. In this type of setup, the path of least resistence is to put the class anywhere you see fit. This yields code that is harder to maintain by others and one where it is easy to introduce bugs.

By supporting a simple package and class naming convention for each type of object, your new XCFService class will now make the path of least resistence one where the class is created and named such that it follows a simple convention that is documented AND specified in one place.

Flyweight Structure

You use flyweight structures when you need to support large numbers of fine-grained objects efficiently. From the first two articles, you now know that you will use XCF to model functionality using a facade composed of a multitude of objects such as processing instructions, validators, and setters. Additionally:

  • The object state for each composite object in the facade is extrinsic. For all these objects we pass in the state (usually an XCFContext object)
  • Once the extrinsic state is removed, many groups of objects can be replaced by one. For example, VALIDATOR_Required only requires one instance since the data it validates is passed in as an argument.
  • Objects in the facade are decoupled from each other and do not rely on object identity. For example, a request processing instruction may need to contain an object that validates that a parameter exists. However, that class is not dependant upon the class com.eternal.xcf.common.request.parameter.VALIDATOR_Required. It is only requires that there is some class that performs the “required” functionality.

For these reasons, Flyweight is a good approach.

The workhorse class in flyweight is the flyweight factory. 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.

Flyweight Factory Design

To support this pattern, you will only need to create one class: SERVICE_FlyweightFactory. The actual flyweight objects will be all the other XCF classes such as XCFProcessingInstruction, XCFValidator, XCFSetter, etc. The idea behind this class is prior to construction of the facade, you define a set of conventions in the flyweight factory. These conventions are then used by the factory to convert the key lookup string to a concrete class during facade construction.

The other thing you will want to do is introduce the concept of scope. By having a boilerplate flyweight factory, we provide functionality that allows us to map the tag “required” to the class com.eternal.xcf.common.request.parameter.VALIDATOR_Required. However, down the road you may create modules that need to overide that class and provide a different implementation. By introducing scope and providing a mechanism for pushing and popping scope, you will support the ability to overide flyweights and add new types without adversly affecting convention or requiring configuration that violates DRY.

As you can see from the design, the factory contains a stack of scopes. Each scope contains a search path of packages and a cache of flyweight objects already created in that scope. To find a flyweight instance, the factory will ask each scope to return a flyweight. It will do this from top of the stack on down until a flyweight is returned. Each scope will first look in its cache and if one can’t be found, it will use the naming convention for the object type to generate class names. If the class exists, it will create a flyweight instance.

CONVENTION: The class name will be of the form [search path].[type-prefix][tag].

For example, if the typ prefix convention for “validator” is .request.parameter.VALIDATOR_ and in a particular scope, the search path is:

  • com.eternal.xcf.common
  • com.eternal.xcf.myapp

Then for a request of getInstance("validator","required") the scope will look (and instantiate) the first of the following two classes it finds:

  • com.eternal.xcf.myapp.request.parameter.VALIDATOR_Required
  • com.eternal.xcf.common.request.parameter.VALIDATOR_Required

Flyweight Factory Implementation: SERVICE_FlyweightFactory


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();
	private HashMapconventions = new HashMap();

	/**
	 * 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;
	}

	/**
	 * No extra worked needed when the service starts
	 */
	public void start() throws XCFException {
	}

	/**
	 * No extra work needed when the service stops
	 */
	public void stop() throws XCFException {
	}
	
	/**
	 * 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 {
		// get the sub-package and class name prefix for this type
		String prefix = conventions.get(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);
			
			// 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
			}
			
			// 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);
	}
	
	/**
	 * 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) {
		conventions.put(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
	 *
	 */
	class Scope {
		Stack searchPath = new Stack();
		HashMapcache = new HashMap();
		
		/**
		 * 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);
		}
	}
}

Testing it out

For this test to work, you will need to move the class VALIDATOR_Required that you created in the last lesson to the package com.eternal.xcf.common.request.parameter. You will also need to create the following classes:

  • com.eternal.stubs.request.parameter.SETTER_Custom (implements XCFSetter)
  • com.eternal.scopeTwo.request.parameter.SETTER_Custom (implements XCFSetter)
  • com.eternal.scopeThree.request.parameter.VALIDATOR_Custom (implements XCFValidator)
  • com.eternal.scopeThree.request.parameter.VALIDATOR_Required (implements XCFValidator)
  • com.eternal.scopeThree.request.parameter.SETTER_WithLongName (implements XCFSetter)

As we won’t actually be invoking these classes they can have empty implementations. Now write the following JUnit test class:


package com.eternal.xcf.common.service;

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.XCFLogger;
import com.eternal.xcf.request.parameter.XCFSetter;
import com.eternal.xcf.request.parameter.XCFValidator;

import junit.framework.TestCase;

public class TestFlywieghtFactory extends TestCase {
	
	public void testFlywieghtFactoryScope() throws Exception {
		XCFFacade facade = new XCFFacade();
		SERVICE_FlyweightFactory factory = new SERVICE_FlyweightFactory();
		facade.getLogManager().setLogger(XCFLogger.LogTypes.DEBUG, facade.getLogManager().getLogger(XCFLogger.LOG_TO_CONSOLE));
		
		facade.putService(SERVICE_FlyweightFactory.XCF_TAG, factory);
		
		XCFValidator requiredScope1 = null; // exists in common package
		XCFValidator requiredScope2 = null; // same as scope 1
		XCFValidator requiredScope3 = null; // exists in scope 3 package
		
		XCFSetter customSetterScope1 = null; // exists in stub package
		XCFSetter customSetterScope2 = null; // exists in scope 2 package
		XCFSetter customSetterScope3 = null; // same as scope 2
		
		XCFValidator customValidatorScope3 = null; // exists in scope 3 package
		XCFSetter    setterWithLongNameScope3 = null; // exists in scope 3 package
				
		// add a rule that binds validator to .request.processors.VALIDATOR_
		factory.addConvention("validator", ".request.parameter.VALIDATOR_");
		
		// add a rule that binds setter to .request.processors.SETTER_
		factory.addConvention("setter", ".request.parameter.SETTER_");
		
		/////////////////////////////////////////////////////
		// SCOPE 1 TEST
		// push scope 1
		factory.pushScope();
		factory.addPackage("com.eternal.xcf.common");
		factory.addPackage("com.eternal.stubs");
		
		// ok, get the scope 1 flyweights
		requiredScope1 = (XCFValidator)factory.getInstance("validator", "required");
		customSetterScope1 = (XCFSetter)factory.getInstance("setter", "custom");
		
		// these next two shouldn't be there
		try {customValidatorScope3 = (XCFValidator)factory.getInstance("validator", "custom");} catch (XCFException e) {}
		try {setterWithLongNameScope3 = (XCFSetter)factory.getInstance("setter", "with-long-name");} catch (XCFException e) {}
		
		assertNotNull(requiredScope1);
		assertNotNull(customSetterScope1);
		
		assertTrue(requiredScope1 instanceof com.eternal.xcf.common.request.parameter.VALIDATOR_Required);
		assertTrue(customSetterScope1 instanceof com.eternal.stubs.request.parameter.SETTER_Custom);
			
		assertNull(customValidatorScope3);
		assertNull(setterWithLongNameScope3);
		
		/////////////////////////////////////////////////////
		// SCOPE 2 TEST
		// push scope 2
		factory.pushScope();
		factory.addPackage("com.eternal.scopeTwo");
		
		// ok, get the scope 2 flyweights
		requiredScope2 = (XCFValidator)factory.getInstance("validator", "required");
		customSetterScope2 = (XCFSetter)factory.getInstance("setter", "custom");
		
		// these next two shouldn't be there
		try {customValidatorScope3 = (XCFValidator)factory.getInstance("validator", "custom");} catch (XCFException e) {}
		try {setterWithLongNameScope3 = (XCFSetter)factory.getInstance("setter", "with-long-name");} catch (XCFException e) {}
		
		assertEquals(requiredScope1, requiredScope2);
		assertNotNull(customSetterScope2);
		assertFalse(customSetterScope1 == customSetterScope2);
		
		assertTrue(requiredScope2 instanceof com.eternal.xcf.common.request.parameter.VALIDATOR_Required);
		assertTrue(customSetterScope2 instanceof com.eternal.scopeTwo.request.parameter.SETTER_Custom);

		assertNull(customValidatorScope3);
		assertNull(setterWithLongNameScope3);
		
		/////////////////////////////////////////////////////
		// SCOPE 3 TEST
		// push scope 3
		factory.pushScope();
		factory.addPackage("com.eternal.scopeThree");
		
		// ok, get the scope 3 flyweights
		requiredScope3 = (XCFValidator)factory.getInstance("validator", "required");
		customSetterScope3 = (XCFSetter)factory.getInstance("setter", "custom");
		customValidatorScope3 = (XCFValidator)factory.getInstance("validator", "custom");
		setterWithLongNameScope3 = (XCFSetter)factory.getInstance("setter", "with-long-name");	
		
		assertNotNull(requiredScope3);
		assertFalse(requiredScope1 == requiredScope3);
		assertEquals(customSetterScope2, customSetterScope3);
		
		assertNotNull(customValidatorScope3);
		assertNotNull(setterWithLongNameScope3);
		
		assertTrue(requiredScope3 instanceof com.eternal.scopeThree.request.parameter.VALIDATOR_Required);
		assertTrue(customSetterScope3 instanceof com.eternal.scopeTwo.request.parameter.SETTER_Custom);
		assertTrue(customValidatorScope3 instanceof com.eternal.scopeThree.request.parameter.VALIDATOR_Custom);
		assertTrue(setterWithLongNameScope3 instanceof com.eternal.scopeThree.request.parameter.SETTER_WithLongName);
		
		/////////////////////////////////////////////////////
		// SCOPE 2 TEST after pop of Scope 3
		// pop scope 3
		factory.popScope();

		// ok, get the scope 2 flyweights
		requiredScope2 = (XCFValidator)factory.getInstance("validator", "required");
		customSetterScope2 = (XCFSetter)factory.getInstance("setter", "custom");
		
		// these next two shouldn't be there
		try {customValidatorScope3 = (XCFValidator)factory.getInstance("validator", "custom");} catch (XCFException e) {customValidatorScope3 = null;}
		try {setterWithLongNameScope3 = (XCFSetter)factory.getInstance("setter", "with-long-name");} catch (XCFException e) {setterWithLongNameScope3 = null;}
		
		assertEquals(requiredScope1, requiredScope2);
		assertNotNull(customSetterScope2);
		assertFalse(customSetterScope1 == customSetterScope2);
		
		assertTrue(requiredScope2 instanceof com.eternal.xcf.common.request.parameter.VALIDATOR_Required);
		assertTrue(customSetterScope2 instanceof com.eternal.scopeTwo.request.parameter.SETTER_Custom);

		assertNull(customValidatorScope3);
		assertNull(setterWithLongNameScope3);

		/////////////////////////////////////////////////////
		// SCOPE 3 TEST with a bogus path
		// push a new scope 3 with a non-existent package
		factory.pushScope();
		factory.addPackage("com.eternal.bogus");
		
		requiredScope3 = (XCFValidator)factory.getInstance("validator", "required");
		customSetterScope3 = (XCFSetter)factory.getInstance("setter", "custom");

		// these next two shouldn't be there
		try {customValidatorScope3 = (XCFValidator)factory.getInstance("validator", "custom");} catch (XCFException e) {customValidatorScope3 = null;}
		try {setterWithLongNameScope3 = (XCFSetter)factory.getInstance("setter", "with-long-name");} catch (XCFException e) {setterWithLongNameScope3 = null;}

		assertEquals(requiredScope1, requiredScope3);
		assertEquals(customSetterScope2, customSetterScope3);
		
		assertTrue(requiredScope3 instanceof com.eternal.xcf.common.request.parameter.VALIDATOR_Required);
		assertTrue(customSetterScope3 instanceof com.eternal.scopeTwo.request.parameter.SETTER_Custom);

		assertNull(customValidatorScope3);
		assertNull(setterWithLongNameScope3);
		
		/////////////////////////////////////////////////////
		// SCOPE 2 TEST after pop of Scope 3
		// pop scope 3
		factory.popScope();
		// ok, get the scope 2 flyweights
		requiredScope2 = (XCFValidator)factory.getInstance("validator", "required");
		customSetterScope2 = (XCFSetter)factory.getInstance("setter", "custom");
		
		// these next two shouldn't be there
		try {customValidatorScope3 = (XCFValidator)factory.getInstance("validator", "custom");} catch (XCFException e) {customValidatorScope3 = null;}
		try {setterWithLongNameScope3 = (XCFSetter)factory.getInstance("setter", "with-long-name");} catch (XCFException e) {setterWithLongNameScope3 = null;}
		
		assertEquals(requiredScope1, requiredScope2);
		assertNotNull(customSetterScope2);
		assertFalse(customSetterScope1 == customSetterScope2);
		
		assertTrue(requiredScope2 instanceof com.eternal.xcf.common.request.parameter.VALIDATOR_Required);
		assertTrue(customSetterScope2 instanceof com.eternal.scopeTwo.request.parameter.SETTER_Custom);

		assertNull(customValidatorScope3);
		assertNull(setterWithLongNameScope3);
		
		/////////////////////////////////////////////////////
		// SCOPE 3 TEST
		// push scope 3 with old package
		factory.pushScope();
		factory.addPackage("com.eternal.scopeThree");
		
		// ok, get the scope 3 flyweights
		requiredScope3 = (XCFValidator)factory.getInstance("validator", "required");
		customSetterScope3 = (XCFSetter)factory.getInstance("setter", "custom");
		customValidatorScope3 = (XCFValidator)factory.getInstance("validator", "custom");
		setterWithLongNameScope3 = (XCFSetter)factory.getInstance("setter", "with-long-name");	
		
		assertNotNull(requiredScope3);
		assertFalse(requiredScope1 == requiredScope3);
		assertEquals(customSetterScope2, customSetterScope3);
		
		assertNotNull(customValidatorScope3);
		assertNotNull(customValidatorScope3);
				
		assertTrue(requiredScope3 instanceof com.eternal.scopeThree.request.parameter.VALIDATOR_Required);
		assertTrue(customSetterScope3 instanceof com.eternal.scopeTwo.request.parameter.SETTER_Custom);
		assertTrue(customValidatorScope3 instanceof com.eternal.scopeThree.request.parameter.VALIDATOR_Custom);
		assertTrue(setterWithLongNameScope3 instanceof com.eternal.scopeThree.request.parameter.SETTER_WithLongName);

		/////////////////////////////////////////////////////
		// SCOPE 2 TEST after pop of Scope 3
		// pop scope 3
		factory.popScope();
		requiredScope2 = (XCFValidator)factory.getInstance("validator", "required");
		customSetterScope2 = (XCFSetter)factory.getInstance("setter", "custom");
		
		// these next two shouldn't be there
		try {customValidatorScope3 = (XCFValidator)factory.getInstance("validator", "custom");} catch (XCFException e) {customValidatorScope3 = null;}
		try {setterWithLongNameScope3 = (XCFSetter)factory.getInstance("setter", "with-long-name");} catch (XCFException e) {setterWithLongNameScope3 = null;}
		
		assertEquals(requiredScope1, requiredScope2);
		assertNotNull(customSetterScope2);
		assertFalse(customSetterScope1 == customSetterScope2);
		
		assertTrue(requiredScope2 instanceof com.eternal.xcf.common.request.parameter.VALIDATOR_Required);
		assertTrue(customSetterScope2 instanceof com.eternal.scopeTwo.request.parameter.SETTER_Custom);

		assertNull(customValidatorScope3);
		assertNull(setterWithLongNameScope3);

		/////////////////////////////////////////////////////
		// SCOPE 1 TEST after pop of Scope 2
		// pop scope 2
		factory.popScope();
		// ok, get the scope 1 flyweights
		requiredScope1 = (XCFValidator)factory.getInstance("validator", "required");
		customSetterScope1 = (XCFSetter)factory.getInstance("setter", "custom");
		
		// these next two shouldn't be there
		try {customValidatorScope3 = (XCFValidator)factory.getInstance("validator", "custom");} catch (XCFException e) {customValidatorScope3 = null;}
		try {setterWithLongNameScope3 = (XCFSetter)factory.getInstance("setter", "with-long-name");} catch (XCFException e) {setterWithLongNameScope3 = null;}
		
		assertNotNull(requiredScope1);
		assertNotNull(customSetterScope1);
		
		assertTrue(requiredScope1 instanceof com.eternal.xcf.common.request.parameter.VALIDATOR_Required);
		assertTrue(customSetterScope1 instanceof com.eternal.stubs.request.parameter.SETTER_Custom);

		assertNull(customValidatorScope3);
		assertNull(setterWithLongNameScope3);

		/////////////////////////////////////////////////////
		// NULL SCOPE TEST 
		// pop scope 1
		factory.popScope();
		try {requiredScope1 = (XCFValidator)factory.getInstance("validator", "custom");} catch (XCFException e) {requiredScope1 = null;}
		assertNull(requiredScope1);
	}
	
	public void testFlyweightFactorySearchPath() throws Exception {
		XCFFacade facade = new XCFFacade();
		SERVICE_FlyweightFactory factory = new SERVICE_FlyweightFactory();
		facade.getLogManager().setLogger(XCFLogger.LogTypes.DEBUG, facade.getLogManager().getLogger(XCFLogger.LOG_TO_CONSOLE));
		
		facade.putService(SERVICE_FlyweightFactory.XCF_TAG, factory);
		
		// add a rule that binds validator to .request.processors.VALIDATOR_
		factory.addConvention("validator", ".request.parameter.VALIDATOR_");

		XCFValidator requiredScope1 = null; // exists in common package
		
		factory.pushScope();
		factory.addPackage("com.eternal.xcf.common");
		factory.addPackage("com.eternal.scopeThree");
		requiredScope1 = (XCFValidator)factory.getInstance("validator", "required");
		
		assertTrue(requiredScope1 instanceof com.eternal.scopeThree.request.parameter.VALIDATOR_Required);
		
	}
}

Conclusion

In lesson one you created the concept of a facade. In lesson two you created the interfaces that will be used o model the request processing in that facade. Today, you created a class that will critical to the process you will use to build the facade. This class will greatly reduce configuration by modeling a convention that not only dictates how classes are names but also what sub-package they are placed. Because of this you have made the path of least resistence one where people will name classes in a manner where their function is easier to intuite and place them in a package future engineers will expect them. In the next lessons we will move on to the actual act of building the facade using an XML file as the director.

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

November 2006
M T W T F S S
« Oct   Dec »
 12345
6789101112
13141516171819
20212223242526
27282930  

Most Recent Posts