Revision: 1055
http://stripes.svn.sourceforge.net/stripes/?rev=1055&view=rev
Author: bengunter
Date: 2009-02-26 05:53:16 +0000 (Thu, 26 Feb 2009)
Log Message:
-----------
STS-614: Added ObjectPostProcessor interface that can be implemented and tagged
with @TargetTypes and registered with DefaultObjectFactory. DOF uses
TypeHandlerCache to keep track of which post-processors apply to which types
and only calls them when necessary. These ObjectPostProcessors will be loaded
as Stripes extensions and automatically registered.
Modified Paths:
--------------
trunk/stripes/src/net/sourceforge/stripes/controller/DefaultObjectFactory.java
trunk/tests/src/net/sourceforge/stripes/controller/ObjectFactoryTests.java
Added Paths:
-----------
trunk/stripes/src/net/sourceforge/stripes/controller/ObjectPostProcessor.java
Modified:
trunk/stripes/src/net/sourceforge/stripes/controller/DefaultObjectFactory.java
===================================================================
---
trunk/stripes/src/net/sourceforge/stripes/controller/DefaultObjectFactory.java
2009-02-26 05:26:51 UTC (rev 1054)
+++
trunk/stripes/src/net/sourceforge/stripes/controller/DefaultObjectFactory.java
2009-02-26 05:53:16 UTC (rev 1055)
@@ -1,4 +1,4 @@
-/* Copyright 2008 Ben Gunter
+/* Copyright 2008-2009 Ben Gunter
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,7 +31,10 @@
import java.util.TreeSet;
import net.sourceforge.stripes.config.Configuration;
+import net.sourceforge.stripes.config.TargetTypes;
import net.sourceforge.stripes.exception.StripesRuntimeException;
+import net.sourceforge.stripes.util.Log;
+import net.sourceforge.stripes.util.TypeHandlerCache;
/**
* <p>
@@ -75,6 +78,8 @@
}
}
+ private static final Log log = Log.getInstance(DefaultObjectFactory.class);
+
/**
* Holds a map of commonly used interface types (mostly collections) to a
class that implements
* the interface and will, by default, be instantiated when an instance of
the interface is
@@ -92,6 +97,7 @@
}
private Configuration configuration;
+ private TypeHandlerCache<List<ObjectPostProcessor>> postProcessors;
/** Does nothing. */
public void init(Configuration configuration) throws Exception {
@@ -104,6 +110,44 @@
}
/**
+ * Register a post-processor that will be allowed to manipulate instances
of {...@code targetType}
+ * after they are created and before they are returned. The types to which
the post-processor
+ * will apply are determined by the value of the {...@link TargetTypes}
annotation on the class. If
+ * there is no such annotation, then the post-processor will process all
instances created by
+ * the object factory.
+ *
+ * @param postProcessor The post-processor to use.
+ */
+ public synchronized void addPostProcessor(ObjectPostProcessor
postProcessor) {
+ // The cache will be null by default to indicate that there are no
post-processors
+ if (postProcessors == null) {
+ postProcessors = new TypeHandlerCache<List<ObjectPostProcessor>>();
+ }
+
+ // Determine target types from annotation; if no annotation then
process everything
+ TargetTypes annotation =
postProcessor.getClass().getAnnotation(TargetTypes.class);
+ Class<?>[] targetTypes;
+ if (annotation == null) {
+ targetTypes = new Class<?>[] { Object.class };
+ }
+ else {
+ targetTypes = annotation.value();
+ }
+
+ // Register post-processor for each target type
+ for (Class<?> targetType : targetTypes) {
+ List<ObjectPostProcessor> list =
postProcessors.getHandler(targetType);
+ if (list == null) {
+ list = new ArrayList<ObjectPostProcessor>();
+ postProcessors.add(targetType, list);
+ }
+ log.debug("Adding post-processor of type ",
postProcessor.getClass().getName(),
+ " for ", targetType);
+ list.add(postProcessor);
+ }
+ }
+
+ /**
* Calls {...@link Class#newInstance()} and returns the newly created
object.
*
* @param clazz The class to instantiate.
@@ -151,7 +195,7 @@
"might get implemented.");
}
else {
- return
StripesFilter.getConfiguration().getObjectFactory().newInstance((Class<T>)
impl);
+ return newInstance((Class<T>) impl);
}
}
@@ -260,6 +304,15 @@
* @return The given object, unchanged.
*/
protected <T> T postProcess(T object) {
+ if (postProcessors != null) {
+ List<ObjectPostProcessor> list =
postProcessors.getHandler(object.getClass());
+ if (list != null) {
+ for (ObjectPostProcessor postProcessor : list) {
+ object = postProcessor.postProcess(object);
+ }
+ }
+ }
+
return object;
}
}
Added:
trunk/stripes/src/net/sourceforge/stripes/controller/ObjectPostProcessor.java
===================================================================
---
trunk/stripes/src/net/sourceforge/stripes/controller/ObjectPostProcessor.java
(rev 0)
+++
trunk/stripes/src/net/sourceforge/stripes/controller/ObjectPostProcessor.java
2009-02-26 05:53:16 UTC (rev 1055)
@@ -0,0 +1,34 @@
+/* Copyright 2009 Ben Gunter
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.sourceforge.stripes.controller;
+
+/**
+ * Allows for post-processing of objects created by {...@link
DefaultObjectFactory}. To register a
+ * post-processor with the {...@link ObjectFactory}, you must pass it to $$$.
Implementations of this
+ * interface must be thread-safe, as instances will be reused.
+ *
+ * @author Ben Gunter
+ */
+public interface ObjectPostProcessor {
+ /**
+ * Do whatever post-processing is necessary on the object and return it.
It is not absolutely
+ * required that this method return exactly the same object that was
passed to it, but it is
+ * strongly recommended.
+ *
+ * @param object The object to be processed.
+ * @return The object that was passed in.
+ */
+ <T> T postProcess(T object);
+}
Modified:
trunk/tests/src/net/sourceforge/stripes/controller/ObjectFactoryTests.java
===================================================================
--- trunk/tests/src/net/sourceforge/stripes/controller/ObjectFactoryTests.java
2009-02-26 05:26:51 UTC (rev 1054)
+++ trunk/tests/src/net/sourceforge/stripes/controller/ObjectFactoryTests.java
2009-02-26 05:53:16 UTC (rev 1055)
@@ -15,14 +15,17 @@
package net.sourceforge.stripes.controller;
import java.util.Collection;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
+import java.util.concurrent.atomic.AtomicInteger;
import net.sourceforge.stripes.StripesTestFixture;
+import net.sourceforge.stripes.config.TargetTypes;
import net.sourceforge.stripes.controller.ObjectFactory.ConstructorWrapper;
import net.sourceforge.stripes.exception.StripesRuntimeException;
import net.sourceforge.stripes.util.Log;
@@ -62,7 +65,7 @@
log.debug("Instantiating ", clazz);
Object o = factory.newInstance(clazz);
Assert.assertNotNull(o);
- Assert.assertSame(clazz, o.getClass());
+ Assert.assertSame(o.getClass(), clazz);
}
}
@@ -102,8 +105,8 @@
int a = 37, b = 91;
Adder adder = constructor.newInstance(a, b);
Assert.assertNotNull(adder);
- Assert.assertSame(Adder.class, adder.getClass());
- Assert.assertEquals(a + b, adder.sum());
+ Assert.assertSame(adder.getClass(), Adder.class);
+ Assert.assertEquals(adder.sum(), a + b);
}
/** Attempt to instantiate an interface that does not have a known
implementing class. */
@@ -121,8 +124,11 @@
@Test(groups = "fast")
public void customInterfaceImpl() {
DefaultObjectFactory factory = new DefaultObjectFactory();
+ factory.addImplementingClass(CharSequence.class, String.class);
+ factory.addImplementingClass(List.class, LinkedList.class);
factory.addImplementingClass(Runnable.class, MyRunnable.class);
- instantiateInterfaces(factory, Runnable.class);
+ instantiateInterfaces(factory, CharSequence.class, List.class,
Runnable.class);
+ Assert.assertSame(factory.newInstance(List.class).getClass(),
LinkedList.class);
}
/** Attempt to instantiate a class that does not have a no-arg
constructor. */
@@ -139,7 +145,7 @@
/** Alter an instance via {...@link
DefaultObjectFactory#postProcess(Object)}. */
@Test(groups = "fast")
- public void postProcess() {
+ public void postProcessMethod() {
final String prefix = "Stripey!";
DefaultObjectFactory factory = new DefaultObjectFactory() {
@SuppressWarnings("unchecked")
@@ -155,20 +161,122 @@
final String expect = "TEST";
String string;
- log.debug("Testing post-process skips StringBuilder");
+ log.debug("Testing post-process method skips StringBuilder");
string = factory.constructor(StringBuilder.class,
String.class).newInstance(expect)
.toString();
log.debug("Got " + string);
- Assert.assertEquals(expect, string);
+ Assert.assertEquals(string, expect);
- log.debug("Testing post-process via no-arg constructor");
+ log.debug("Testing post-process method via no-arg constructor");
string = factory.newInstance(String.class);
log.debug("Got " + string);
- Assert.assertEquals(prefix, string);
+ Assert.assertEquals(string, prefix);
- log.debug("Testing post-process via constructor with args");
+ log.debug("Testing post-process method via constructor with args");
string = factory.constructor(String.class,
String.class).newInstance(expect);
log.debug("Got " + string);
- Assert.assertEquals(prefix + expect, string);
+ Assert.assertEquals(string, prefix + expect);
}
+
+ /** Alter an instance via {...@link
DefaultObjectFactory#postProcess(Object)}. */
+ @Test(groups = "fast")
+ public void classPostProcessor() {
+ final String prefix = "Stripey!";
+ @TargetTypes(String.class)
+ class MyObjectPostProcessor implements ObjectPostProcessor {
+ @SuppressWarnings("unchecked")
+ public <T> T postProcess(T object) {
+ log.debug("Altering '", object, "'");
+ return (T) (prefix + object);
+ }
+ }
+
+ DefaultObjectFactory factory = new DefaultObjectFactory();
+ factory.addPostProcessor(new MyObjectPostProcessor());
+
+ final String expect = "TEST";
+ String string;
+
+ log.debug("Testing post-processor impl skips StringBuilder");
+ string = factory.constructor(StringBuilder.class,
String.class).newInstance(expect)
+ .toString();
+ log.debug("Got " + string);
+ Assert.assertEquals(string, expect);
+
+ log.debug("Testing post-processor impl via no-arg constructor");
+ string = factory.newInstance(String.class);
+ log.debug("Got " + string);
+ Assert.assertEquals(string, prefix);
+
+ log.debug("Testing post-processor impl via constructor with args");
+ string = factory.constructor(String.class,
String.class).newInstance(expect);
+ log.debug("Got " + string);
+ Assert.assertEquals(string, prefix + expect);
+ }
+
+ /** Alter an instance via {...@link
DefaultObjectFactory#postProcess(Object)}. */
+ @Test(groups = "fast")
+ public void interfacePostProcessor() {
+ final String prefix = "Stripey!";
+ @TargetTypes(CharSequence.class)
+ class MyObjectPostProcessor implements ObjectPostProcessor {
+ @SuppressWarnings("unchecked")
+ public <T> T postProcess(T object) {
+ log.debug("Altering '", object, "'");
+ return (T) (prefix + object);
+ }
+ }
+
+ DefaultObjectFactory factory = new DefaultObjectFactory();
+ factory.addImplementingClass(Runnable.class, MyRunnable.class);
+ factory.addPostProcessor(new MyObjectPostProcessor());
+
+ final String expect = "TEST";
+ String string;
+
+ log.debug("Testing post-processor impl handles StringBuilder");
+ string = String.valueOf(factory.constructor(StringBuilder.class,
String.class).newInstance(
+ expect));
+ log.debug("Got " + string);
+ Assert.assertEquals(string, prefix + expect);
+
+ log.debug("Testing post-processor impl via no-arg constructor");
+ string = factory.newInstance(String.class);
+ log.debug("Got " + string);
+ Assert.assertEquals(string, prefix);
+
+ log.debug("Testing post-processor impl via constructor with args");
+ string = factory.constructor(String.class,
String.class).newInstance(expect);
+ log.debug("Got " + string);
+ Assert.assertEquals(string, prefix + expect);
+
+ log.debug("Testing post-processor does not handle Runnable");
+ string = factory.newInstance(Runnable.class).getClass().getName();
+ log.debug("Got " + string);
+ Assert.assertEquals(string, MyRunnable.class.getName());
+ }
+
+ @Test(groups = "fast")
+ public void multipleSequentialPostProcessors() {
+ final AtomicInteger counter = new AtomicInteger(0);
+ @TargetTypes(StringBuilder.class)
+ class MyObjectPostProcessor implements ObjectPostProcessor {
+ @SuppressWarnings("unchecked")
+ public <T> T postProcess(T object) {
+ log.debug("Altering '", object, "'");
+ return (T) ((StringBuilder) object).append("Touched by
").append(
+ this.toString().replaceAll(".*@", "")).append("
(counter=").append(
+ counter.addAndGet(1)).append(") ... ");
+ }
+ }
+
+ DefaultObjectFactory factory = new DefaultObjectFactory();
+ for (int i = 0; i < 5; i++) {
+ factory.addPostProcessor(new MyObjectPostProcessor());
+ }
+ log.debug("Testing multiple post-processors");
+ StringBuilder buf = factory.newInstance(StringBuilder.class);
+ log.debug("Got ", buf);
+ Assert.assertEquals(counter.intValue(), 5);
+ }
}
This was sent by the SourceForge.net collaborative development platform, the
world's largest Open Source development site.
------------------------------------------------------------------------------
Open Source Business Conference (OSBC), March 24-25, 2009, San Francisco, CA
-OSBC tackles the biggest issue in open source: Open Sourcing the Enterprise
-Strategies to boost innovation and cut costs with open source participation
-Receive a $600 discount off the registration fee with the source code: SFAD
http://p.sf.net/sfu/XcvMzF8H
_______________________________________________
Stripes-development mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/stripes-development