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

Reply via email to