commit f705e530f7d48392f2e37fa68b458ac7ce4ec2c0
parent 0ea43eb64bac1ede702a657a50d38d76fe45e3f3
Author: Dan Callaghan <djc@djc.id.au>
Date: Sat, 8 May 2010 20:36:34 +1000
annotation-based AttributeRenderer detection
Diffstat:
4 files changed, 199 insertions(+), 5 deletions(-)
diff --git a/src/main/java/au/id/djc/stringtemplate/AnnotationAttributeRendererGenerator.java b/src/main/java/au/id/djc/stringtemplate/AnnotationAttributeRendererGenerator.java
@@ -0,0 +1,139 @@
+package au.id.djc.stringtemplate;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.logging.Logger;
+
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.support.ApplicationObjectSupport;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.util.ReflectionUtils;
+
+/**
+ * This bean will automatically populate your {@link ApplicationContext} with an
+ * {@link AttributeRenderer} implementation for each method on any (singleton)
+ * beans which is annotated with {@link AttributeRendererMethod}.
+ */
+public class AnnotationAttributeRendererGenerator extends ApplicationObjectSupport {
+
+ private static final Logger LOG = Logger.getLogger(AnnotationAttributeRendererGenerator.class.getName());
+
+ private static final class MethodWrapper {
+
+ private final String format;
+ private final String beanName;
+ private final Method method;
+
+ public MethodWrapper(String format, String beanName, Method method) {
+ this.format = format;
+ this.beanName = beanName;
+ this.method = method;
+ }
+
+ @Override
+ public String toString() {
+ return "MethodWrapper[" + beanName + "," + method.getName() + "]";
+ }
+
+ }
+
+ private static final class MethodWrappingAttributeRenderer implements AttributeRenderer {
+
+ private final ApplicationContext applicationContext;
+ private final Class<?> targetClass;
+ private final Map<String, MethodWrapper> methodsByFormat = new HashMap<String, MethodWrapper>();
+
+ public MethodWrappingAttributeRenderer(ApplicationContext applicationContext,
+ Class<?> targetClass, List<MethodWrapper> methodWrappers) {
+ this.applicationContext = applicationContext;
+ this.targetClass = targetClass;
+ for (MethodWrapper methodWrapper : methodWrappers) {
+ methodsByFormat.put(methodWrapper.format, methodWrapper);
+ }
+ LOG.info("Registering generated AttributeRenderer targeting " + targetClass.getName() + " with methods " + methodsByFormat);
+ }
+
+ @Override
+ public Class<?> getTargetClass() {
+ return targetClass;
+ }
+
+ @Override
+ public String toString(Object o) {
+ return toString(o, "");
+ }
+
+ @Override
+ public String toString(Object o, String formatName) {
+ MethodWrapper methodWrapper = methodsByFormat.get(formatName);
+ if (methodWrapper == null) {
+ if (!formatName.isEmpty()) {
+ return toString(o, "");
+ } else {
+ return o.toString();
+ }
+ }
+ Object bean = applicationContext.getBean(methodWrapper.beanName);
+ try {
+ return (String) methodWrapper.method.invoke(bean, o);
+ } catch (IllegalArgumentException e) {
+ throw new RuntimeException(e);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ } catch (InvocationTargetException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ }
+
+ private final MultiValueMap<Class<?>, MethodWrapper> methodsByTargetClass = new LinkedMultiValueMap<Class<?>, MethodWrapper>();
+
+ @Override
+ protected void initApplicationContext() throws BeansException {
+ String[] beanNames = getApplicationContext().getBeanNamesForType(Object.class, false, true);
+ for (final String beanName : beanNames) {
+ Object bean = getApplicationContext().getBean(beanName);
+ ReflectionUtils.doWithMethods(bean.getClass(), new ReflectionUtils.MethodCallback() {
+ @Override
+ public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
+ AttributeRendererMethod annotation = AnnotationUtils.findAnnotation(method, AttributeRendererMethod.class);
+ if (annotation != null) {
+ if (method.getParameterTypes().length != 1) {
+ throw new IllegalArgumentException("AttributeRenderer method does not take exactly one argument: "
+ + method.getName() + " of bean " + beanName);
+ }
+ if (!String.class.isAssignableFrom(method.getReturnType())) {
+ throw new IllegalArgumentException("AttributeRenderer method does not return String: "
+ + method.getName() + " of bean " + beanName);
+ }
+ methodsByTargetClass.add(method.getParameterTypes()[0],
+ new MethodWrapper(annotation.format(), beanName, method));
+ }
+ }
+ });
+ }
+ registerAttributeRendererBeans();
+ }
+
+ private void registerAttributeRendererBeans() {
+ int i = 1;
+ for (Entry<Class<?>, List<MethodWrapper>> entry : methodsByTargetClass.entrySet()) {
+ MethodWrappingAttributeRenderer attributeRenderer =
+ new MethodWrappingAttributeRenderer(getApplicationContext(), entry.getKey(), entry.getValue());
+ ((ConfigurableApplicationContext) getApplicationContext()).getBeanFactory().registerSingleton(
+ String.format("generatedAttributeRenderer#%d-targeting-%s", i, entry.getKey().getName()),
+ attributeRenderer);
+ i ++;
+ }
+ }
+
+}
diff --git a/src/main/java/au/id/djc/stringtemplate/AttributeRendererMethod.java b/src/main/java/au/id/djc/stringtemplate/AttributeRendererMethod.java
@@ -0,0 +1,14 @@
+package au.id.djc.stringtemplate;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface AttributeRendererMethod {
+
+ String format() default "";
+
+}
diff --git a/src/main/java/au/id/djc/stringtemplate/webmvc/StringTemplateViewResolver.java b/src/main/java/au/id/djc/stringtemplate/webmvc/StringTemplateViewResolver.java
@@ -24,10 +24,6 @@ public class StringTemplateViewResolver extends AbstractTemplateViewResolver imp
if (errorListener == null) {
throw new IllegalArgumentException("Property 'errorListener' is required");
}
- if (attributeRenderers == null) {
- attributeRenderers = new ArrayList<AttributeRenderer>(
- BeanFactoryUtils.beansOfTypeIncludingAncestors(getApplicationContext(), AttributeRenderer.class).values());
- }
}
public void setRootTemplateName(String rootTemplateName) {
@@ -65,7 +61,12 @@ public class StringTemplateViewResolver extends AbstractTemplateViewResolver imp
if (rootTemplateName != null) view.setRootTemplateName(rootTemplateName);
if (charset != null) view.setCharset(charset);
view.setErrorListener(errorListener);
- view.setAttributeRenderers(attributeRenderers);
+ if (attributeRenderers == null) {
+ view.setAttributeRenderers(new ArrayList<AttributeRenderer>(
+ BeanFactoryUtils.beansOfTypeIncludingAncestors(getApplicationContext(), AttributeRenderer.class).values()));
+ } else {
+ view.setAttributeRenderers(attributeRenderers);
+ }
return view;
}
diff --git a/src/test/java/au/id/djc/stringtemplate/AnnotationAttributeRendererGeneratorUnitTest.java b/src/test/java/au/id/djc/stringtemplate/AnnotationAttributeRendererGeneratorUnitTest.java
@@ -0,0 +1,40 @@
+package au.id.djc.stringtemplate;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.junit.Assert.*;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.context.support.StaticApplicationContext;
+
+public class AnnotationAttributeRendererGeneratorUnitTest {
+
+ private StaticApplicationContext context;
+
+ @Before
+ public void setUp() {
+ context = new StaticApplicationContext();
+ context.registerSingleton("dummyBean", Object.class);
+ }
+
+ @Test
+ public void shouldRegisterNothingForNoAnnotatedBeans() {
+ context.registerSingleton("generator", AnnotationAttributeRendererGenerator.class);
+ context.refresh();
+ assertThat(context.getBeansOfType(AttributeRenderer.class).size(), equalTo(0));
+ }
+
+ public static class Annotated {
+ @AttributeRendererMethod
+ public String render(String s) { return s; }
+ }
+
+ @Test
+ public void shouldRegisterGeneratedBean() {
+ context.registerSingleton("annotated", Annotated.class);
+ context.registerSingleton("generator", AnnotationAttributeRendererGenerator.class);
+ context.refresh();
+ assertThat(context.getBeansOfType(AttributeRenderer.class).size(), equalTo(1));
+ }
+
+}