Using JSF’s managed beans to access constants

So I’ve been working on this JSF project for a while. Like most Java projects we have a few constant classes where we keep some constant values (no surprise there). Until now we’ve typically had trouble using those constants in a JSP page as the JSF EL has no way to access any class. You’re pretty much stuck with the managed beans. So often we would just have a method on the managed bean called getConstantValue() which would return the constant value we wanted to use. We had also written some custom components and just set rtexprvalue to true which allowed us to access a constant with . However today I ran into an issue where I need to get some constant values and it didn’t seem practical to have a bunch of getters on the managed bean. I had an idea though. The JSF EL has no real way of calling a method with parameters, but you can access keys in a Map. So essentially this means you can have an JSF EL expression like #{bean.myMap[‘key’]} which will translate into bean.getMyMap.get(key). Looks like we can pass a parameter after all. How do we take advantage of this? Easy really… you just need to create your own Map implementation and do whatever you want in the “get” method.

So now I just need a function to access my constants. I decided that if I want to access a constant called MY_VALUE from a class called Constants easiest way would be to pass a String “Constants.MY_VALUE” and use reflection on the other side to get the value. But then I realized that using reflection would require a fully qualified class name which will make the code look very cluttered and ugly. So instead I decided to keep a Map of class names and their corresponding classes.

    
private final static Map CONSTANT_CLASSES;
static {
    CONSTANT_CLASSES = new HashMap();
    CONSTANT_CLASSES.put("Constants", Constants.class);
}

With this I can pass “Constants” by itself and let the map worry about the fully qualified class name. The disadvantage to this is if I decide to add another constants class I have to change this class too. However realistically it’s not very often that you’ll be adding new constants classes.

Next is the actual get method that will retrieve the value. Reflection is still the way to go here:

 public Object get(Object key) {
	Object value = null;

	String[] asString = ((String) key).split("\\u002e");

	if (asString.length != 2) {
		throw new PropertyNotFoundException(
			"Invalid key format for key \"" + key + "\"");
	}

	Class clazz = (Class) CONSTANT_CLASSES.get(asString[0]);

	if (clazz == null) {
		throw new PropertyNotFoundException("Class \"" + 
			clazz + "\" is not a defined as a Constants class");
	}

	try {
		Field field = clazz.getDeclaredField(asString[1]);
		int mods = field.getModifiers();

		if (Modifier.isPublic(mods) && Modifier.isFinal(mods) 
						&& Modifier.isStatic(mods)) {
			value = clazz.getDeclaredField(asString[1]).get(null);
		} else {
			throw new PropertyNotFoundException("No \"public " +
			final static\" field exists for the key " + key);
		}
	} catch (Exception e) {
		throw new PropertyNotFoundException(e);
	}
	return value;
}

Now I was concerned about the performance of this since reflection has been known to be a bit slower. Although I doubt any significant performance will arise from using this I still thought it would be safer to add some sort of cache to this. It’s a really simple cache. It’s just a HashMap which will store the key parameter and map it to the value retrieved using reflection. So really reflection will only be use the first time a particular constant is used. After that it will just be a get() call on a HashMap. The code for this is almost identical except for the cache part.

public Object get(Object key) {
	Object value = CACHE.get(key);

	if (value == null) {
		String[] asString = ((String) key).split("\\u002e");

		if (asString.length != 2) {
			throw new PropertyNotFoundException(
				"Invalid key format for key \"" + key + "\"");
		}

		Class clazz = (Class) CONSTANT_CLASSES.get(asString[0]);

		if (clazz == null) {
			throw new PropertyNotFoundException("Class \"" + clazz + 
				"\" is not a defined as a Constants class");
		}

		try {

			Field field = clazz.getDeclaredField(asString[1]);
			int mods = field.getModifiers();

			if (Modifier.isPublic(mods) && Modifier.isFinal(mods) 
						&& Modifier.isStatic(mods)) {
				value = clazz
					.getDeclaredField(asString[1])
					.get(null);

				CACHE.put(key, value);
			} else {
				throw new PropertyNotFoundException(
				"No \"public final static\" field exists for the key " + key);
			}

		} catch (Exception e) {
			throw new PropertyNotFoundException(e);
		}
	}
	return value;
}

This is what the full class looks like:

public class MbConstants {
	private final static Logger log;

	// Map to cache retrieved values
	private final static Map CACHE;

	/*
	 Constant classes must be defined here!
	 */
	private final static Map CONSTANT_CLASSES;
	static {
		log = Logger.getLogger(MbConstants.class);

		CACHE = new HashMap();

		CONSTANT_CLASSES = new HashMap();

	}

	/**
	 * An anonymous Map type that will be used to get values from the Constant
	 * classes. The first time a value is accessed it will be retrived using
	 * reflection and stored in the cache. Subsequent requests for the same key
	 * will just come from the cache. Since the JSF EL has no way of passing
	 * parameters to a function using a Map is the next best alternative since
	 * we can do #{bean.map['key'} which translates to Map#get(key).
	 */
	private final Map constants = new Map() {
		public Object get(Object key) {
			Object value = CACHE.get(key);

			if (value == null) {
				String[] asString = ((String) key).split("\\u002e");

				if (asString.length != 2) {
					log.error("Invalid key format for key \"" + key + "\"");
					throw new PropertyNotFoundException(
							"Invalid key format for key \"" + key + "\"");
				}
				Class clazz = (Class) CONSTANT_CLASSES.get(asString[0]);
				if (clazz == null) {
					log.error("Class \"" + clazz + "\" is not a defined as a Constants class");
					throw new PropertyNotFoundException("Class \"" + clazz + "\" is not a defined as a Constants class");
				}
				try {
					Field field = clazz.getDeclaredField(asString[1]);
					int mods = field.getModifiers();
					if (Modifier.isPublic(mods) && Modifier.isFinal(mods) && Modifier.isStatic(mods)) {
						value = clazz.getDeclaredField(asString[1]).get(null);
						CACHE.put(key, value);
					} else {
						throw new PropertyNotFoundException("No \"public final static\" field exists for the key " + key);
					}
				} catch (Exception e) {
					log.error("Unable to find constant value with key " + key);
					log.error(e.getMessage(), e);
					throw new PropertyNotFoundException(e);
				}

			}
			return value;
		}

		public boolean containsKey(Object key) {
			return false;
		}

		public boolean containsValue(Object value) {
			return false;
		}

		public Set entrySet() {
			return null;
		}

		public Collection values() {
			return null;
		}

		public void clear() {
		}

		public boolean isEmpty() {
			return false;
		}

		public Set keySet() {
			return null;
		}

		public Object put(Object key, Object value) {
			return null;
		}

		public void putAll(Map t) {
		}

		public Object remove(Object key) {
			return null;
		}

		public int size() {
			return 0;
		}

	};

	public Map getConstants() {
		return constants;
	}
}
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: