You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tapestry.apache.org by hl...@apache.org on 2006/07/30 01:28:42 UTC

svn commit: r426850 - in /tapestry/tapestry5/tapestry-core/trunk: ./ src/main/java/org/apache/tapestry/internal/ src/main/java/org/apache/tapestry/internal/ioc/services/ src/main/java/org/apache/tapestry/internal/services/ src/main/java/org/apache/tape...

Author: hlship
Date: Sat Jul 29 16:28:41 2006
New Revision: 426850

URL: http://svn.apache.org/viewvc?rev=426850&view=rev
Log:
Finish up the ChainBuilder service and tests.
Add some documentation about chain of command and ChainBuilder.
Replace the HiveMind configuration files with Tapestry IoC module classes.

Added:
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/InternalModule.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/transform/InternalTransformModule.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/transform/TransformModule.java
    tapestry/tapestry5/tapestry-core/trunk/src/site/apt/ioc/command.apt
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/ioc/services/ChainBuilderImplTest.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/ioc/services/ChainCommand.java
Removed:
    tapestry/tapestry5/tapestry-core/trunk/src/main/resources/META-INF/hivemodule.xml
    tapestry/tapestry5/tapestry-core/trunk/src/main/resources/META-INF/org.apache.tapestry.internal.services.xml
    tapestry/tapestry5/tapestry-core/trunk/src/main/resources/META-INF/org.apache.tapestry.internal.transform.xml
    tapestry/tapestry5/tapestry-core/trunk/src/main/resources/META-INF/org.apache.tapestry.transform.xml
Modified:
    tapestry/tapestry5/tapestry-core/trunk/pom.xml
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/services/ChainBuilderImpl.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/services/PerThreadServiceLifecycle.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/UpdateListenerHub.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/transform/ComponentClassTransformerImpl.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/transform/ComponentInstantiatorSourceImpl.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/ChainBuilder.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/TapestryIOCModule.java
    tapestry/tapestry5/tapestry-core/trunk/src/site/apt/ioc/service.apt
    tapestry/tapestry5/tapestry-core/trunk/src/site/site.xml
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/transform/ComponentInstantiatorSourceImplTest.java

Modified: tapestry/tapestry5/tapestry-core/trunk/pom.xml
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/pom.xml?rev=426850&r1=426849&r2=426850&view=diff
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/pom.xml (original)
+++ tapestry/tapestry5/tapestry-core/trunk/pom.xml Sat Jul 29 16:28:41 2006
@@ -82,7 +82,12 @@
                 <configuration>
                     <archive>
                         <manifestEntries>
-                            <Tapestry-Module-Classes>org.apache.tapestry.ioc.services.TapestryIOCModule</Tapestry-Module-Classes>
+                            <Tapestry-Module-Classes>
+                                org.apache.tapestry.ioc.services.TapestryIOCModule,
+                                org.apache.tapestry.transform.TransformModule,
+                                org.apache.tapestry.internal.InternalModule,
+                                org.apache.tapestry.internal.transform.InternalTransformModule
+                            </Tapestry-Module-Classes>
                         </manifestEntries>
                     </archive>
                 </configuration>

Added: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/InternalModule.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/InternalModule.java?rev=426850&view=auto
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/InternalModule.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/InternalModule.java Sat Jul 29 16:28:41 2006
@@ -0,0 +1,55 @@
+// Copyright 2006 The Apache Software Foundation
+//
+// 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 org.apache.tapestry.internal;
+
+import org.apache.commons.logging.Log;
+import org.apache.tapestry.annotations.Match;
+import org.apache.tapestry.internal.services.UpdateListenerHub;
+import org.apache.tapestry.internal.services.UpdateListenerHubImpl;
+import org.apache.tapestry.ioc.annotations.Before;
+import org.apache.tapestry.ioc.annotations.Id;
+import org.apache.tapestry.ioc.annotations.InjectService;
+import org.apache.tapestry.ioc.services.LoggingDecorator;
+
+/**
+ * @author Howard M. Lewis Ship
+ */
+@Id("tapestry.internal.services")
+public final class InternalModule
+{
+    /**
+     * The UpdateListenerHub is responsible for tracking which listeners are interested when some
+     * part of external storage changes (invalidating cached data).
+     */
+    public UpdateListenerHub buildUpdateListenerHub()
+    {
+        return new UpdateListenerHubImpl();
+    }
+
+    /**
+     * All public services in the tapestry module, and in any sub-module of tapestry will get
+     * logging.
+     */
+    @Match(
+    { "tapestry.*", "tapestry.*.*" })
+    @Before("*")
+    public <T> T decorateWithLogging(Class<T> serviceInterface, T delegate, String serviceId,
+            Log log, @InjectService("tapestry.ioc.LoggingDecorator")
+            LoggingDecorator loggingDecorator)
+    {
+        return loggingDecorator
+                .createLoggingInterceptor(serviceInterface, delegate, serviceId, log);
+    }
+}

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/services/ChainBuilderImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/services/ChainBuilderImpl.java?rev=426850&r1=426849&r2=426850&view=diff
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/services/ChainBuilderImpl.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/services/ChainBuilderImpl.java Sat Jul 29 16:28:41 2006
@@ -1,3 +1,17 @@
+// Copyright 2006 The Apache Software Foundation
+//
+// 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 org.apache.tapestry.internal.ioc.services;
 
 import java.lang.reflect.Constructor;
@@ -15,6 +29,8 @@
 import org.apache.tapestry.util.BodyBuilder;
 import org.apache.tapestry.util.CollectionFactory;
 
+import static java.lang.String.format;
+
 /**
  * @author Howard M. Lewis Ship
  */
@@ -28,32 +44,23 @@
         _classFactory = classFactory;
     }
 
-    public <T> T buildImplementation(Class<T> commandInterface, List<T> commands)
+    public <T> T buildChain(Class<T> commandInterface, List<T> commands)
     {
-        return null;
+        Class<T> chainClass = findImplementationClass(commandInterface);
+
+        return createInstance(chainClass, commands);
     }
 
     /**
      * Map, keyed on service interface, of implementation Class.
      */
 
-    private Map<Class, Class> _implementations = CollectionFactory.newMap();
-
-    // public Object buildImplementation(Class commandInterface, List commands, String toString)
-    // {
-    // Defense.notNull(commandInterface, "commandInterface");
-    // Defense.notNull(commands, "commands");
-    // Defense.notNull(toString, "toString");
-    //
-    // Class instanceClass = findImplementationClass(commandInterface);
-    //
-    // return createInstance(instanceClass, commands, toString);
-    // }
+    private Map<Class, Class> _cache = CollectionFactory.newMap();
 
     @Synchronized.Read
     private Class findImplementationClass(Class commandInterface)
     {
-        Class result = (Class) _implementations.get(commandInterface);
+        Class result = (Class) _cache.get(commandInterface);
 
         if (result == null)
             result = constructImplementationClass(commandInterface);
@@ -76,7 +83,7 @@
 
         // This is the part that really needs synchronization:
 
-        _implementations.put(commandInterface, result);
+        _cache.put(commandInterface, result);
 
         return result;
 
@@ -85,8 +92,11 @@
         // same command interface simultaneously).
     }
 
-    void addInfrastructure(ClassFab cf, Class commandInterface)
+    private void addInfrastructure(ClassFab cf, Class commandInterface)
     {
+        // Array types are very, very tricky to deal with.
+        // Also, generics don't help (<T> new T[]) is still java.lang.Object[].
+
         String arrayClassName = commandInterface.getCanonicalName() + "[]";
         String jvmName = org.apache.tapestry.ioc.services.ClassFabUtils
                 .getJVMClassName(arrayClassName);
@@ -95,7 +105,11 @@
 
         try
         {
-            array = Class.forName(jvmName, true, commandInterface.getClass().getClassLoader());
+            ClassLoader loader = commandInterface.getClass().getClassLoader();
+            if (loader == null)
+                loader = Thread.currentThread().getContextClassLoader();
+
+            array = Class.forName(jvmName, true, loader);
         }
         catch (Exception ex)
         {
@@ -106,11 +120,10 @@
         cf.addField("_commands", array);
 
         BodyBuilder builder = new BodyBuilder();
-
         builder.addln("_commands = (%s[]) $1.toArray(new %<s[0]);", commandInterface.getName());
 
         cf.addConstructor(new Class[]
-        { List.class, String.class }, null, builder.toString());
+        { List.class }, null, builder.toString());
     }
 
     private <T> T createInstance(Class<T> instanceClass, List<T> commands)
@@ -130,7 +143,7 @@
 
     }
 
-    void addMethods(ClassFab cf, Class commandInterface)
+    private void addMethods(ClassFab cf, Class commandInterface)
     {
         MethodIterator mi = new MethodIterator(commandInterface);
 
@@ -142,10 +155,10 @@
         }
 
         if (!mi.getToString())
-            addToString(cf);
+            cf.addToString(format("<Command chain of %s>", commandInterface.getName()));
     }
 
-    void addMethod(ClassFab cf, Class commandInterface, MethodSignature sig)
+    private void addMethod(ClassFab cf, Class commandInterface, MethodSignature sig)
     {
         Class returnType = sig.getReturnType();
 
@@ -160,17 +173,13 @@
         BodyBuilder builder = new BodyBuilder();
         builder.begin();
 
-        builder
-                .addln(
-                        "{0} result = {1};",
-                        ClassFabUtils.getJavaClassName(returnType),
-                        defaultValue);
+        builder.addln("%s result = %s;", ClassFabUtils.getJavaClassName(returnType), defaultValue);
         builder.addln("for (int i = 0; i < _commands.length; i++)");
 
         builder.begin();
-        builder.addln("result = _commands[i].{0}($$);", sig.getName());
+        builder.addln("result = _commands[i].%s($$);", sig.getName());
 
-        builder.addln("if (result != {0}) break;", defaultValue);
+        builder.addln("if (result != %s) break;", defaultValue);
 
         builder.end();
 
@@ -180,7 +189,7 @@
         cf.addMethod(Modifier.PUBLIC, sig, builder.toString());
     }
 
-    String defaultForReturnType(Class returnType)
+    private String defaultForReturnType(Class returnType)
     {
         // For all object and array types.
 
@@ -191,7 +200,8 @@
             return "false";
 
         // Assume, then, that it is a numeric type (this method
-        // isn't called for void).
+        // isn't called for type void). Javassist seems to be
+        // able to handle 0 for all numeric types.
 
         return "0";
     }
@@ -203,18 +213,11 @@
         builder.begin();
 
         builder.addln("for (int i = 0; i < _commands.length; i++)");
-        builder.addln("_commands[i].{0}($$);", sig.getName());
+        builder.addln("_commands[i].%s($$);", sig.getName());
 
         builder.end();
 
         cf.addMethod(Modifier.PUBLIC, sig, builder.toString());
-    }
-
-    void addToString(ClassFab cf)
-    {
-        MethodSignature sig = new MethodSignature(String.class, "toString", null, null);
-
-        cf.addMethod(Modifier.PUBLIC, sig, "return _toString;");
     }
 
 }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/services/PerThreadServiceLifecycle.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/services/PerThreadServiceLifecycle.java?rev=426850&r1=426849&r2=426850&view=diff
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/services/PerThreadServiceLifecycle.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/services/PerThreadServiceLifecycle.java Sat Jul 29 16:28:41 2006
@@ -87,9 +87,8 @@
 
         // Constructor takes a ServiceCreator
 
-        // Caution: Javassist needs this to be a block, not just a single statement!
         cf.addConstructor(new Class[]
-        { ServiceCreator.class }, null, "{ _creator = $1; }");
+        { ServiceCreator.class }, null, "_creator = $1;");
 
         String body = format("return (%s) _creator.createService();", serviceInterface.getName());
 

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/UpdateListenerHub.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/UpdateListenerHub.java?rev=426850&r1=426849&r2=426850&view=diff
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/UpdateListenerHub.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/UpdateListenerHub.java Sat Jul 29 16:28:41 2006
@@ -25,6 +25,7 @@
 {
     void addUpdateListener(UpdateListener listener);
 
+    /** For completeness. */
     void removeUpdateListener(UpdateListener listener);
 
     void fireUpdateEvent();

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/transform/ComponentClassTransformerImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/transform/ComponentClassTransformerImpl.java?rev=426850&r1=426849&r2=426850&view=diff
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/transform/ComponentClassTransformerImpl.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/transform/ComponentClassTransformerImpl.java Sat Jul 29 16:28:41 2006
@@ -41,7 +41,16 @@
     /** Map from class name to class transformation. */
     private final Map<String, InternalClassTransformation> _nameToClassTransformation = newMap();
 
-    private ComponentClassTransformWorker _workers;
+    private final ComponentClassTransformWorker _workerChain;
+
+    /**
+     * @param workerChain
+     *            the ordered list of class transform works as a chain of command instance
+     */
+    public ComponentClassTransformerImpl(ComponentClassTransformWorker workerChain)
+    {
+        _workerChain = workerChain;
+    }
 
     /**
      * Clears the cache of {@link InternalClassTransformation} instances whenever the class loader
@@ -90,7 +99,7 @@
 
         MutableComponentModel model = new MutableComponentModelImpl();
 
-        _workers.transform(transformation, model);
+        _workerChain.transform(transformation, model);
 
         transformation.finish();
 
@@ -109,14 +118,4 @@
 
         return ct.createInstantiator(componentClass);
     }
-
-    /**
-     * For injection. This will usually be an ordered series of
-     * {@link ComponentClassTransformWorker}s, as a chain-of-command.
-     */
-    public final void setWorkers(ComponentClassTransformWorker workers)
-    {
-        _workers = workers;
-    }
-
 }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/transform/ComponentInstantiatorSourceImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/transform/ComponentInstantiatorSourceImpl.java?rev=426850&r1=426849&r2=426850&view=diff
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/transform/ComponentInstantiatorSourceImpl.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/transform/ComponentInstantiatorSourceImpl.java Sat Jul 29 16:28:41 2006
@@ -14,9 +14,6 @@
 
 package org.apache.tapestry.internal.transform;
 
-import static org.apache.tapestry.util.CollectionFactory.newMap;
-import static org.apache.tapestry.util.CollectionFactory.newSet;
-
 import java.net.URL;
 import java.util.Map;
 import java.util.Set;
@@ -31,13 +28,15 @@
 import javassist.Translator;
 
 import org.apache.commons.logging.Log;
-import org.apache.hivemind.ClassResolver;
 import org.apache.tapestry.events.UpdateEvent;
 import org.apache.tapestry.events.UpdateListener;
 import org.apache.tapestry.internal.event.InvalidationEventHubImpl;
 import org.apache.tapestry.internal.util.URLChangeTracker;
 import org.apache.tapestry.util.Defense;
 
+import static org.apache.tapestry.util.CollectionFactory.newMap;
+import static org.apache.tapestry.util.CollectionFactory.newSet;
+
 /**
  * A wrapper around a Javassist class loader that allows certain classes to be modified as they are
  * loaded.
@@ -57,9 +56,9 @@
 
     private Loader _loader;
 
-    private ComponentClassTransformer _transformer;
+    private final ComponentClassTransformer _transformer;
 
-    private Log _log;
+    private final Log _log;
 
     /** Map from class name to Instantiator. */
     private final Map<String, Instantiator> _instantiatorMap = newMap();
@@ -70,14 +69,19 @@
         return _loader;
     }
 
-    public ComponentInstantiatorSourceImpl(ClassResolver resolver)
+    public ComponentInstantiatorSourceImpl(ComponentClassTransformer transformer, Log log)
     {
-        this(resolver.getClassLoader());
+        this(Thread.currentThread().getContextClassLoader(), transformer, log);
     }
 
-    public ComponentInstantiatorSourceImpl(ClassLoader parent)
+    public ComponentInstantiatorSourceImpl(ClassLoader parent,
+            ComponentClassTransformer transformer, Log log)
     {
         _parent = parent;
+        _transformer = transformer;
+        _log = log;
+
+        initializeService();
     }
 
     private class PackageAwareLoader extends Loader
@@ -118,7 +122,11 @@
         fireInvalidationEvent();
     }
 
-    public void initializeService()
+    /**
+     * Invoked at object crtation, or when there are updates, to create a new set of Javassist class
+     * pools and loaders.
+     */
+    private void initializeService()
     {
         _classPool = new ClassPool();
 
@@ -258,17 +266,4 @@
 
         _controlledPackageNames.add(packageName);
     }
-
-    /** For injection. */
-    public void setTransformer(ComponentClassTransformer transformer)
-    {
-        _transformer = transformer;
-    }
-
-    /** For injection. */
-    public void setLog(Log log)
-    {
-        _log = log;
-    }
-
 }

Added: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/transform/InternalTransformModule.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/transform/InternalTransformModule.java?rev=426850&view=auto
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/transform/InternalTransformModule.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/transform/InternalTransformModule.java Sat Jul 29 16:28:41 2006
@@ -0,0 +1,70 @@
+// Copyright 2006 The Apache Software Foundation
+//
+// 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 org.apache.tapestry.internal.transform;
+
+import org.apache.commons.logging.Log;
+import org.apache.tapestry.internal.services.UpdateListenerHub;
+import org.apache.tapestry.internal.transform.worker.RetainWorker;
+import org.apache.tapestry.internal.transform.worker.UnclaimedFieldWorker;
+import org.apache.tapestry.ioc.OrderedConfiguration;
+import org.apache.tapestry.ioc.annotations.Contribute;
+import org.apache.tapestry.ioc.annotations.Id;
+import org.apache.tapestry.ioc.annotations.InjectService;
+import org.apache.tapestry.transform.ComponentClassTransformWorker;
+
+/**
+ * Counter part to {@link org.apache.tapestry.transform.TransformModule} that discusses actual
+ * implementations.
+ * 
+ * @author Howard M. Lewis Ship
+ */
+@Id("tapestry.internal.transform")
+public final class InternalTransformModule
+{
+    @Contribute("tapestry.transform.ComponentClassTransformWorker")
+    public void contributeInternalWorkers(
+            OrderedConfiguration<ComponentClassTransformWorker> configuration)
+    {
+        configuration.add("Retain", new RetainWorker());
+        configuration.add("UnclaimedField", "*", null, new UnclaimedFieldWorker());
+    }
+
+    public ComponentClassTransformer buildComponentClassTransformer(
+            @InjectService("tapestry.transform.ComponentClassTransformWorker")
+            ComponentClassTransformWorker workerChain,
+            @InjectService("ComponentInstantiatorSource")
+            ComponentInstantiatorSource cis)
+    {
+        ComponentClassTransformerImpl transformer = new ComponentClassTransformerImpl(workerChain);
+
+        cis.addInvalidationListener(transformer);
+
+        return transformer;
+    }
+
+    public ComponentInstantiatorSource buildComponentInstantiatorSource(
+            @InjectService("ComponentClassTransformer")
+            ComponentClassTransformer transformer,
+            @InjectService("tapestry.internal.services.UpdateListenerHub")
+            UpdateListenerHub hub, Log log)
+    {
+        ComponentInstantiatorSourceImpl source = new ComponentInstantiatorSourceImpl(transformer,
+                log);
+
+        hub.addUpdateListener(source);
+
+        return source;
+    }
+}

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/ChainBuilder.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/ChainBuilder.java?rev=426850&r1=426849&r2=426850&view=diff
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/ChainBuilder.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/ChainBuilder.java Sat Jul 29 16:28:41 2006
@@ -22,9 +22,10 @@
  * Four Chain Of Command pattern.
  * <p>
  * For each method in the interface, the chain implementation will call the corresponding method on
- * each command object in turn. If any of the command objects return true, then the chain of command
- * stops and the initial method invocation returns true. Otherwise, the chain of command continues
- * to the next command (and will return false if none of the commands returns true).
+ * each command object in turn (with the order defined by the list). If any of the command objects
+ * return true, then the chain of command stops and the initial method invocation returns true.
+ * Otherwise, the chain of command continues to the next command (and will return false if none of
+ * the commands returns true).
  * <p>
  * For methods whose return type is not boolean, the chain stops with the first non-null (for object
  * types), or non-zero (for numeric types). The chain returns the value that was returned by the
@@ -36,5 +37,9 @@
  */
 public interface ChainBuilder
 {
-    <T> T buildImplementation(Class<T> commandInterface, List<T> commands);
+    /**
+     * Creates a chain instance from a command interface and a list of commands (implementing the
+     * interface).
+     */
+    <T> T buildChain(Class<T> commandInterface, List<T> commands);
 }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/TapestryIOCModule.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/TapestryIOCModule.java?rev=426850&r1=426849&r2=426850&view=diff
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/TapestryIOCModule.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/TapestryIOCModule.java Sat Jul 29 16:28:41 2006
@@ -17,6 +17,7 @@
 import java.util.Map;
 
 import org.apache.commons.logging.Log;
+import org.apache.tapestry.internal.ioc.services.ChainBuilderImpl;
 import org.apache.tapestry.internal.ioc.services.ClassFactoryImpl;
 import org.apache.tapestry.internal.ioc.services.LoggingDecoratorImpl;
 import org.apache.tapestry.internal.ioc.services.PerThreadServiceLifecycle;
@@ -33,7 +34,7 @@
  * @author Howard M. Lewis Ship
  */
 @Id("tapestry.ioc")
-public class TapestryIOCModule
+public final class TapestryIOCModule
 {
     /**
      * The ClassFactory service is used to create new classes at runtime.
@@ -90,5 +91,15 @@
         configuration.add(
                 "perthread",
                 new PerThreadServiceLifecycle(threadCleanupHub, classFactory));
+    }
+
+    /**
+     * A service that implements the chain of command pattern, creating an efficient implementation
+     * of a chain of command for an arbitrary interface.
+     */
+    public ChainBuilder buildChainBuilder(@InjectService("ClassFactory")
+    ClassFactory classFactory)
+    {
+        return new ChainBuilderImpl(classFactory);
     }
 }

Added: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/transform/TransformModule.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/transform/TransformModule.java?rev=426850&view=auto
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/transform/TransformModule.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/transform/TransformModule.java Sat Jul 29 16:28:41 2006
@@ -0,0 +1,42 @@
+// Copyright 2006 The Apache Software Foundation
+//
+// 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 org.apache.tapestry.transform;
+
+import java.util.List;
+
+import org.apache.tapestry.ioc.annotations.Id;
+import org.apache.tapestry.ioc.annotations.InjectService;
+import org.apache.tapestry.ioc.services.ChainBuilder;
+
+/**
+ * Public facing module that defines some services and configuration used when transforming
+ * component classes (as they are loaded into memory).
+ * 
+ * @author Howard M. Lewis Ship
+ */
+@Id("tapestry.transform")
+public final class TransformModule
+{
+    /**
+     * Allows the exact steps in the component class transformation process to be defined.
+     */
+    public ComponentClassTransformWorker buildComponentClassTransformWorker(
+            List<ComponentClassTransformWorker> configuration,
+            @InjectService("tapestry.ioc.ChainBuilder")
+            ChainBuilder chainBuilder)
+    {
+        return chainBuilder.buildChain(ComponentClassTransformWorker.class, configuration);
+    }
+}

Added: tapestry/tapestry5/tapestry-core/trunk/src/site/apt/ioc/command.apt
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/site/apt/ioc/command.apt?rev=426850&view=auto
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/site/apt/ioc/command.apt (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/site/apt/ioc/command.apt Sat Jul 29 16:28:41 2006
@@ -0,0 +1,74 @@
+ ----
+ Chain of Command
+ ----
+ 
+Chain of Command
+ 
+  One of the most useful of the Gang Of Four Design Patterns is the <<command>> pattern.
+
+  With the command pattern, a complex process is broken down into many individual steps.  The
+  steps are the <commands> in the command pattern.  A key part of this is that the commands
+  are expected to implement some common interface. The commands are carefully arrainged into
+  a specific order.
+
+  The process operates by working down the list of commands. Each command is given a chance
+  to operate. A command can terminate the process either by throwing an exception, or by
+  returning true.
+  
+  The return type of the command method does not have to be boolean:  For object types,
+  any non-null value short-circuits the process. For numeric type, any non-zero value.
+  For void methods, only throwing an exception will short circuit the process.
+  
+  Often, the command interface consists of a single method. When the command interface
+  has multiple methods, each can be thought of as its own chain.
+  
+  This is a useful pattern because it makes it very easy to <extend> a given process,
+  simply by providing new commands and specifying where they fit into the overall
+  process. Most of chain of command is combined with an ordered 
+  {{{configuration.html}configuration}} to define what the command are (and in what
+  order they should execute).
+  
+ChainBuilder Service
+
+  Because this pattern is used so often inside Tapestry, a built-in service exists
+  to create implementations of the pattern as needed.  The
+  {{{../apidocs/org/apache/tapestry/ioc/services/ChainBuilder.html}tapestry.ioc.ChainBuilder}}
+  service takes care of all the work:
+  
++----+
+public interface ChainBuilder
+{
+  <T> T buildChain(Class<T> commandInterface, List<T> commands);
+}
++----+
+
+  All that generics parameterization just ensures that the command interface matches
+  the items in the list, and confirms that a single instance of the command interface
+  will be returned.
+
+  Invoking this method returns an object that encapsulates the chain of command for a
+  particular interface and a particular list of commands implementing that interface.  
+  
+  This can be used inside a service builder method.  Nothing says a service builder method
+  just has to just instantiate a class; it is only required to return an appropriate object.
+  We can just let the ChainBuilder service create that object.
+  
++----+
+  public MyChainService buildMyChainService(List<MyChainService> commands,
+    @InjectService("tapestry.ioc.ChainBuilder")
+    ChainBuilder chainBuilder)
+  {
+     return chainBuilder.buildChain(MyChainService.class, commands);
+  }
++----+
+
+  Here, the behavior of the MyChainService is defined by its configuration: an ordered
+  list of MyChainService commands that are contributed by many modules.
+  
+  Internally, the ChainBuilder creates a new class that implements the service interface.
+  The list of commands is converted into an array, which is used inside the service implementation
+  (for maximum efficiency).  Therefore, changing the list after creating the chain
+  instance will not affect the chain instance's behavior.
+  
+  ChainBuilder will reuse the fabricated class for any number of chains of the same
+  command interface.
\ No newline at end of file

Modified: tapestry/tapestry5/tapestry-core/trunk/src/site/apt/ioc/service.apt
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/site/apt/ioc/service.apt?rev=426850&r1=426849&r2=426850&view=diff
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/site/apt/ioc/service.apt (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/site/apt/ioc/service.apt Sat Jul 29 16:28:41 2006
@@ -158,6 +158,15 @@
   The important thing is to invoke the cleanupThread() method, to discard any request/thread
   specific information, before processing the next request, message, transaction, or
   what have you.  
+  
+  <<Caution:>> A common technique in Tapestry IoC is to have a service builder method
+  registry a core service implementation as an event listener with some event hub service.
+  With non-singleton objects, this can cause a number of problems; the event hub will
+  hold a reference to the per-thread instance, even after that per-thread instance has been
+  cleaned up (discarded by the inner proxy). Simply put, this is a pattern to avoid. For
+  the most part, perthread services should be simple holders of data specific to a thread or
+  a request, and should not have overly complex relationships with the other services
+  in the registry.
      
 Injecting Resources
 

Modified: tapestry/tapestry5/tapestry-core/trunk/src/site/site.xml
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/site/site.xml?rev=426850&r1=426849&r2=426850&view=diff
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/site/site.xml (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/site/site.xml Sat Jul 29 16:28:41 2006
@@ -56,6 +56,7 @@
             <item name="Services" href="ioc/service.html"/>
             <item name="Decorators" href="ioc/decorator.html"/>
             <item name="Configuration" href="ioc/configuration.html"/>
+            <item name="Chain Of Command" href="ioc/command.html"/>
         </menu>
                 
         ${reports}

Added: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/ioc/services/ChainBuilderImplTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/ioc/services/ChainBuilderImplTest.java?rev=426850&view=auto
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/ioc/services/ChainBuilderImplTest.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/ioc/services/ChainBuilderImplTest.java Sat Jul 29 16:28:41 2006
@@ -0,0 +1,201 @@
+// Copyright 2006 The Apache Software Foundation
+//
+// 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 org.apache.tapestry.internal.ioc.services;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.tapestry.internal.test.InternalBaseTestCase;
+import org.apache.tapestry.ioc.services.ChainBuilder;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+
+/**
+ * @author Howard M. Lewis Ship
+ */
+public class ChainBuilderImplTest extends InternalBaseTestCase
+{
+    private final ChainBuilder _builder = new ChainBuilderImpl(new ClassFactoryImpl());
+
+    @Test
+    public void simple_void_method()
+    {
+        Runnable r1 = newRunnable();
+        Runnable r2 = newRunnable();
+
+        // Training:
+
+        r1.run();
+        r2.run();
+
+        replay();
+
+        Runnable chain = build(Runnable.class, r1, r2);
+
+        chain.run();
+
+        verify();
+
+        Assert.assertEquals(chain.toString(), "<Command chain of java.lang.Runnable>");
+    }
+
+    @Test
+    public void int_method()
+    {
+        ChainCommand c1 = newChainCommand();
+        ChainCommand c2 = newChainCommand();
+
+        c1.workInt(7);
+        setReturnValue(0);
+
+        c2.workInt(7);
+        setReturnValue(99);
+
+        replay();
+
+        ChainCommand chain = build(ChainCommand.class, c1, c2);
+
+        assertEquals(chain.workInt(7), 99);
+
+        verify();
+    }
+
+    @Test
+    public void int_method_shortcircuits()
+    {
+        ChainCommand c1 = newChainCommand();
+        ChainCommand c2 = newChainCommand();
+
+        c1.workInt(7);
+        setReturnValue(88);
+
+        replay();
+
+        ChainCommand chain = build(ChainCommand.class, c1, c2);
+
+        assertEquals(chain.workInt(7), 88);
+
+        verify();
+    }
+
+    @Test
+    public void boolean_method()
+    {
+        ChainCommand c1 = newChainCommand();
+        ChainCommand c2 = newChainCommand();
+
+        c1.workBoolean(true);
+        setReturnValue(false);
+
+        c2.workBoolean(true);
+        setReturnValue(true);
+
+        replay();
+
+        ChainCommand chain = build(ChainCommand.class, c1, c2);
+
+        assertEquals(chain.workBoolean(true), true);
+
+        verify();
+    }
+
+    @Test
+    public void string_method()
+    {
+        ChainCommand c1 = newChainCommand();
+        ChainCommand c2 = newChainCommand();
+
+        c1.workString("fred");
+        setReturnValue(null);
+
+        c2.workString("fred");
+        setReturnValue("flintstone");
+
+        replay();
+
+        ChainCommand chain = build(ChainCommand.class, c1, c2);
+
+        assertEquals(chain.workString("fred"), "flintstone");
+
+        verify();
+
+    }
+
+    @Test
+    public void double_method()
+    {
+        ChainCommand c1 = newChainCommand();
+        ChainCommand c2 = newChainCommand();
+
+        c1.workDouble(1.2d);
+        setReturnValue(0);
+
+        c2.workDouble(1.2d);
+        setReturnValue(3.14d);
+
+        replay();
+
+        ChainCommand chain = build(ChainCommand.class, c1, c2);
+
+        assertEquals(chain.workDouble(1.2d), 3.14d);
+
+        verify();
+    }
+
+    private ChainCommand newChainCommand()
+    {
+        return newMock(ChainCommand.class);
+    }
+
+    @Test
+    public void fabricated_classes_are_reused()
+    {
+        Runnable r1 = newRunnable();
+        Runnable r2 = newRunnable();
+
+        Runnable chain1 = build(Runnable.class, r1);
+        Runnable chain2 = build(Runnable.class, r2);
+
+        Assert.assertSame(chain1.getClass(), chain2.getClass());
+
+        // Now make sure that the two instances are independent.
+
+        r1.run();
+
+        replay();
+
+        chain1.run();
+
+        verify();
+
+        r2.run();
+
+        replay();
+
+        chain2.run();
+
+        verify();
+    }
+
+    private <T> T build(Class<T> commandInterface, T... commands)
+    {
+        List<T> list = Arrays.asList(commands);
+
+        return _builder.buildChain(commandInterface, list);
+    }
+
+}

Added: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/ioc/services/ChainCommand.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/ioc/services/ChainCommand.java?rev=426850&view=auto
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/ioc/services/ChainCommand.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/ioc/services/ChainCommand.java Sat Jul 29 16:28:41 2006
@@ -0,0 +1,31 @@
+// Copyright 2006 The Apache Software Foundation
+//
+// 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 org.apache.tapestry.internal.ioc.services;
+
+/**
+ * Used with {@link org.apache.tapestry.internal.ioc.services.ChainBuilderImplTest}.
+ * 
+ * @author Howard M. Lewis Ship
+ */
+public interface ChainCommand
+{
+    int workInt(int input);
+
+    boolean workBoolean(boolean input);
+
+    double workDouble(double input);
+
+    String workString(String input);
+}

Modified: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/transform/ComponentInstantiatorSourceImplTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/transform/ComponentInstantiatorSourceImplTest.java?rev=426850&r1=426849&r2=426850&view=diff
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/transform/ComponentInstantiatorSourceImplTest.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/transform/ComponentInstantiatorSourceImplTest.java Sat Jul 29 16:28:41 2006
@@ -14,16 +14,10 @@
 
 package org.apache.tapestry.internal.transform;
 
-import static java.lang.Thread.currentThread;
-import static org.apache.hivemind.util.PropertyUtils.read;
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertNull;
-
 import java.io.File;
 import java.io.IOException;
 import java.net.URL;
 import java.net.URLClassLoader;
-import java.util.Locale;
 import java.util.UUID;
 
 import javassist.CannotCompileException;
@@ -34,23 +28,28 @@
 import javassist.LoaderClassPath;
 import javassist.NotFoundException;
 
-import org.apache.hivemind.ClassResolver;
-import org.apache.hivemind.ModuleDescriptorProvider;
-import org.apache.hivemind.Registry;
-import org.apache.hivemind.impl.DefaultClassResolver;
-import org.apache.hivemind.impl.RegistryBuilder;
-import org.apache.hivemind.impl.XmlModuleDescriptorProvider;
+import org.apache.commons.logging.Log;
 import org.apache.hivemind.util.PropertyUtils;
 import org.apache.tapestry.internal.InternalComponentResources;
+import org.apache.tapestry.internal.InternalModule;
 import org.apache.tapestry.internal.services.UpdateListenerHub;
 import org.apache.tapestry.internal.transform.pages.BasicComponent;
 import org.apache.tapestry.internal.transform.pages.BasicSubComponent;
+import org.apache.tapestry.ioc.Registry;
+import org.apache.tapestry.ioc.RegistryBuilder;
+import org.apache.tapestry.ioc.services.TapestryIOCModule;
 import org.apache.tapestry.runtime.ComponentLifecycle;
 import org.apache.tapestry.test.BaseTestCase;
+import org.apache.tapestry.transform.TransformModule;
 import org.testng.Assert;
 import org.testng.annotations.Configuration;
 import org.testng.annotations.Test;
 
+import static java.lang.Thread.currentThread;
+import static org.apache.hivemind.util.PropertyUtils.read;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNull;
+
 /**
  * Tests for {@link org.apache.tapestry.internal.transform.ComponentInstantiatorSourceImpl}. Many
  * of these tests are more of the form of integration tests that instantiate the HiveMind Registry.
@@ -61,19 +60,24 @@
 {
     private static final String SYNTH_COMPONENT_CLASSNAME = "org.apache.tapestry.internal.transform.pages.SynthComponent";
 
-    private final ClassLoader _defaultClassLoader = currentThread().getContextClassLoader();
-
     private File _extraClasspath;
 
     private ComponentInstantiatorSource _source;
 
     private Registry _registry;
 
+    private ClassLoader _originalContextClassLoader = Thread.currentThread()
+            .getContextClassLoader();
+
     @Test
     public void controlled_packages() throws Exception
     {
+        ComponentClassTransformer transformer = newMock(ComponentClassTransformer.class);
+        Log log = newLog();
 
-        ComponentInstantiatorSourceImpl e = new ComponentInstantiatorSourceImpl(_defaultClassLoader);
+        replay();
+
+        ComponentInstantiatorSourceImpl e = new ComponentInstantiatorSourceImpl(transformer, log);
 
         assertEquals(e.inControlledPackage("foo.bar.Baz"), false);
 
@@ -94,6 +98,8 @@
         // Parents of controlled packages are not controlled
 
         assertEquals(e.inControlledPackage("foo.Gloop"), false);
+
+        verify();
     }
 
     /** More of an integration test. */
@@ -155,7 +161,7 @@
 
         assertEquals(named.getName(), "Original");
 
-        // Sometimes this code runs so far that the updated file has the same timestamp as
+        // Sometimes this code runs so fast that the updated file has the same timestamp as
         // the original; this little blip seems to help.
 
         Thread.sleep(250);
@@ -165,7 +171,7 @@
         // Detect the change and clear out the internal caches
 
         UpdateListenerHub hub = (UpdateListenerHub) _registry.getService(
-                "org.apache.tapestry.internal.services.UpdateListenerHub",
+                "tapestry.internal.services.UpdateListenerHub",
                 UpdateListenerHub.class);
 
         hub.fireUpdateEvent();
@@ -241,19 +247,21 @@
         URL url = _extraClasspath.toURL();
 
         ClassLoader extraLoader = new URLClassLoader(new URL[]
-        { url }, _defaultClassLoader);
-
-        ClassResolver resolver = new DefaultClassResolver(extraLoader);
+        { url }, _originalContextClassLoader);
 
-        ModuleDescriptorProvider provider = new XmlModuleDescriptorProvider(resolver);
+        Thread.currentThread().setContextClassLoader(extraLoader);
 
         RegistryBuilder builder = new RegistryBuilder();
-        builder.addModuleDescriptorProvider(provider);
 
-        _registry = builder.constructRegistry(Locale.getDefault());
+        builder.add(
+                TapestryIOCModule.class,
+                TransformModule.class,
+                InternalModule.class,
+                InternalTransformModule.class);
 
-        _source = (ComponentInstantiatorSource) _registry
-                .getService(ComponentInstantiatorSource.class);
+        _registry = builder.build();
+
+        _source = _registry.getService(ComponentInstantiatorSource.class);
 
         _source.addPackage("org.apache.tapestry.internal.transform.pages");
     }
@@ -261,10 +269,12 @@
     @Configuration(afterTestClass = true)
     public void shutdownRegistry()
     {
-        _registry.shutdown();
+        // _registry.shutdown();
 
         _registry = null;
         _source = null;
+
+        Thread.currentThread().setContextClassLoader(_originalContextClassLoader);
     }
 
     protected final InternalComponentResources newInternalComponentResources()