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/11/21 01:58:30 UTC

svn commit: r477448 [2/3] - in /tapestry/tapestry5: tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/ tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/s...

Added: tapestry/tapestry5/tapestry-ioc/trunk/src/main/java/org/apache/tapestry/internal/ioc/DefaultModuleDefImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-ioc/trunk/src/main/java/org/apache/tapestry/internal/ioc/DefaultModuleDefImpl.java?view=auto&rev=477448
==============================================================================
--- tapestry/tapestry5/tapestry-ioc/trunk/src/main/java/org/apache/tapestry/internal/ioc/DefaultModuleDefImpl.java (added)
+++ tapestry/tapestry5/tapestry-ioc/trunk/src/main/java/org/apache/tapestry/internal/ioc/DefaultModuleDefImpl.java Mon Nov 20 16:58:25 2006
@@ -0,0 +1,364 @@
+// 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;
+
+import static org.apache.tapestry.internal.ioc.ConfigurationType.MAPPED;
+import static org.apache.tapestry.internal.ioc.ConfigurationType.ORDERED;
+import static org.apache.tapestry.internal.ioc.ConfigurationType.UNORDERED;
+import static org.apache.tapestry.internal.ioc.IOCMessages.buildMethodConflict;
+import static org.apache.tapestry.internal.ioc.IOCMessages.buildMethodWrongReturnType;
+import static org.apache.tapestry.internal.ioc.IOCMessages.decoratorMethodWrongReturnType;
+import static org.apache.tapestry.ioc.IOCUtilities.toQualifiedId;
+import static org.apache.tapestry.util.CollectionFactory.newMap;
+import static org.apache.tapestry.util.CollectionFactory.newSet;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.logging.Log;
+import org.apache.tapestry.ioc.Configuration;
+import org.apache.tapestry.ioc.IOCConstants;
+import org.apache.tapestry.ioc.IOCUtilities;
+import org.apache.tapestry.ioc.MappedConfiguration;
+import org.apache.tapestry.ioc.OrderedConfiguration;
+import org.apache.tapestry.ioc.annotations.Contribute;
+import org.apache.tapestry.ioc.annotations.EagerLoad;
+import org.apache.tapestry.ioc.annotations.Id;
+import org.apache.tapestry.ioc.annotations.Lifecycle;
+import org.apache.tapestry.ioc.annotations.Match;
+import org.apache.tapestry.ioc.annotations.Order;
+import org.apache.tapestry.ioc.annotations.Private;
+import org.apache.tapestry.ioc.def.ContributionDef;
+import org.apache.tapestry.ioc.def.DecoratorDef;
+import org.apache.tapestry.ioc.def.ModuleDef;
+import org.apache.tapestry.ioc.def.ServiceDef;
+
+/**
+ * Starting from the Class for a module builder, identifies all the services (service builder
+ * methods), decorators (service decorator methods) and (not yet implemented) contributions (service
+ * contributor methods).
+ * 
+ * 
+ */
+public class DefaultModuleDefImpl implements ModuleDef
+{
+    /** The prefix used to identify service builder methods. */
+    private static final String BUILD_METHOD_NAME_PREFIX = "build";
+
+    /** The prefix used to identify service decorator methods. */
+    private static final String DECORATE_METHOD_NAME_PREFIX = "decorate";
+
+    /** The prefix used to identify service contribution methods. */
+    private static final String CONTRIBUTE_METHOD_NAME_PREFIX = "contribute";
+
+    private final Class _builderClass;
+
+    private final Log _log;
+
+    /** Keyed on fully qualified service id. */
+    private final Map<String, ServiceDef> _serviceDefs = newMap();
+
+    /** Keyed on fully qualified decorator id. */
+    private final Map<String, DecoratorDef> _decoratorDefs = newMap();
+
+    private final Set<ContributionDef> _contributionDefs = newSet();
+
+    private final String _moduleId;
+
+    private final static Map<Class, ConfigurationType> PARAMETER_TYPE_TO_CONFIGURATION_TYPE = newMap();
+
+    static
+    {
+        PARAMETER_TYPE_TO_CONFIGURATION_TYPE.put(Configuration.class, UNORDERED);
+        PARAMETER_TYPE_TO_CONFIGURATION_TYPE.put(OrderedConfiguration.class, ORDERED);
+        PARAMETER_TYPE_TO_CONFIGURATION_TYPE.put(MappedConfiguration.class, MAPPED);
+    }
+
+    /**
+     * @param builderClass
+     *            the class that is responsible for building services, etc.
+     * @param log
+     */
+    public DefaultModuleDefImpl(Class builderClass, Log log)
+    {
+        _builderClass = builderClass;
+        _log = log;
+
+        _moduleId = extractModuleId();
+
+        grind();
+    }
+
+    public Class getBuilderClass()
+    {
+        return _builderClass;
+    }
+
+    public Set<String> getServiceIds()
+    {
+        return _serviceDefs.keySet();
+    }
+
+    public ServiceDef getServiceDef(String serviceId)
+    {
+        return _serviceDefs.get(serviceId);
+    }
+
+    public String getModuleId()
+    {
+        return _moduleId;
+    }
+
+    private String extractModuleId()
+    {
+        Id id = getAnnotation(_builderClass, Id.class);
+
+        if (id != null)
+            return id.value();
+
+        String className = _builderClass.getName();
+
+        // Don't try to do this with classes in the default package. Then again, you should
+        // never put classes in the default package!
+
+        int lastdot = className.lastIndexOf('.');
+
+        return className.substring(0, lastdot);
+    }
+
+    // This appears useless, but it's really about some kind of ambiguity in Class, which seems
+    // to think getAnnotation() returns Object, not <? extends Annotation>. This may be a bug
+    // in Eclipse's Java compiler.
+
+    private <T extends Annotation> T getAnnotation(AnnotatedElement element, Class<T> annotationType)
+    {
+        return element.getAnnotation(annotationType);
+    }
+
+    private void grind()
+    {
+        Method[] methods = _builderClass.getMethods();
+
+        Comparator<Method> c = new Comparator<Method>()
+        {
+            // By name, ascending, then by parameter count, descending.
+
+            public int compare(Method o1, Method o2)
+            {
+                int result = o1.getName().compareTo(o2.getName());
+
+                if (result == 0)
+                    result = o2.getParameterTypes().length - o1.getParameterTypes().length;
+
+                return result;
+            }
+
+        };
+
+        Arrays.sort(methods, c);
+
+        for (Method m : methods)
+        {
+            String name = m.getName();
+
+            if (name.startsWith(BUILD_METHOD_NAME_PREFIX))
+            {
+                addServiceDef(m);
+                continue;
+            }
+
+            if (name.startsWith(DECORATE_METHOD_NAME_PREFIX))
+            {
+                addDecoratorDef(m);
+                continue;
+            }
+
+            if (name.startsWith(CONTRIBUTE_METHOD_NAME_PREFIX))
+            {
+                addContributionDef(m);
+                continue;
+            }
+
+        }
+    }
+
+    private void addContributionDef(Method method)
+    {
+        String serviceId = stripMethodPrefix(method, CONTRIBUTE_METHOD_NAME_PREFIX);
+
+        Class returnType = method.getReturnType();
+        if (!returnType.equals(void.class))
+            _log.warn(IOCMessages.contributionWrongReturnType(method));
+
+        Contribute contribute = method.getAnnotation(Contribute.class);
+        if (contribute != null)
+            serviceId = contribute.value();
+
+        ConfigurationType type = null;
+
+        for (Class parameterType : method.getParameterTypes())
+        {
+            ConfigurationType thisParameter = PARAMETER_TYPE_TO_CONFIGURATION_TYPE
+                    .get(parameterType);
+
+            if (thisParameter != null)
+            {
+                if (type != null)
+                {
+                    _log.warn(IOCMessages.tooManyContributionParameters(method));
+                    return;
+                }
+
+                type = thisParameter;
+            }
+        }
+
+        if (type == null)
+        {
+            _log.warn(IOCMessages.noContributionParameter(method));
+            return;
+        }
+
+        // Any other parameters will be validated and worked out at runtime, when we invoke the
+        // service contribution method.
+
+        String qualifiedId = IOCUtilities.toQualifiedId(_moduleId, serviceId);
+
+        ContributionDef def = new ContributionDefImpl(qualifiedId, method);
+
+        _contributionDefs.add(def);
+    }
+
+    private void addDecoratorDef(Method method)
+    {
+        // TODO: methods just named "decorate"
+
+        String simpleDecoratorId = stripMethodPrefix(method, DECORATE_METHOD_NAME_PREFIX);
+        String id = _moduleId + "." + simpleDecoratorId;
+
+        // TODO: Check for duplicates
+
+        Class returnType = method.getReturnType();
+
+        if (returnType.isPrimitive() || returnType.isArray())
+        {
+            _log.warn(decoratorMethodWrongReturnType(method), null);
+            return;
+        }
+
+        if (!methodContainsObjectParameter(method))
+        {
+            _log.warn(IOCMessages.decoratorMethodNeedsDelegateParameter(method), null);
+            return;
+        }
+
+        // TODO: Check that at least one parameter is type java.lang.Object,
+        // since that's how the delegate is passed in.
+
+        Order orderAnnotation = method.getAnnotation(Order.class);
+        Match match = method.getAnnotation(Match.class);
+
+        String[] constraints = orderAnnotation != null ? orderAnnotation.value() : null;
+
+        // TODO: Validate constraints here?
+
+        String[] patterns = match == null ? new String[]
+        { simpleDecoratorId } : match.value();
+
+        // Qualify any unqualified match patterns with the decorator's module id.
+
+        for (int i = 0; i < patterns.length; i++)
+            patterns[i] = toQualifiedId(_moduleId, patterns[i]);
+
+        DecoratorDef def = new DecoratorDefImpl(id, method, patterns, constraints);
+
+        _decoratorDefs.put(id, def);
+    }
+
+    private boolean methodContainsObjectParameter(Method method)
+    {
+        for (Class parameterType : method.getParameterTypes())
+        {
+            // TODO: But what if the type Object parameter has an injection?
+            // We should skip it and look for a different parameter.
+
+            if (parameterType.equals(Object.class))
+                return true;
+        }
+
+        return false;
+    }
+
+    private String stripMethodPrefix(Method method, String prefix)
+    {
+        return method.getName().substring(prefix.length());
+    }
+
+    /** Invoked for public methods that have the proper prefix. */
+    private void addServiceDef(Method method)
+    {
+        // TODO: Methods named just "build"
+        String serviceId = _moduleId + "." + stripMethodPrefix(method, BUILD_METHOD_NAME_PREFIX);
+
+        ServiceDef existing = _serviceDefs.get(serviceId);
+        if (existing != null)
+        {
+            _log.warn(buildMethodConflict(method, existing.toString()), null);
+            return;
+        }
+
+        // Any number of parameters is fine, we'll adapt. Eventually we have to check
+        // that we can satisfy the parameters requested. Thrown exceptions of the method
+        // will be caught and wrapped, so we don't need to check those. But we do need a proper
+        // return type.
+
+        Class returnType = method.getReturnType();
+
+        if (!returnType.isInterface())
+        {
+            _log.warn(buildMethodWrongReturnType(method), null);
+            return;
+        }
+
+        String lifecycle = extractLifecycle(method);
+        boolean isPrivate = method.isAnnotationPresent(Private.class);
+        boolean eagerLoad = method.isAnnotationPresent(EagerLoad.class);
+
+        _serviceDefs.put(serviceId, new ServiceDefImpl(serviceId, lifecycle, method, isPrivate,
+                eagerLoad));
+    }
+
+    private String extractLifecycle(Method method)
+    {
+        Lifecycle lifecycle = method.getAnnotation(Lifecycle.class);
+
+        return lifecycle != null ? lifecycle.value() : IOCConstants.DEFAULT_LIFECYCLE;
+    }
+
+    public Set<DecoratorDef> getDecoratorDefs()
+    {
+        return newSet(_decoratorDefs.values());
+    }
+
+    public Set<ContributionDef> getContributionDefs()
+    {
+        return _contributionDefs;
+    }
+}

Added: tapestry/tapestry5/tapestry-ioc/trunk/src/main/java/org/apache/tapestry/internal/ioc/EagerLoadServiceProxy.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-ioc/trunk/src/main/java/org/apache/tapestry/internal/ioc/EagerLoadServiceProxy.java?view=auto&rev=477448
==============================================================================
--- tapestry/tapestry5/tapestry-ioc/trunk/src/main/java/org/apache/tapestry/internal/ioc/EagerLoadServiceProxy.java (added)
+++ tapestry/tapestry5/tapestry-ioc/trunk/src/main/java/org/apache/tapestry/internal/ioc/EagerLoadServiceProxy.java Mon Nov 20 16:58:25 2006
@@ -0,0 +1,26 @@
+// 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;
+
+/**
+ * Interface implemented by all service proxies. Service proxies are always
+ * {@link org.apache.tapestry.ioc.services.RegistryShutdownListener}s, they also can be eager-load
+ * 
+ * 
+ */
+public interface EagerLoadServiceProxy
+{
+    void eagerLoadService();
+}

Added: tapestry/tapestry5/tapestry-ioc/trunk/src/main/java/org/apache/tapestry/internal/ioc/IOCMessages.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-ioc/trunk/src/main/java/org/apache/tapestry/internal/ioc/IOCMessages.java?view=auto&rev=477448
==============================================================================
--- tapestry/tapestry5/tapestry-ioc/trunk/src/main/java/org/apache/tapestry/internal/ioc/IOCMessages.java (added)
+++ tapestry/tapestry5/tapestry-ioc/trunk/src/main/java/org/apache/tapestry/internal/ioc/IOCMessages.java Mon Nov 20 16:58:25 2006
@@ -0,0 +1,291 @@
+// 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;
+
+import static org.apache.tapestry.internal.util.InternalUtils.asString;
+import static org.apache.tapestry.ioc.services.ClassFabUtils.getJavaClassName;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.tapestry.internal.MessagesImpl;
+import org.apache.tapestry.ioc.Messages;
+import org.apache.tapestry.ioc.def.ContributionDef;
+import org.apache.tapestry.ioc.def.ServiceDef;
+
+public final class IOCMessages
+{
+    private static final Messages MESSAGES = MessagesImpl.forClass(IOCMessages.class);
+
+    private IOCMessages()
+    {
+    }
+
+    static String buildMethodConflict(Method conflict, String existing)
+    {
+        return MESSAGES.format("build-method-conflict", asString(conflict), existing);
+    }
+
+    static String buildMethodWrongReturnType(Method method)
+    {
+        return MESSAGES.format("build-method-wrong-return-type", asString(method), method
+                .getReturnType().getCanonicalName());
+    }
+
+    static String decoratorMethodWrongReturnType(Method method)
+    {
+        return MESSAGES.format("decorator-method-wrong-return-type", asString(method), method
+                .getReturnType().getCanonicalName());
+    }
+
+    static String noSuchModule(String moduleId)
+    {
+        return MESSAGES.format("no-such-module", moduleId);
+    }
+
+    static String missingService(String serviceId)
+    {
+        return MESSAGES.format("missing-service", serviceId);
+    }
+
+    public static String builderLocked()
+    {
+        return MESSAGES.get("builder-locked");
+    }
+
+    public static String moduleIdConflict(String id)
+    {
+        return MESSAGES.format("module-id-conflict", id);
+    }
+
+    static String serviceWrongInterface(String serviceId, Class actualInterface,
+            Class requestedInterface)
+    {
+        return MESSAGES.format(
+                "service-wrong-interface",
+                serviceId,
+                actualInterface.getName(),
+                requestedInterface.getName());
+    }
+
+    static String instantiateBuilderError(Class builderClass, String moduleId, Throwable cause)
+    {
+        return MESSAGES
+                .format("instantiate-builder-error", builderClass.getName(), moduleId, cause);
+    }
+
+    static String builderMethodError(Method method, String serviceId, Throwable cause)
+    {
+        return MESSAGES.format("builder-method-error", asString(method), serviceId, cause);
+    }
+
+    static String decoratorMethodError(Method method, String serviceId, Throwable cause)
+    {
+        return MESSAGES.format("decorator-method-error", asString(method), serviceId, cause);
+    }
+
+    static String builderMethodReturnedNull(Method method, String serviceId)
+    {
+        return MESSAGES.format("builder-method-returned-null", asString(method), serviceId);
+    }
+
+    static String serviceIsPrivate(String serviceId)
+    {
+        return MESSAGES.format("service-is-private", serviceId);
+    }
+
+    static String noServiceMatchesType(Class serviceInterface)
+    {
+        return MESSAGES.format("no-service-matches-type", serviceInterface.getName());
+    }
+
+    static String manyServiceMatches(Class serviceInterface, List<String> ids)
+    {
+        StringBuilder buffer = new StringBuilder();
+
+        for (int i = 0; i < ids.size(); i++)
+        {
+            if (i > 0)
+                buffer.append(", ");
+
+            buffer.append(ids.get(i));
+        }
+
+        return MESSAGES.format(
+                "many-service-matches",
+                serviceInterface.getName(),
+                ids.size(),
+                buffer.toString());
+    }
+
+    static String unknownLifecycle(String name)
+    {
+        return MESSAGES.format("unknown-lifecycle", name);
+    }
+
+    static String decoratorMethodNeedsDelegateParameter(Method method)
+    {
+        return MESSAGES.format("decorator-method-needs-delegate-parameter", asString(method));
+    }
+
+    static String decoratorReturnedWrongType(Method method, String serviceId, Object returned,
+            Class serviceInterface)
+    {
+        return MESSAGES.format(
+                "decorator-returned-wrong-type",
+                asString(method),
+                serviceId,
+                returned,
+                serviceInterface.getName());
+    }
+
+    static String creatingService(String serviceId)
+    {
+        return MESSAGES.format("creating-service", serviceId);
+    }
+
+    static String invokingMethod(Method method)
+    {
+        return MESSAGES.format("invoking-method", asString(method));
+    }
+
+    static String invokingMethod(ContributionDef def)
+    {
+        // The toString() of a contribution def is the name of the method.
+        return MESSAGES.format("invoking-method", def);
+    }
+
+    static String recursiveServiceBuild(ServiceDef def)
+    {
+        return MESSAGES.format("recursive-service-build", def.getServiceId(), def.toString());
+    }
+
+    static String contributionWrongReturnType(Method method)
+    {
+        return MESSAGES.format(
+                "contribution-wrong-return-type",
+                asString(method),
+                getJavaClassName(method.getReturnType()));
+    }
+
+    static String tooManyContributionParameters(Method method)
+    {
+        return MESSAGES.format("too-many-contribution-parameters", asString(method));
+    }
+
+    static String noContributionParameter(Method method)
+    {
+        return MESSAGES.format("no-contribution-parameter", asString(method));
+    }
+
+    static String contributionMethodError(Method method, Throwable cause)
+    {
+        return MESSAGES.format("contribution-method-error", asString(method), cause);
+    }
+
+    static String contributionWasNull(String serviceId, ContributionDef def)
+    {
+        return MESSAGES.format("contribution-was-null", serviceId, def);
+    }
+
+    static String contributionKeyWasNull(String serviceId, ContributionDef def)
+    {
+        return MESSAGES.format("contribution-key-was-null", serviceId, def);
+    }
+
+    static String contributionWrongValueType(String serviceId, ContributionDef def,
+            Class actualClass, Class expectedClass)
+    {
+        return MESSAGES.format("contribution-wrong-value-type", serviceId, def, actualClass
+                .getName(), expectedClass.getName());
+    }
+
+    static String contributionWrongKeyType(String serviceId, ContributionDef def,
+            Class actualClass, Class expectedClass)
+    {
+        return MESSAGES.format(
+                "contribution-wrong-key-type",
+                serviceId,
+                def,
+                actualClass.getName(),
+                expectedClass.getName());
+    }
+
+    static String tooManyConfigurationParameters(Method method)
+    {
+        return MESSAGES.format("too-many-configuration-parameters", asString(method));
+    }
+
+    static String genericTypeNotSupported(Type type)
+    {
+        return MESSAGES.format("generic-type-not-supported", type);
+    }
+
+    static String contributionDuplicateKey(String serviceId, ContributionDef contributionDef,
+            ContributionDef existingDef)
+    {
+        return MESSAGES.format(
+                "contribution-duplicate-key",
+                serviceId,
+                contributionDef,
+                existingDef);
+    }
+
+    static String errorBuildingService(String serviceId, ServiceDef serviceDef, Throwable cause)
+    {
+        return MESSAGES.format("error-building-service", serviceId, serviceDef, cause);
+    }
+
+    static String noPublicConstructors(String moduleId, Class moduleBuilderClass)
+    {
+        return MESSAGES.format("no-public-constructors", moduleId, moduleBuilderClass.getName());
+    }
+
+    static String tooManyPublicConstructors(String moduleId, Class moduleBuilderClass,
+            Constructor constructor)
+    {
+        return MESSAGES.format("too-many-public-constructors", moduleId, moduleBuilderClass
+                .getName(), constructor);
+    }
+
+    static String recursiveModuleConstructor(String moduleId, Class builderClass,
+            Constructor constructor)
+    {
+        return MESSAGES.format(
+                "recursive-module-constructor",
+                moduleId,
+                builderClass.getName(),
+                constructor);
+    }
+
+    static String registryShutdown(String serviceId)
+    {
+        return MESSAGES.format("registry-shutdown", serviceId);
+    }
+
+    static String constructedConfiguration(Collection result)
+    {
+        return MESSAGES.format("constructed-configuration", result);
+    }
+
+    static String constructedConfiguration(Map result)
+    {
+        return MESSAGES.format("constructed-configuration", result);
+    }
+}

Added: tapestry/tapestry5/tapestry-ioc/trunk/src/main/java/org/apache/tapestry/internal/ioc/IOCProxyUtilities.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-ioc/trunk/src/main/java/org/apache/tapestry/internal/ioc/IOCProxyUtilities.java?view=auto&rev=477448
==============================================================================
--- tapestry/tapestry5/tapestry-ioc/trunk/src/main/java/org/apache/tapestry/internal/ioc/IOCProxyUtilities.java (added)
+++ tapestry/tapestry5/tapestry-ioc/trunk/src/main/java/org/apache/tapestry/internal/ioc/IOCProxyUtilities.java Mon Nov 20 16:58:25 2006
@@ -0,0 +1,36 @@
+// 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;
+
+/**
+ * Contains static methods used by proxies generated at runtime.
+ */
+public final class IOCProxyUtilities
+{
+    private IOCProxyUtilities()
+    {
+    }
+
+    /**
+     * Invoked by a proxy when the registry has shutdown.
+     * 
+     * @throws IllegalStateException
+     *             always
+     */
+    public static void registryShutdown(String serviceId)
+    {
+        throw new IllegalStateException(IOCMessages.registryShutdown(serviceId));
+    }
+}

Added: tapestry/tapestry5/tapestry-ioc/trunk/src/main/java/org/apache/tapestry/internal/ioc/IdMatcherImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-ioc/trunk/src/main/java/org/apache/tapestry/internal/ioc/IdMatcherImpl.java?view=auto&rev=477448
==============================================================================
--- tapestry/tapestry5/tapestry-ioc/trunk/src/main/java/org/apache/tapestry/internal/ioc/IdMatcherImpl.java (added)
+++ tapestry/tapestry5/tapestry-ioc/trunk/src/main/java/org/apache/tapestry/internal/ioc/IdMatcherImpl.java Mon Nov 20 16:58:25 2006
@@ -0,0 +1,59 @@
+// 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;
+
+import org.apache.tapestry.ioc.GlobPatternMatcher;
+import org.apache.tapestry.ioc.IdMatcher;
+
+/**
+ * Used to match input values (as fully qualified ids) to a pattern. The pattern is split into two
+ * glob match patterns at the last dot.
+ * 
+ * 
+ */
+public class IdMatcherImpl implements IdMatcher
+{
+    private final GlobPatternMatcher _moduleMatcher;
+
+    private final GlobPatternMatcher _serviceMatcher;
+
+    public IdMatcherImpl(String pattern)
+    {
+        int dotx = pattern.lastIndexOf('.');
+
+        if (dotx < 0)
+            throw new IllegalArgumentException(String.format(
+                    "Pattern '%s' does not contain a '.' seperator character.",
+                    pattern));
+
+        _moduleMatcher = new GlobPatternMatcher(pattern.substring(0, dotx));
+        _serviceMatcher = new GlobPatternMatcher(pattern.substring(dotx + 1));
+    }
+
+    public boolean matches(String id)
+    {
+        int dotx = id.lastIndexOf('.');
+
+        if (dotx < 0)
+            throw new IllegalArgumentException(String.format(
+                    "Input id '%s' does not contain a '.' seperator character.",
+                    id));
+
+        String moduleId = id.substring(0, dotx);
+        String serviceId = id.substring(dotx + 1);
+
+        return _moduleMatcher.matches(moduleId) && _serviceMatcher.matches(serviceId);
+    }
+}

Added: tapestry/tapestry5/tapestry-ioc/trunk/src/main/java/org/apache/tapestry/internal/ioc/InterceptorStackBuilder.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-ioc/trunk/src/main/java/org/apache/tapestry/internal/ioc/InterceptorStackBuilder.java?view=auto&rev=477448
==============================================================================
--- tapestry/tapestry5/tapestry-ioc/trunk/src/main/java/org/apache/tapestry/internal/ioc/InterceptorStackBuilder.java (added)
+++ tapestry/tapestry5/tapestry-ioc/trunk/src/main/java/org/apache/tapestry/internal/ioc/InterceptorStackBuilder.java Mon Nov 20 16:58:25 2006
@@ -0,0 +1,82 @@
+// 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;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.tapestry.ioc.ObjectCreator;
+import org.apache.tapestry.ioc.ServiceDecorator;
+
+/**
+ * Responsible for constructing the interceptor stack, on demand, by invoking an ordered series of
+ * decorators ({@link org.apache.tapestry.ioc.def.DecoratorDef}.
+ * 
+ * 
+ */
+public class InterceptorStackBuilder implements ObjectCreator
+{
+    private final String _serviceId;
+
+    private final ObjectCreator _coreServiceCreator;
+
+    private final Module _module;
+
+    /**
+     * @param module
+     *            the module containing the decorator method
+     * @param serviceId
+     *            identifies the service to be decorated
+     * @param coreServiceCreator
+     *            responsible for creating the core service which is then decorated with a stack of
+     *            interceptors
+     */
+    public InterceptorStackBuilder(Module module, String serviceId, ObjectCreator coreServiceCreator)
+    {
+        _module = module;
+        _serviceId = serviceId;
+        _coreServiceCreator = coreServiceCreator;
+    }
+
+    public Object createObject()
+    {
+        Object current = _coreServiceCreator.createObject();
+
+        List<ServiceDecorator> decorators = _module.findDecoratorsForService(_serviceId);
+
+        // We get the decorators ordered according to their dependencies. However, we want to
+        // process from the last interceptor to the first, so we reverse the list.
+
+        Collections.reverse(decorators);
+
+        for (ServiceDecorator decorator : decorators)
+        {
+            Object interceptor = decorator.createInterceptor(current);
+
+            // Decorator methods may return null; this indicates that the decorator chose not to
+            // decorate.
+
+            if (interceptor != null)
+                current = interceptor;
+        }
+
+        // The stack of interceptors (plus the core service implementation) are "represented" to the
+        // outside world
+        // as the outermost interceptor. That will still be buried inside the service proxy.
+
+        return current;
+    }
+
+}

Added: tapestry/tapestry5/tapestry-ioc/trunk/src/main/java/org/apache/tapestry/internal/ioc/InternalRegistry.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-ioc/trunk/src/main/java/org/apache/tapestry/internal/ioc/InternalRegistry.java?view=auto&rev=477448
==============================================================================
--- tapestry/tapestry5/tapestry-ioc/trunk/src/main/java/org/apache/tapestry/internal/ioc/InternalRegistry.java (added)
+++ tapestry/tapestry5/tapestry-ioc/trunk/src/main/java/org/apache/tapestry/internal/ioc/InternalRegistry.java Mon Nov 20 16:58:25 2006
@@ -0,0 +1,149 @@
+// 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;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.tapestry.ioc.LogSource;
+import org.apache.tapestry.ioc.Registry;
+import org.apache.tapestry.ioc.ServiceDecorator;
+import org.apache.tapestry.ioc.ServiceLifecycle;
+import org.apache.tapestry.ioc.ServiceLocator;
+import org.apache.tapestry.ioc.def.ServiceDef;
+import org.apache.tapestry.ioc.services.ClassFab;
+import org.apache.tapestry.ioc.services.RegistryShutdownHub;
+
+/**
+ * Internal view of the module registry, adding additional methods needed by modules.
+ * 
+ * 
+ */
+public interface InternalRegistry extends Registry, LogSource, RegistryShutdownHub
+{
+    /**
+     * Locates a service given a fully qualified service id and the corresponding service interface
+     * type. This is typically invoked by a module (passing itself as the third parameter), but may
+     * also be invoked by the registry itself, passing null for module.
+     * 
+     * @param <T>
+     * @param serviceId
+     *            the simple or fully qualified service id
+     * @param serviceInterface
+     *            the interface the service implements
+     * @param module
+     *            the module requesting the service (used for visibility checks)
+     * @return the service's proxy
+     * @throws RuntimeException
+     *             if the service does not exist (this is considered programmer error)
+     */
+    <T> T getService(String serviceId, Class<T> serviceInterface, Module module);
+
+    /**
+     * Locates a service given just a service interface. A single service must implement the service
+     * interface (which can be hard to guarantee). This is typically invoked by a module (passing
+     * itself as the third parameter), but may also be invoked by the registry itself, passing null
+     * for module.
+     * 
+     * @param <T>
+     * @param serviceInterface
+     *            the interface the service implements
+     * @param module
+     *            the module requesting the service (used for visibility checks)
+     * @return the service's proxy
+     * @throws RuntimeException
+     *             if the service does not exist (this is considered programmer error), or multiple
+     *             services implement the service interface
+     */
+    <T> T getService(Class<T> serviceInterface, Module module);
+
+    /**
+     * Returns a service lifecycle by name.
+     * 
+     * @param lifecycle
+     *            the name of the lifecycle
+     * @return the lifecycle instance
+     * @throws RuntimeException
+     *             if the lifecycle name does not match a known lifecycle
+     */
+    ServiceLifecycle getServiceLifecycle(String lifecycle);
+
+    /**
+     * Searches for decorators for a particular service. The resulting
+     * {@link org.apache.tapestry.ioc.def.DecoratorDef}s are ordered, then converted into
+     * {@link ServiceDecorator}s.
+     */
+    List<ServiceDecorator> findDecoratorsForService(ServiceDef serviceDef);
+
+    /**
+     * Builds up an unordered collection by invoking service contributor methods that target the
+     * service (from any module, unless the service is private).
+     * 
+     * @param <T>
+     * @param serviceDef
+     *            defines the service for which configuration data is being assembled
+     * @param valueType
+     *            identifies the type of object allowed into the collection
+     * @return the final collection
+     */
+    <T> Collection<T> getUnorderedConfiguration(ServiceDef serviceDef, Class<T> valueType);
+
+    /**
+     * Builds up an ordered collection by invoking service contributor methods that target the
+     * service (from any module, unless the service is private). Once all values have been added
+     * (each with an id, and pre/post constraints), the values are ordered, null values dropped, and
+     * the final sorted list is returned.
+     * 
+     * @param <T>
+     * @param serviceDef
+     *            defines the service for which configuration data is being assembled
+     * @param valueType
+     *            identifies the type of object allowed into the collection
+     * @return the final ordered list
+     */
+    <T> List<T> getOrderedConfiguration(ServiceDef serviceDef, Class<T> valueType);
+
+    /**
+     * Builds up a map of key/value pairs by invoking service contribution methods that tharget the
+     * service (from any module, unless the service is private). Values and keys may not be null.
+     * Invalid values (keys or values that are the wrong type, or duplicate keys) result in warnings
+     * and are ignored.
+     * 
+     * @param <K,
+     *            V>
+     * @param serviceDef
+     *            defines the service for which configuration data is being assembled
+     * @param keyType
+     *            identifies the type of key object allowed into the map
+     * @param valueType
+     *            identifies the type of value object allowed into the map
+     * @return the final ordered list
+     */
+    <K, V> Map<K, V> getMappedConfiguration(ServiceDef serviceDef, Class<K> keyType,
+            Class<V> valueType);
+
+    /**
+     * Convieience for creating a new {@link ClassFab} instance using a
+     * {@link org.apache.tapestry.ioc.services.ClassFactory}.
+     * 
+     * @param serviceInterface
+     *            the interface to be implemented by the provided class
+     */
+    ClassFab newClass(Class serviceInterface);
+
+    /** Provides an object by delegating to the tapestry.ioc.MasterObjectProvider service. */
+    <T> T getObject(String reference, Class<T> objectType, ServiceLocator locator);
+}

Added: tapestry/tapestry5/tapestry-ioc/trunk/src/main/java/org/apache/tapestry/internal/ioc/LifecycleWrappedServiceCreator.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-ioc/trunk/src/main/java/org/apache/tapestry/internal/ioc/LifecycleWrappedServiceCreator.java?view=auto&rev=477448
==============================================================================
--- tapestry/tapestry5/tapestry-ioc/trunk/src/main/java/org/apache/tapestry/internal/ioc/LifecycleWrappedServiceCreator.java (added)
+++ tapestry/tapestry5/tapestry-ioc/trunk/src/main/java/org/apache/tapestry/internal/ioc/LifecycleWrappedServiceCreator.java Mon Nov 20 16:58:25 2006
@@ -0,0 +1,53 @@
+// 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;
+
+import org.apache.tapestry.ioc.ObjectCreator;
+import org.apache.tapestry.ioc.ServiceLifecycle;
+import org.apache.tapestry.ioc.ServiceResources;
+
+/**
+ * Wrapper around a lifecycle, a set of resources for a service, and an underlying service creator
+ * for a service that allows the service lifecycle to alter the way that the service is created
+ * (this is needed for the more advanced, non-singleton types of service lifecycles).
+ * 
+ * 
+ */
+public class LifecycleWrappedServiceCreator implements ObjectCreator
+{
+    private final ServiceLifecycle _lifecycle;
+
+    private final ServiceResources _resources;
+
+    private final ObjectCreator _creator;
+
+    public LifecycleWrappedServiceCreator(ServiceLifecycle lifecycle, ServiceResources resources,
+            ObjectCreator creator)
+    {
+        _lifecycle = lifecycle;
+        _resources = resources;
+        _creator = creator;
+    }
+
+    /**
+     * Passes the resources and the service creator through the
+     * {@link org.apache.tapestry.ioc.ServiceLifecycle}.
+     */
+    public Object createObject()
+    {
+        return _lifecycle.createService(_resources, _creator);
+    }
+
+}

Added: tapestry/tapestry5/tapestry-ioc/trunk/src/main/java/org/apache/tapestry/internal/ioc/LogSourceImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-ioc/trunk/src/main/java/org/apache/tapestry/internal/ioc/LogSourceImpl.java?view=auto&rev=477448
==============================================================================
--- tapestry/tapestry5/tapestry-ioc/trunk/src/main/java/org/apache/tapestry/internal/ioc/LogSourceImpl.java (added)
+++ tapestry/tapestry5/tapestry-ioc/trunk/src/main/java/org/apache/tapestry/internal/ioc/LogSourceImpl.java Mon Nov 20 16:58:25 2006
@@ -0,0 +1,40 @@
+// 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;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.tapestry.ioc.LogSource;
+
+/**
+ * Simple wrapper around {@link org.apache.commons.logging.LogFactory}. The concept here is that
+ * Log implementations could be provided that promote warnings or errors upto thrown exceptions, for
+ * people who like their IOC container extra finicky.
+ * 
+ * 
+ */
+public class LogSourceImpl implements LogSource
+{
+    public Log getLog(Class clazz)
+    {
+        return LogFactory.getLog(clazz);
+    }
+
+    public Log getLog(String name)
+    {
+        return LogFactory.getLog(name);
+    }
+
+}

Added: tapestry/tapestry5/tapestry-ioc/trunk/src/main/java/org/apache/tapestry/internal/ioc/Module.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-ioc/trunk/src/main/java/org/apache/tapestry/internal/ioc/Module.java?view=auto&rev=477448
==============================================================================
--- tapestry/tapestry5/tapestry-ioc/trunk/src/main/java/org/apache/tapestry/internal/ioc/Module.java (added)
+++ tapestry/tapestry5/tapestry-ioc/trunk/src/main/java/org/apache/tapestry/internal/ioc/Module.java Mon Nov 20 16:58:25 2006
@@ -0,0 +1,99 @@
+// 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;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.tapestry.ioc.ModuleBuilderSource;
+import org.apache.tapestry.ioc.ServiceDecorator;
+import org.apache.tapestry.ioc.def.ContributionDef;
+import org.apache.tapestry.ioc.def.DecoratorDef;
+import org.apache.tapestry.ioc.def.ServiceDef;
+
+/**
+ * A module within the Tapestry IoC registry. Each Module is constructed around a corresponding
+ * module builder instance; the methods and annotations of that instance define the services
+ * provided by the module.
+ * 
+ * 
+ */
+public interface Module extends ModuleBuilderSource
+{
+    /**
+     * Locates a service given a service id and the corresponding service interface type.
+     * 
+     * @param <T>
+     * @param serviceId
+     *            the fully qualified service id
+     * @param serviceInterface
+     *            the interface the service implements
+     * @param module
+     *            the module requesting the service, or null for no module; this is used for
+     *            performing visibility checks
+     * @return the service's proxy
+     * @throws RuntimeException
+     *             if the service does not exist (this is considered programmer error)
+     */
+    <T> T getService(String serviceId, Class<T> serviceInterface, Module module);
+
+    /**
+     * Locates the ids of all services that implement the provided service interface. The requesting
+     * module is passed, so that visibility rules may be enforced.
+     * 
+     * @param serviceInterface
+     *            the interface to search for
+     * @param module
+     *            the module requesting the search, or null if no module is requesting the search
+     * @return a collection of strings, each a fully qualified id of a service
+     */
+    Collection<String> findServiceIdsForInterface(Class serviceInterface, Module module);
+
+    /**
+     * Locates all the decorators that should apply the identified service. This includes visibility
+     * rules (private services may only be decorated by decorators in the same module) and other
+     * filtering rules. The resulting list is ordered and from the list of
+     * {@link org.apache.tapestry.ioc.def.DecoratorDef}s, a list of {@link ServiceDecorator}s is
+     * returned.
+     * 
+     * @param serviceId
+     *            identifies the service to be decorated
+     * @return the ordered list of service decorators
+     */
+    List<ServiceDecorator> findDecoratorsForService(String serviceId);
+
+    /**
+     * Iterates over any decorator definitions defined by the module and returns those that apply to
+     * the provided service definition.
+     * 
+     * @param serviceDef
+     *            for which decorators are being assembled
+     * @return set of decorators, possibly empty (but not null)
+     */
+    Set<DecoratorDef> findMatchingDecoratorDefs(ServiceDef serviceDef);
+
+    /** Returns the id that uniquely identifies this module within the registry. */
+    String getModuleId();
+
+    /** Finds any contributions that are targetted at the indicated service. */
+    Set<ContributionDef> getContributorDefsForService(String serviceId);
+
+    /**
+     * Locates services with the {@link org.apache.tapestry.ioc.annotations.EagerLoad} annotation
+     * and forces them to instantiate fully. This is part of the Registry startup.
+     */
+    void eagerLoadServices();
+}

Added: tapestry/tapestry5/tapestry-ioc/trunk/src/main/java/org/apache/tapestry/internal/ioc/ModuleImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-ioc/trunk/src/main/java/org/apache/tapestry/internal/ioc/ModuleImpl.java?view=auto&rev=477448
==============================================================================
--- tapestry/tapestry5/tapestry-ioc/trunk/src/main/java/org/apache/tapestry/internal/ioc/ModuleImpl.java (added)
+++ tapestry/tapestry5/tapestry-ioc/trunk/src/main/java/org/apache/tapestry/internal/ioc/ModuleImpl.java Mon Nov 20 16:58:25 2006
@@ -0,0 +1,467 @@
+// 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;
+
+import static java.lang.String.format;
+import static org.apache.tapestry.util.CollectionFactory.newList;
+import static org.apache.tapestry.util.CollectionFactory.newMap;
+import static org.apache.tapestry.util.CollectionFactory.newSet;
+import static org.apache.tapestry.util.Defense.notBlank;
+import static org.apache.tapestry.util.Defense.notNull;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.logging.Log;
+import org.apache.tapestry.internal.util.InternalUtils;
+import org.apache.tapestry.ioc.ObjectCreator;
+import org.apache.tapestry.ioc.ServiceBuilderResources;
+import org.apache.tapestry.ioc.ServiceDecorator;
+import org.apache.tapestry.ioc.ServiceLifecycle;
+import org.apache.tapestry.ioc.ServiceLocator;
+import org.apache.tapestry.ioc.ServiceResources;
+import org.apache.tapestry.ioc.def.ContributionDef;
+import org.apache.tapestry.ioc.def.DecoratorDef;
+import org.apache.tapestry.ioc.def.ModuleDef;
+import org.apache.tapestry.ioc.def.ServiceDef;
+import org.apache.tapestry.ioc.services.ClassFab;
+import org.apache.tapestry.ioc.services.MethodSignature;
+import org.apache.tapestry.ioc.services.RegistryShutdownListener;
+import org.apache.tapestry.util.BodyBuilder;
+
+/**
+ * 
+ */
+public class ModuleImpl implements Module
+{
+    private final InternalRegistry _registry;
+
+    private final ModuleDef _moduleDef;
+
+    private final Log _log;
+
+    private Object _moduleBuilder;
+
+    private final static String INTERNAL_MODULE_ID = "tapestry.ioc";
+
+    // Set to true when invoking the module constructor. Used to
+    // detect endless loops caused by irresponsible dependencies into
+    // the constructor.
+
+    private boolean _insideConstructor;
+
+    public ModuleImpl(InternalRegistry registry, ModuleDef moduleDef, Log log)
+    {
+        _registry = registry;
+        _moduleDef = moduleDef;
+        _log = log;
+    }
+
+    /** Keyed on fully qualified service id; values are instantiated services (proxies). */
+    private final Map<String, Object> _services = newMap();
+
+    public <T> T getService(String serviceId, Class<T> serviceInterface, Module module)
+    {
+        notBlank(serviceId, "serviceId");
+        notNull(serviceInterface, "serviceInterface");
+        // module may be null.
+
+        ServiceDef def = _moduleDef.getServiceDef(serviceId);
+
+        if (def == null)
+            throw new IllegalArgumentException(IOCMessages.missingService(serviceId));
+
+        if (notVisible(def, module))
+            throw new RuntimeException(IOCMessages.serviceIsPrivate(serviceId));
+
+        Object service = findOrCreate(def);
+
+        try
+        {
+            return serviceInterface.cast(service);
+        }
+        catch (ClassCastException ex)
+        {
+            // This may be overkill: I don't know how this could happen
+            // given that the return type of the method determines
+            // the service interface.
+
+            throw new RuntimeException(IOCMessages.serviceWrongInterface(serviceId, def
+                    .getServiceInterface(), serviceInterface));
+        }
+    }
+
+    public Set<DecoratorDef> findMatchingDecoratorDefs(ServiceDef serviceDef)
+    {
+        Set<DecoratorDef> result = newSet();
+
+        for (DecoratorDef def : _moduleDef.getDecoratorDefs())
+        {
+            if (def.matches(serviceDef))
+                result.add(def);
+        }
+
+        return result;
+    }
+
+    private boolean notVisible(ServiceDef def, Module module)
+    {
+        return def.isPrivate() && this != module;
+    }
+
+    public List<ServiceDecorator> findDecoratorsForService(String serviceId)
+    {
+        ServiceDef sd = _moduleDef.getServiceDef(serviceId);
+
+        return _registry.findDecoratorsForService(sd);
+    }
+
+    public Collection<String> findServiceIdsForInterface(Class serviceInterface, Module module)
+    {
+        notNull(serviceInterface, "serviceInterface");
+
+        Collection<String> result = newList();
+
+        for (String id : _moduleDef.getServiceIds())
+        {
+            ServiceDef def = _moduleDef.getServiceDef(id);
+
+            if (def.getServiceInterface() != serviceInterface)
+                continue;
+
+            if (notVisible(def, module))
+                continue;
+
+            result.add(id);
+        }
+
+        return result;
+    }
+
+    // Why synchronized here? Two reasons. First, with some lifecycle models (or perhaps in some
+    // scenarios using interceptors), we may try to acquire the write lock a second time and the
+    // @Concurrent.Write annotation doesn't currently support that. Second, I'm concerned about
+    // multiple threads building services simultaneously, and getting into a thread deadlock. Of
+    // course, this isn't a solution for that ... we may need a global mutex to handle that specific
+    // case! Alternately, I've thought about creating a "service creation" thread at startup and
+    // queuing service creation requests to that thread, and blocking the local thread.
+
+    private synchronized Object findOrCreate(ServiceDef def)
+    {
+        String key = def.getServiceId();
+
+        Object result = _services.get(key);
+
+        if (result == null)
+        {
+            result = create(def);
+            _services.put(key, result);
+        }
+
+        return result;
+    }
+
+    public void eagerLoadServices()
+    {
+        for (String id : _moduleDef.getServiceIds())
+        {
+            ServiceDef def = _moduleDef.getServiceDef(id);
+
+            if (!def.isEagerLoad())
+                continue;
+
+            // The proxy implements the service interface, and RegistryShutdownListener, and (for
+            // eager load services), EagerLoadServiceProxy
+
+            EagerLoadServiceProxy proxy = (EagerLoadServiceProxy) findOrCreate(def);
+
+            proxy.eagerLoadService();
+        }
+    }
+
+    /**
+     * Creates the service and updates the cache of created services. Method is called from
+     * synchronized block.
+     */
+    private Object create(ServiceDef def)
+    {
+        String serviceId = def.getServiceId();
+
+        Log log = _registry.getLog(serviceId);
+
+        if (log.isDebugEnabled())
+            log.debug(IOCMessages.creatingService(serviceId));
+
+        try
+        {
+            ServiceLifecycle lifecycle = _registry.getServiceLifecycle(def.getServiceLifeycle());
+
+            ServiceBuilderResources resources = new ServiceResourcesImpl(_registry, this, def, log);
+
+            // Build up a stack of operations that will be needed to instantiate the service
+            // (by the proxy, at a later date).
+
+            ObjectCreator creator = def.createServiceCreator(resources);
+
+            creator = new LifecycleWrappedServiceCreator(lifecycle, resources, creator);
+
+            // Don't allow the tapestry.ioc services to be decorated.
+
+            if (!getModuleId().equals(INTERNAL_MODULE_ID))
+                creator = new InterceptorStackBuilder(this, serviceId, creator);
+
+            // Add a wrapper that makes sure that it only gets created once.
+
+            creator = new OneShotServiceCreator(def, creator);
+
+            Object service = createProxy(resources, creator, def.isEagerLoad());
+
+            _services.put(serviceId, service);
+
+            return service;
+        }
+        catch (Exception ex)
+        {
+            throw new RuntimeException(IOCMessages.errorBuildingService(serviceId, def, ex), ex);
+        }
+    }
+
+    public synchronized Object getModuleBuilder()
+    {
+        if (_moduleBuilder == null)
+            _moduleBuilder = instantiateModuleBuilder();
+
+        return _moduleBuilder;
+    }
+
+    private Object instantiateModuleBuilder()
+    {
+        Class builderClass = _moduleDef.getBuilderClass();
+
+        Constructor[] constructors = builderClass.getConstructors();
+
+        if (constructors.length == 0)
+            throw new RuntimeException(IOCMessages.noPublicConstructors(
+                    _moduleDef.getModuleId(),
+                    builderClass));
+
+        if (constructors.length > 1)
+        {
+            // Sort the constructors ascending by number of parameters (descending); this is really
+            // just to allow the test suite to work properly across different JVMs (which will
+            // often order the constructors differently).
+
+            Comparator<Constructor> comparator = new Comparator<Constructor>()
+            {
+                public int compare(Constructor c1, Constructor c2)
+                {
+                    return c2.getParameterTypes().length - c1.getParameterTypes().length;
+                }
+            };
+
+            Arrays.sort(constructors, comparator);
+
+            _log.warn(IOCMessages.tooManyPublicConstructors(
+                    _moduleDef.getModuleId(),
+                    builderClass,
+                    constructors[0]));
+        }
+
+        Constructor constructor = constructors[0];
+
+        if (_insideConstructor)
+            throw new RuntimeException(IOCMessages.recursiveModuleConstructor(_moduleDef
+                    .getModuleId(), builderClass, constructor));
+
+        ServiceLocator locator = new ServiceLocatorImpl(_registry, this);
+        Map<Class, Object> parameterDefaults = newMap();
+
+        parameterDefaults.put(Log.class, _log);
+        parameterDefaults.put(String.class, _moduleDef.getModuleId());
+        parameterDefaults.put(ServiceLocator.class, locator);
+
+        Throwable fail = null;
+
+        try
+        {
+            _insideConstructor = true;
+
+            Object[] parameterValues = InternalUtils.calculateParameters(
+                    locator,
+                    parameterDefaults,
+                    constructor.getParameterTypes(),
+                    constructor.getParameterAnnotations());
+
+            return constructor.newInstance(parameterValues);
+        }
+        catch (InvocationTargetException ex)
+        {
+            fail = ex.getTargetException();
+        }
+        catch (Exception ex)
+        {
+            fail = ex;
+        }
+        finally
+        {
+            _insideConstructor = false;
+        }
+
+        throw new RuntimeException(IOCMessages.instantiateBuilderError(builderClass, _moduleDef
+                .getModuleId(), fail), fail);
+    }
+
+    private Object createProxy(ServiceResources resources, ObjectCreator creator, boolean eagerLoad)
+    {
+        String serviceId = resources.getServiceId();
+        Class serviceInterface = resources.getServiceInterface();
+
+        String toString = format("<Proxy for %s(%s)>", serviceId, serviceInterface.getName());
+
+        RegistryShutdownListener proxy = createProxyInstance(
+                creator,
+                serviceId,
+                serviceInterface,
+                eagerLoad,
+                toString);
+
+        _registry.addRegistryShutdownListener(proxy);
+
+        return proxy;
+    }
+
+    private RegistryShutdownListener createProxyInstance(ObjectCreator creator, String serviceId,
+            Class serviceInterface, boolean eagerLoad, String description)
+    {
+        Class proxyClass = createProxyClass(serviceId, serviceInterface, eagerLoad, description);
+
+        try
+        {
+            return (RegistryShutdownListener) proxyClass.getConstructors()[0].newInstance(creator);
+        }
+        catch (Exception ex)
+        {
+            // This should never happen, so we won't go to a lot of trouble
+            // reporting it.
+            throw new RuntimeException(ex.getMessage(), ex);
+        }
+    }
+
+    private Class createProxyClass(String serviceId, Class serviceInterface, boolean eagerLoad,
+            String proxyDescription)
+    {
+        ClassFab cf = _registry.newClass(serviceInterface);
+
+        cf.addField("_creator", ObjectCreator.class);
+        cf.addField("_delegate", serviceInterface);
+        cf.addField("_shutdown", boolean.class);
+
+        cf.addConstructor(new Class[]
+        { ObjectCreator.class }, null, "_creator = $1;");
+
+        addDelegateGetter(cf, serviceInterface, serviceId);
+
+        addShutdownListenerMethod(cf);
+
+        cf.proxyMethodsToDelegate(serviceInterface, "_delegate()", proxyDescription);
+
+        // For eager load services, add an eagerLoadService() method that calls _delegate(), to
+        // force the creation of the service.
+
+        if (eagerLoad)
+        {
+            cf.addInterface(EagerLoadServiceProxy.class);
+
+            cf.addMethod(Modifier.PUBLIC, new MethodSignature(void.class, "eagerLoadService", null,
+                    null), "_delegate();");
+        }
+
+        return cf.createClass();
+    }
+
+    private void addDelegateGetter(ClassFab cf, Class serviceInterface, String serviceId)
+    {
+        BodyBuilder builder = new BodyBuilder();
+        builder.begin();
+
+        // Check to see if the registry has shutdown. The registryShutdown() method
+        // throws IllegalStateException.
+
+        builder.addln("if (_shutdown) %s.registryShutdown(\"%s\");", IOCProxyUtilities.class
+                .getName(), serviceId);
+
+        // We can release the creator after invoking it, we only create the service once.
+
+        builder.addln("if (_delegate == null)");
+        builder.begin();
+        builder.addln("_delegate = (%s) _creator.createObject();", serviceInterface.getName());
+        builder.addln("_creator = null;");
+        builder.end();
+
+        builder.addln("return _delegate;");
+        builder.end();
+
+        MethodSignature sig = new MethodSignature(serviceInterface, "_delegate", null, null);
+
+        // Here's the rub, this _delegate() method has to be synchronized. But after the first
+        // time through (when we create the service), the time inside the method is infintesmal.
+        // Let's hope that they aren't lying when they say that synchronized is now super cheap!
+
+        cf.addMethod(Modifier.PRIVATE | Modifier.SYNCHRONIZED, sig, builder.toString());
+    }
+
+    /**
+     * All proxies implement {@link RegistryShutdownListener}. When the registry shuts down, the
+     * proxy sets a flag that ultimately converts method invocations to
+     * {@link IllegalStateException}s, and discards its delegate and creator.
+     */
+    private void addShutdownListenerMethod(ClassFab cf)
+    {
+        cf.addInterface(RegistryShutdownListener.class);
+
+        MethodSignature sig = new MethodSignature(void.class, "registryDidShutdown", null, null);
+
+        cf.addMethod(
+                Modifier.PUBLIC | Modifier.SYNCHRONIZED,
+                sig,
+                "{ _shutdown = true; _delegate = null; _creator = null; }");
+    }
+
+    public String getModuleId()
+    {
+        return _moduleDef.getModuleId();
+    }
+
+    public Set<ContributionDef> getContributorDefsForService(String serviceId)
+    {
+        Set<ContributionDef> result = newSet();
+
+        for (ContributionDef def : _moduleDef.getContributionDefs())
+        {
+            if (def.getServiceId().equals(serviceId))
+                result.add(def);
+        }
+
+        return result;
+    }
+
+}

Added: tapestry/tapestry5/tapestry-ioc/trunk/src/main/java/org/apache/tapestry/internal/ioc/OneShotServiceCreator.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-ioc/trunk/src/main/java/org/apache/tapestry/internal/ioc/OneShotServiceCreator.java?view=auto&rev=477448
==============================================================================
--- tapestry/tapestry5/tapestry-ioc/trunk/src/main/java/org/apache/tapestry/internal/ioc/OneShotServiceCreator.java (added)
+++ tapestry/tapestry5/tapestry-ioc/trunk/src/main/java/org/apache/tapestry/internal/ioc/OneShotServiceCreator.java Mon Nov 20 16:58:25 2006
@@ -0,0 +1,59 @@
+// 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;
+
+import org.apache.tapestry.ioc.ObjectCreator;
+import org.apache.tapestry.ioc.def.ServiceDef;
+
+/**
+ * Decorator for {@link org.apache.tapestry.ioc.ObjectCreator} that ensures the service is only
+ * created once. This detects a situation where the service builder for a service directly or
+ * indirectly invokes methods on the service itself. This would show up as a second call up the
+ * ServiceCreator stack injected into the proxy.
+ * <p>
+ * We could use the {@link org.apache.tapestry.internal.annotations.OneShot} annotation, but this
+ * implementation gives us a bit more flexibility to report the error.
+ * 
+ * 
+ */
+public class OneShotServiceCreator implements ObjectCreator
+{
+    private final ServiceDef _serviceDef;
+
+    private final ObjectCreator _delegate;
+
+    private boolean _locked;
+
+    public OneShotServiceCreator(ServiceDef serviceDef, ObjectCreator delegate)
+    {
+        _serviceDef = serviceDef;
+        _delegate = delegate;
+    }
+
+    /**
+     * We could make this method synchronized, but in the context of creating a service for a proxy,
+     * it will already be synchronized (inside the proxy).
+     */
+    public Object createObject()
+    {
+        if (_locked)
+            throw new IllegalStateException(IOCMessages.recursiveServiceBuild(_serviceDef));
+
+        _locked = true;
+
+        return _delegate.createObject();
+    }
+
+}

Added: tapestry/tapestry5/tapestry-ioc/trunk/src/main/java/org/apache/tapestry/internal/ioc/RegistryImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-ioc/trunk/src/main/java/org/apache/tapestry/internal/ioc/RegistryImpl.java?view=auto&rev=477448
==============================================================================
--- tapestry/tapestry5/tapestry-ioc/trunk/src/main/java/org/apache/tapestry/internal/ioc/RegistryImpl.java (added)
+++ tapestry/tapestry5/tapestry-ioc/trunk/src/main/java/org/apache/tapestry/internal/ioc/RegistryImpl.java Mon Nov 20 16:58:25 2006
@@ -0,0 +1,574 @@
+// 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;
+
+import static org.apache.tapestry.util.CollectionFactory.newList;
+import static org.apache.tapestry.util.CollectionFactory.newMap;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.logging.Log;
+import org.apache.tapestry.internal.ioc.services.ClassFactoryImpl;
+import org.apache.tapestry.internal.ioc.services.RegistryShutdownHubImpl;
+import org.apache.tapestry.internal.ioc.services.ThreadCleanupHubImpl;
+import org.apache.tapestry.ioc.Configuration;
+import org.apache.tapestry.ioc.IOCConstants;
+import org.apache.tapestry.ioc.IOCUtilities;
+import org.apache.tapestry.ioc.LogSource;
+import org.apache.tapestry.ioc.MappedConfiguration;
+import org.apache.tapestry.ioc.ObjectProvider;
+import org.apache.tapestry.ioc.OrderedConfiguration;
+import org.apache.tapestry.ioc.Registry;
+import org.apache.tapestry.ioc.ServiceDecorator;
+import org.apache.tapestry.ioc.ServiceLifecycle;
+import org.apache.tapestry.ioc.ServiceLocator;
+import org.apache.tapestry.ioc.ServiceResources;
+import org.apache.tapestry.ioc.def.ContributionDef;
+import org.apache.tapestry.ioc.def.DecoratorDef;
+import org.apache.tapestry.ioc.def.ModuleDef;
+import org.apache.tapestry.ioc.def.ServiceDef;
+import org.apache.tapestry.ioc.internal.util.OneShotLock;
+import org.apache.tapestry.ioc.internal.util.Orderer;
+import org.apache.tapestry.ioc.services.ClassFab;
+import org.apache.tapestry.ioc.services.ClassFactory;
+import org.apache.tapestry.ioc.services.RegistryShutdownHub;
+import org.apache.tapestry.ioc.services.RegistryShutdownListener;
+import org.apache.tapestry.ioc.services.ServiceLifecycleSource;
+import org.apache.tapestry.ioc.services.ThreadCleanupHub;
+import org.apache.tapestry.util.CollectionFactory;
+
+public class RegistryImpl implements Registry, InternalRegistry
+{
+    private static final String REGISTRY_SHUTDOWN_HUB_SERVICE_ID = "tapestry.ioc.RegistryShutdownHub";
+
+    static final String THREAD_CLEANUP_HUB_SERVICE_ID = "tapestry.ioc.ThreadCleanupHub";
+
+    /**
+     * Used to obtain the {@link org.apache.tapestry.ioc.services.ClassFactory} service, which is
+     * crucial when creating runtime classes for proxies and the like.
+     */
+    static final String CLASS_FACTORY_SERVICE_ID = "tapestry.ioc.ClassFactory";
+
+    static final String LOG_SOURCE_SERVICE_ID = "tapestry.ioc.LogSource";
+
+    private final OneShotLock _lock = new OneShotLock();
+
+    private final Map<String, Object> _builtinServices = newMap();
+
+    private final Map<String, Class> _builtinTypes = newMap();
+
+    private final RegistryShutdownHubImpl _registryShutdownHub;
+
+    private final LogSource _logSource;
+
+    /** Keyed on module id. */
+    private final Map<String, Module> _modules = newMap();
+
+    private final Map<String, ServiceLifecycle> _lifecycles = newMap();
+
+    private final ThreadCleanupHubImpl _cleanupHub;
+
+    private final ClassFactory _classFactory;
+
+    public static final class OrderedConfigurationToOrdererAdaptor<T> implements
+            OrderedConfiguration<T>
+    {
+        private final Orderer<T> _orderer;
+
+        public OrderedConfigurationToOrdererAdaptor(Orderer<T> orderer)
+        {
+            _orderer = orderer;
+        }
+
+        public void add(String id, T object, String... constraints)
+        {
+            _orderer.add(id, object, constraints);
+        }
+    }
+
+    public RegistryImpl(Collection<ModuleDef> moduleDefs, ClassLoader contextClassLoader,
+            LogSource logSource)
+    {
+        _logSource = logSource;
+
+        for (ModuleDef def : moduleDefs)
+        {
+            Log log = _logSource.getLog(def.getModuleId());
+
+            Module module = new ModuleImpl(this, def, log);
+
+            _modules.put(def.getModuleId(), module);
+        }
+
+        addBuiltin(LOG_SOURCE_SERVICE_ID, LogSource.class, _logSource);
+
+        Log log = _logSource.getLog(RegistryImpl.CLASS_FACTORY_SERVICE_ID);
+
+        _classFactory = new ClassFactoryImpl(contextClassLoader, log);
+
+        addBuiltin(RegistryImpl.CLASS_FACTORY_SERVICE_ID, ClassFactory.class, _classFactory);
+
+        log = _logSource.getLog(THREAD_CLEANUP_HUB_SERVICE_ID);
+
+        _cleanupHub = new ThreadCleanupHubImpl(log);
+
+        addBuiltin(THREAD_CLEANUP_HUB_SERVICE_ID, ThreadCleanupHub.class, _cleanupHub);
+
+        log = _logSource.getLog(REGISTRY_SHUTDOWN_HUB_SERVICE_ID);
+
+        _registryShutdownHub = new RegistryShutdownHubImpl(log);
+
+        addBuiltin(
+                REGISTRY_SHUTDOWN_HUB_SERVICE_ID,
+                RegistryShutdownHub.class,
+                _registryShutdownHub);
+
+        _lifecycles.put("singleton", new SingletonServiceLifecycle());
+
+        // Ask all modules to eager-load any services marked with @EagerLoad
+
+        for (Module m : _modules.values())
+            m.eagerLoadServices();
+    }
+
+    private <T> void addBuiltin(String serviceId, Class<T> serviceInterface, T service)
+    {
+        _builtinTypes.put(serviceId, serviceInterface);
+        _builtinServices.put(serviceId, service);
+    }
+
+    public synchronized void shutdown()
+    {
+        _lock.lock();
+
+        _registryShutdownHub.fireRegistryDidShutdown();
+    }
+
+    /** Internal access, usualy from another module. */
+    public <T> T getService(String serviceId, Class<T> serviceInterface, Module module)
+    {
+        _lock.check();
+
+        T result = checkForBuiltinService(serviceId, serviceInterface);
+        if (result != null) return result;
+
+        // Checking serviceId and serviceInterface is overkill; they have been
+        // checked and rechecked
+        // all the way to here.
+
+        Module containingModule = locateModuleForService(serviceId);
+
+        return containingModule.getService(serviceId, serviceInterface, module);
+    }
+
+    private <T> T checkForBuiltinService(String serviceId, Class<T> serviceInterface)
+    {
+        Object service = _builtinServices.get(serviceId);
+
+        if (service == null) return null;
+
+        try
+        {
+            return serviceInterface.cast(service);
+        }
+        catch (ClassCastException ex)
+        {
+            throw new RuntimeException(IOCMessages.serviceWrongInterface(serviceId, _builtinTypes
+                    .get(serviceId), serviceInterface));
+        }
+    }
+
+    /**
+     * Access for a service from the outside world (limited to publically visible services).
+     */
+    public <T> T getService(String serviceId, Class<T> serviceInterface)
+    {
+        _lock.check();
+
+        return getService(serviceId, serviceInterface, null);
+    }
+
+    public void cleanupThread()
+    {
+        _lock.check();
+
+        _cleanupHub.cleanup();
+    }
+
+    private Module locateModuleForService(String serviceId)
+    {
+        String moduleId = IOCUtilities.extractModuleId(serviceId);
+
+        Module module = _modules.get(moduleId);
+
+        if (module == null) throw new RuntimeException(IOCMessages.noSuchModule(moduleId));
+
+        return module;
+    }
+
+    public <T> Collection<T> getUnorderedConfiguration(ServiceDef serviceDef, Class<T> objectType)
+    {
+        _lock.check();
+
+        final Collection<T> result = newList();
+
+        Configuration<T> configuration = new Configuration<T>()
+        {
+            public void add(T object)
+            {
+                result.add(object);
+            }
+        };
+
+        Collection<Module> modules = modulesThatContributeToService(serviceDef);
+
+        for (Module m : modules)
+            addToUnorderedConfiguration(configuration, objectType, serviceDef, m);
+
+        return result;
+    }
+
+    public <T> List<T> getOrderedConfiguration(ServiceDef serviceDef, Class<T> objectType)
+    {
+        _lock.check();
+
+        Log log = getLog(serviceDef.getServiceId());
+
+        final Orderer<T> orderer = new Orderer<T>(log);
+
+        OrderedConfiguration<T> configuration = new OrderedConfigurationToOrdererAdaptor<T>(orderer);
+
+        Collection<Module> modules = modulesThatContributeToService(serviceDef);
+
+        for (Module m : modules)
+            addToOrderedConfiguration(configuration, objectType, serviceDef, m);
+
+        return orderer.getOrdered();
+    }
+
+    public <K, V> Map<K, V> getMappedConfiguration(ServiceDef serviceDef, Class<K> keyType,
+            Class<V> objectType)
+    {
+        _lock.check();
+
+        final Map<K, V> result = newMap();
+
+        MappedConfiguration<K, V> configuration = new MappedConfiguration<K, V>()
+        {
+            public void add(K key, V value)
+            {
+                result.put(key, value);
+            }
+        };
+
+        Map<K, ContributionDef> keyToContribution = newMap();
+
+        Collection<Module> modules = modulesThatContributeToService(serviceDef);
+
+        for (Module m : modules)
+            addToMappedConfiguration(
+                    configuration,
+                    keyToContribution,
+                    keyType,
+                    objectType,
+                    serviceDef,
+                    m);
+
+        return result;
+    }
+
+    private Collection<Module> modulesThatContributeToService(ServiceDef serviceDef)
+    {
+        if (serviceDef.isPrivate())
+        {
+            String moduleId = IOCUtilities.extractModuleId(serviceDef.getServiceId());
+            Module module = _modules.get(moduleId);
+
+            return Arrays.asList(module);
+        }
+
+        return _modules.values();
+    }
+
+    private <K, V> void addToMappedConfiguration(MappedConfiguration<K, V> configuration,
+            Map<K, ContributionDef> keyToContribution, Class<K> keyClass, Class<V> valueType,
+            ServiceDef serviceDef, Module module)
+    {
+        String serviceId = serviceDef.getServiceId();
+        Set<ContributionDef> contributions = module.getContributorDefsForService(serviceId);
+
+        if (contributions.isEmpty()) return;
+
+        Log log = getLog(serviceId);
+        boolean debug = log.isDebugEnabled();
+
+        ServiceLocator locator = new ServiceResourcesImpl(this, module, serviceDef, log);
+
+        for (ContributionDef def : contributions)
+        {
+            MappedConfiguration<K, V> validating = new ValidatingMappedConfigurationWrapper<K, V>(
+                    serviceId, def, log, keyClass, valueType, keyToContribution, configuration);
+
+            if (debug) log.debug(IOCMessages.invokingMethod(def));
+
+            def.contribute(module, locator, validating);
+        }
+
+    }
+
+    private <T> void addToUnorderedConfiguration(Configuration<T> configuration,
+            Class<T> valueType, ServiceDef serviceDef, Module module)
+    {
+        String serviceId = serviceDef.getServiceId();
+        Set<ContributionDef> contributions = module.getContributorDefsForService(serviceId);
+
+        if (contributions.isEmpty()) return;
+
+        Log log = getLog(serviceId);
+        boolean debug = log.isDebugEnabled();
+
+        ServiceLocator locator = new ServiceResourcesImpl(this, module, serviceDef, log);
+
+        for (ContributionDef def : contributions)
+        {
+            Configuration<T> validating = new ValidatingConfigurationWrapper<T>(serviceId, log,
+                    valueType, def, configuration);
+
+            if (debug) log.debug(IOCMessages.invokingMethod(def));
+
+            def.contribute(module, locator, validating);
+        }
+    }
+
+    private <T> void addToOrderedConfiguration(OrderedConfiguration<T> configuration,
+            Class<T> valueType, ServiceDef serviceDef, Module module)
+    {
+        String serviceId = serviceDef.getServiceId();
+        Set<ContributionDef> contributions = module.getContributorDefsForService(serviceId);
+
+        if (contributions.isEmpty()) return;
+
+        Log log = getLog(serviceId);
+        boolean debug = log.isDebugEnabled();
+
+        ServiceLocator locator = new ServiceResourcesImpl(this, module, serviceDef, log);
+
+        for (ContributionDef def : contributions)
+        {
+            OrderedConfiguration<T> validating = new ValidatingOrderedConfigurationWrapper<T>(
+                    serviceId, module.getModuleId(), def, log, valueType, configuration);
+
+            if (debug) log.debug(IOCMessages.invokingMethod(def));
+
+            def.contribute(module, locator, validating);
+        }
+    }
+
+    // Seems like something that could be cached.
+    public <T> T getService(Class<T> serviceInterface, Module module)
+    {
+        _lock.check();
+
+        List<String> ids = CollectionFactory.newList();
+
+        for (Module m : _modules.values())
+        {
+            Collection<String> matchingIds = m.findServiceIdsForInterface(serviceInterface, module);
+            ids.addAll(matchingIds);
+        }
+
+        switch (ids.size())
+        {
+            case 0:
+
+                throw new RuntimeException(IOCMessages.noServiceMatchesType(serviceInterface));
+
+            case 1:
+
+                String serviceId = ids.get(0);
+
+                return getService(serviceId, serviceInterface, module);
+
+            default:
+
+                Collections.sort(ids);
+
+                throw new RuntimeException(IOCMessages.manyServiceMatches(serviceInterface, ids));
+        }
+    }
+
+    public <T> T getService(Class<T> serviceInterface)
+    {
+        _lock.check();
+
+        return getService(serviceInterface, null);
+    }
+
+    public ServiceLifecycle getServiceLifecycle(String lifecycle)
+    {
+        _lock.check();
+
+        ServiceLifecycle result = _lifecycles.get(lifecycle);
+
+        if (result == null)
+        {
+            ServiceLifecycleSource source = getService(
+                    "tapestry.ioc.ServiceLifecycleSource",
+                    ServiceLifecycleSource.class);
+            result = source.get(lifecycle);
+        }
+
+        if (result == null) throw new RuntimeException(IOCMessages.unknownLifecycle(lifecycle));
+
+        return result;
+    }
+
+    public List<ServiceDecorator> findDecoratorsForService(ServiceDef serviceDef)
+    {
+        _lock.check();
+
+        Log log = getLog(serviceDef.getServiceId());
+
+        Orderer<DecoratorDef> orderer = new Orderer<DecoratorDef>(log);
+
+        addDecoratorDefsToOrderer(orderer, serviceDef);
+
+        List<DecoratorDef> ordered = orderer.getOrdered();
+
+        return convertDecoratorDefsToServiceDecorators(ordered, serviceDef, log);
+    }
+
+    private List<ServiceDecorator> convertDecoratorDefsToServiceDecorators(
+            List<DecoratorDef> ordered, ServiceDef serviceDef, Log log)
+    {
+        List<ServiceDecorator> result = newList();
+
+        ServiceResources resources = null;
+        String moduleId = null;
+        Module module = null;
+
+        for (DecoratorDef dd : ordered)
+        {
+            String decoratorModuleId = IOCUtilities.extractModuleId(dd.getDecoratorId());
+
+            // Whenever the module id containing the decorator changes,
+            // "refresh" the resources, etc., to point to the (new) module.
+            // This means that the ServiceResources will identify services
+            // within
+            // the decorators module (important in terms of service visibility
+            // and abbreviated service ids).
+
+            if (!decoratorModuleId.equals(moduleId))
+            {
+                moduleId = decoratorModuleId;
+
+                module = _modules.get(moduleId);
+
+                resources = new ServiceResourcesImpl(this, module, serviceDef, log);
+            }
+
+            ServiceDecorator decorator = dd.createDecorator(module, resources);
+
+            result.add(decorator);
+        }
+
+        return result;
+    }
+
+    private void addDecoratorDefsToOrderer(Orderer<DecoratorDef> orderer, ServiceDef serviceDef)
+    {
+        if (serviceDef.isPrivate())
+        {
+            Set<DecoratorDef> privateDecorators = findDecoratorsDefsForPrivateService(serviceDef);
+            addToOrderer(orderer, privateDecorators);
+        }
+        else
+        {
+            for (Module m : _modules.values())
+            {
+                Set<DecoratorDef> moduleDecorators = m.findMatchingDecoratorDefs(serviceDef);
+                addToOrderer(orderer, moduleDecorators);
+            }
+        }
+    }
+
+    private void addToOrderer(Orderer<DecoratorDef> orderer, Set<DecoratorDef> decorators)
+    {
+        for (DecoratorDef df : decorators)
+        {
+            orderer.add(df.getDecoratorId(), df, df.getConstraints());
+        }
+    }
+
+    private Set<DecoratorDef> findDecoratorsDefsForPrivateService(ServiceDef serviceDef)
+    {
+        String moduleId = IOCUtilities.extractModuleId(serviceDef.getServiceId());
+        Module module = _modules.get(moduleId);
+
+        return module.findMatchingDecoratorDefs(serviceDef);
+    }
+
+    public Log getLog(Class clazz)
+    {
+        _lock.check();
+
+        return _logSource.getLog(clazz);
+    }
+
+    public Log getLog(String name)
+    {
+        _lock.check();
+
+        return _logSource.getLog(name);
+    }
+
+    public ClassFab newClass(Class serviceInterface)
+    {
+        _lock.check();
+
+        return _classFactory.newClass(serviceInterface);
+    }
+
+    public <T> T getObject(String reference, Class<T> objectType, ServiceLocator locator)
+    {
+        _lock.check();
+
+        ObjectProvider masterProvider = getService(
+                IOCConstants.MASTER_OBJECT_PROVIDER_SERVICE_ID,
+                ObjectProvider.class);
+
+        return masterProvider.provide(reference, objectType, locator);
+    }
+
+    public <T> T getObject(String reference, Class<T> objectType)
+    {
+        _lock.check();
+
+        // Concerened about this causing potential endless loops.
+        return getObject(reference, objectType, this);
+    }
+
+    public void addRegistryShutdownListener(RegistryShutdownListener listener)
+    {
+        _lock.check();
+
+        _registryShutdownHub.addRegistryShutdownListener(listener);
+    }
+}