diff --git a/servlet/src/main/java/com/twelvemonkeys/servlet/ServletConfigurator.java b/servlet/src/main/java/com/twelvemonkeys/servlet/ServletConfigurator.java
new file mode 100644
index 00000000..d04bfeee
--- /dev/null
+++ b/servlet/src/main/java/com/twelvemonkeys/servlet/ServletConfigurator.java
@@ -0,0 +1,241 @@
+package com.twelvemonkeys.servlet;
+
+import com.twelvemonkeys.lang.StringUtil;
+import com.twelvemonkeys.util.FilterIterator;
+import com.twelvemonkeys.util.convert.ConversionException;
+import com.twelvemonkeys.util.convert.Converter;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterConfig;
+import javax.servlet.Servlet;
+import javax.servlet.ServletConfig;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.*;
+
+/**
+ * ServletConfigurator
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: ServletConfigurator.java,v 1.0 Apr 30, 2010 2:51:38 PM haraldk Exp$
+ * @see com.twelvemonkeys.servlet.InitParam
+ */
+final class ServletConfigurator {
+ // TODO: Rethink @InitParam? Allow annotation of method parameters instead? Allows setLocation(@InitParam int x, @InitParam int y)
+ // TODO: At least allow field injection
+ // TODO: defaultValue, required
+
+ private ServletConfigurator() {
+ }
+
+ public static void configure(final Servlet pServlet, final ServletConfig pConfig) throws ServletConfigException {
+ new Configurator(pServlet, pConfig.getServletName()).configure(ServletUtil.asMap(pConfig));
+ }
+
+ public static void configure(final Filter pFilter, final FilterConfig pConfig) throws ServletConfigException {
+ new Configurator(pFilter, pConfig.getFilterName()).configure(ServletUtil.asMap(pConfig));
+ }
+
+ private static class Configurator {
+ private final Object servletOrFilter;
+ private final String name;
+
+ private Configurator(final Object servletOrFilter, final String name) {
+ this.servletOrFilter = servletOrFilter;
+ this.name = name;
+ }
+
+ private void configure(final Map pMapping) throws ServletConfigException {
+ // Loop over methods with InitParam annotations
+ for (Method method : annotatedMethods(servletOrFilter.getClass(), InitParam.class)) {
+ assertAcceptableMethod(method);
+
+ // Get value or default, throw exception if missing required value
+ Object value = getConfiguredValue(method, pMapping);
+
+ if (value != null) {
+ // Inject value to this method
+ try {
+ method.invoke(servletOrFilter, value);
+ }
+ catch (IllegalAccessException e) {
+ // We know the method is accessible, so this should never happen
+ throw new Error(e);
+ }
+ catch (InvocationTargetException e) {
+ throw new ServletConfigException(String.format("Could not configure %s: %s", name, e.getCause().getMessage()), e.getCause());
+ }
+ }
+ }
+
+ // TODO: Loop over fields with InitParam annotations
+
+ // TODO: Log warning for mappings not present among InitParam annotated methods?
+ }
+
+ private Object getConfiguredValue(final Method method, final Map mapping) throws ServletConfigException {
+ InitParam initParam = method.getAnnotation(InitParam.class);
+ String paramName = getParameterName(method, initParam);
+
+ // Get parameter value
+ String stringValue = mapping.get(paramName);
+
+ if (stringValue == null && initParam.name().equals(InitParam.UNDEFINED)) {
+ stringValue = mapping.get(StringUtil.camelToLisp(paramName));
+ }
+
+ if (stringValue == null) {
+ // InitParam support required = true and throw exception if not present in map
+ if (initParam.required()) {
+ throw new ServletConfigException(
+ String.format(
+ "Could not configure %s: Required init-parameter \"%s\" of type %s is missing",
+ name, paramName, method.getParameterTypes()[0]
+ )
+ );
+ }
+ else if (!initParam.defaultValue().equals(InitParam.UNDEFINED)) {
+ // Support default values
+ stringValue = initParam.defaultValue();
+ }
+ }
+
+ // Convert value based on method arguments...
+ return stringValue == null ? null : convertValue(method, stringValue);
+ }
+
+ private Object convertValue(final Method method, final String stringValue) throws ServletConfigException {
+ // We know it's a single parameter method
+ Class> type = method.getParameterTypes()[0];
+
+ try {
+ return String.class.equals(type) ? stringValue : Converter.getInstance().toObject(stringValue, type);
+ }
+ catch (ConversionException e) {
+ throw new ServletConfigException(e);
+ }
+ }
+
+ private String getParameterName(final Method method, final InitParam initParam) throws ServletConfigException {
+ String paramName = initParam.name();
+
+ if (paramName.equals(InitParam.UNDEFINED)) {
+ String methodName = method.getName();
+ if (methodName.startsWith("set") && methodName.length() > 3) {
+ paramName = Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4);
+ }
+ else {
+ throw new ServletConfigException(
+ String.format(
+ "Could not configure %s: InitParam annotated method must either specify name or follow Bean standard for properties (ie. setFoo => 'foo'): %s",
+ name, method
+ )
+ );
+ }
+ }
+
+ return paramName;
+ }
+
+ private void assertAcceptableMethod(final Method method) throws ServletConfigException {
+ // Try to use setAccessible, if not public
+ boolean isAccessible = Modifier.isPublic(method.getModifiers());
+
+ if (!isAccessible) {
+ try {
+ method.setAccessible(true);
+ isAccessible = true;
+ }
+ catch (SecurityException ignore) {
+ // Won't be accessible, we'll fail below
+ }
+ }
+
+ if (!isAccessible || method.getReturnType() != Void.TYPE || method.getParameterTypes().length != 1) {
+ throw new ServletConfigException(
+ String.format(
+ "Could not configure %s: InitParam annotated method must be public void and have a single parameter argument list: %s",
+ name, method
+ )
+ );
+ }
+ }
+
+
+ /**
+ * Gets all methods annotated with the given annotations.
+ *
+ * @param pClass the class to get annotated methods from
+ * @param pAnnotations the annotations to test for
+ * @return an iterable that allows iterating over all methods with the given annotations.
+ */
+ private Iterable annotatedMethods(final Class> pClass, final Class extends Annotation>... pAnnotations) {
+ return new Iterable() {
+ public Iterator iterator() {
+ Set methods = new LinkedHashSet();
+
+ Class> cl = pClass;
+ while (cl.getSuperclass() != null) { // There's no annotations of interest on java.lang.Object
+ methods.addAll(Arrays.asList(cl.getDeclaredMethods()));
+
+ // TODO: What about interface methods? Do we really want them?
+ Class>[] interfaces = cl.getInterfaces();
+ for (Class> i : interfaces) {
+ methods.addAll(Arrays.asList(i.getDeclaredMethods()));
+ }
+
+ cl = cl.getSuperclass();
+ }
+
+ return new FilterIterator(methods.iterator(), new FilterIterator.Filter() {
+ public boolean accept(final Method pMethod) {
+ for (Class extends Annotation> annotation : pAnnotations) {
+ if (!pMethod.isAnnotationPresent(annotation) || isOverriddenWithAnnotation(pMethod, annotation)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * @param pMethod the method to test for override
+ * @param pAnnotation the annotation that must be present
+ * @return {@code true} iff the method is overridden in a subclass, and has annotation
+ * @see The Java Language Specification: Classes: Inheritance, Overriding, and Hiding
+ */
+ private boolean isOverriddenWithAnnotation(final Method pMethod, final Class extends Annotation> pAnnotation) {
+ if (Modifier.isPrivate(pMethod.getModifiers())) {
+ return false;
+ }
+
+ Class cl = pClass;
+
+ // Loop down up from subclass to superclass declaring the method
+ while (cl != null && !pMethod.getDeclaringClass().equals(cl)) {
+ try {
+ Method override = cl.getDeclaredMethod(pMethod.getName(), pMethod.getParameterTypes());
+
+ // Overridden, test if it has the annotation present
+ if (override.isAnnotationPresent(pAnnotation)) {
+ return true;
+ }
+
+ }
+ catch (NoSuchMethodException ignore) {
+ }
+
+ cl = cl.getSuperclass();
+ }
+
+ return false;
+ }
+ });
+ }
+ };
+ }
+ }
+}