TAP5-2192 : adds the ServiceConfigurationListener interface and the ServiceConfigurationListenerHub to receive the listeners
diff --git a/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/RegistryImpl.java b/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/RegistryImpl.java
index 3709fa8..51ca08a 100644
--- a/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/RegistryImpl.java
+++ b/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/RegistryImpl.java
@@ -65,7 +65,7 @@
     static final String PLASTIC_PROXY_FACTORY_SERVICE_ID = "PlasticProxyFactory";
 
     static final String LOGGER_SOURCE_SERVICE_ID = "LoggerSource";
-
+    
     private final OneShotLock lock = new OneShotLock();
 
     private final OneShotLock eagerLoadLock = new OneShotLock();
@@ -109,7 +109,9 @@
     private final Map<Class<? extends Annotation>, Annotation> cachedAnnotationProxies = CollectionFactory.newConcurrentMap();
 
     private final Set<Runnable> startups = CollectionFactory.newSet();
-
+    
+    private DelegatingServiceConfigurationListener serviceConfigurationListener;
+    
     /**
      * Constructs the registry from a set of module definitions and other resources.
      *
@@ -133,7 +135,10 @@
         this.operationTracker = operationTracker;
 
         this.proxyFactory = proxyFactory;
-
+        
+        serviceConfigurationListener = new DelegatingServiceConfigurationListener(
+                loggerForBuiltinService(ServiceConfigurationListener.class.getSimpleName()));
+        
         Logger logger = loggerForBuiltinService(PERTHREAD_MANAGER_SERVICE_ID);
 
         PerthreadManagerImpl ptmImpl = new PerthreadManagerImpl(logger);
@@ -202,10 +207,13 @@
         addBuiltin(PLASTIC_PROXY_FACTORY_SERVICE_ID, PlasticProxyFactory.class, proxyFactory);
 
         validateContributeDefs(moduleDefs);
+        
+        serviceConfigurationListener.setDelegates(getService(ServiceConfigurationListenerHub.class).getListeners());
 
         scoreboardAndTracker.startup();
 
         SerializationSupport.setProvider(this);
+        
     }
 
     private void addStartupsInModule(ModuleDef2 def, final Module module, final Logger logger)
@@ -425,6 +433,31 @@
             {
                 return true;
             }
+            
+            @Override
+            public int hashCode()
+            {
+                final int prime = 31;
+                int result = 1;
+                result = prime * result + ((serviceId == null) ? 0 : serviceId.hashCode());
+                return result;
+            }
+
+            @Override
+            public boolean equals(Object obj)
+            {
+                if (this == obj) { return true; }
+                if (obj == null) { return false; }
+                if (!(obj instanceof ServiceDefImpl)) { return false; }
+                ServiceDef other = (ServiceDef) obj;
+                if (serviceId == null)
+                {
+                    if (other.getServiceId() != null) { return false; }
+                }
+                else if (!serviceId.equals(other.getServiceId())) { return false; }
+                return true;
+            }
+            
         };
 
         for (Class marker : serviceDef.getMarkers())
@@ -508,6 +541,11 @@
 
         for (Module m : moduleToServiceDefs.keySet())
             addToUnorderedConfiguration(result, objectType, serviceDef, m);
+        
+        if (!isServiceConfigurationListenerServiceDef(serviceDef))
+        {
+            serviceConfigurationListener.onUnorderedConfiguration(serviceDef, result);
+        }
 
         return result;
     }
@@ -552,7 +590,19 @@
         for (OrderedConfigurationOverride<T> override : overrides.values())
             override.apply();
 
-        return orderer.getOrdered();
+        final List<T> result = orderer.getOrdered();
+        
+        if (!isServiceConfigurationListenerServiceDef(serviceDef))
+        {
+            serviceConfigurationListener.onOrderedConfiguration(serviceDef, result);
+        }
+        
+        return result;
+    }
+    
+    private boolean isServiceConfigurationListenerServiceDef(ServiceDef serviceDef)
+    {
+        return serviceDef.getServiceId().equalsIgnoreCase(ServiceConfigurationListener.class.getSimpleName());
     }
 
     @Override
@@ -574,6 +624,11 @@
             override.apply();
         }
 
+        if (!isServiceConfigurationListenerServiceDef(serviceDef))
+        {
+            serviceConfigurationListener.onMappedConfiguration(serviceDef, result);
+        }
+        
         return result;
     }
 
@@ -1223,4 +1278,119 @@
         }
     }
     
+    final static private class DelegatingServiceConfigurationListener implements ServiceConfigurationListener {
+        
+        final private Logger logger;
+        
+        private List<ServiceConfigurationListener> delegates;
+        private Map<ServiceDef, Map> mapped = CollectionFactory.newMap();
+        private Map<ServiceDef, Collection> unordered = CollectionFactory.newMap();
+        private Map<ServiceDef, List> ordered = CollectionFactory.newMap();
+        
+        public DelegatingServiceConfigurationListener(Logger logger)
+        {
+            this.logger = logger;
+        }
+
+        public void setDelegates(List<ServiceConfigurationListener> delegates)
+        {
+            
+            this.delegates = delegates;
+            
+            for (ServiceDef serviceDef : mapped.keySet())
+            {
+                for (ServiceConfigurationListener delegate : delegates)
+                {
+                    delegate.onMappedConfiguration(serviceDef, Collections.unmodifiableMap(mapped.get(serviceDef)));
+                }
+            }
+
+            for (ServiceDef serviceDef : unordered.keySet())
+            {
+                for (ServiceConfigurationListener delegate : delegates)
+                {
+                    delegate.onUnorderedConfiguration(serviceDef, Collections.unmodifiableCollection(unordered.get(serviceDef)));
+                }
+            }
+
+            for (ServiceDef serviceDef : ordered.keySet())
+            {
+                for (ServiceConfigurationListener delegate : delegates)
+                {
+                    delegate.onOrderedConfiguration(serviceDef, Collections.unmodifiableList(ordered.get(serviceDef)));
+                }
+            }
+            
+            mapped.clear();
+            mapped = null;
+            unordered.clear();
+            unordered = null;
+            ordered.clear();
+            ordered = null;
+
+        }
+        
+        @Override
+        public void onOrderedConfiguration(ServiceDef serviceDef, List configuration)
+        {
+            log("ordered", serviceDef, configuration);
+            if (delegates == null)
+            {
+                ordered.put(serviceDef, configuration);
+            }
+            else
+            {
+                for (ServiceConfigurationListener delegate : delegates)
+                {
+                    delegate.onOrderedConfiguration(serviceDef, Collections.unmodifiableList(configuration));
+                }
+            }
+        }
+
+        @Override
+        public void onUnorderedConfiguration(ServiceDef serviceDef, Collection configuration)
+        {
+            log("unordered", serviceDef, configuration);
+            if (delegates == null)
+            {
+                unordered.put(serviceDef, configuration);
+            }
+            else
+            {
+                for (ServiceConfigurationListener delegate : delegates)
+                {
+                    delegate.onUnorderedConfiguration(serviceDef, Collections.unmodifiableCollection(configuration));
+                }
+            }
+        }
+
+        @Override
+        public void onMappedConfiguration(ServiceDef serviceDef, Map configuration)
+        {
+            log("mapped", serviceDef, configuration);
+            if (delegates == null)
+            {
+                mapped.put(serviceDef, configuration);
+            }
+            else
+            {
+                for (ServiceConfigurationListener delegate : delegates)
+                {
+                    delegate.onMappedConfiguration(serviceDef, Collections.unmodifiableMap(configuration));
+                }
+            }
+            
+        }
+        
+        private void log(String type, ServiceDef serviceDef, Object configuration)
+        {
+            if (logger.isDebugEnabled())
+            {
+                logger.debug(String.format("Service %s %s configuration: %s", 
+                        serviceDef.getServiceId(), type, configuration.toString()));
+            }
+        }
+        
+    }
+    
 }
diff --git a/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/ServiceDefImpl.java b/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/ServiceDefImpl.java
index 7e35746..aeccb47 100644
--- a/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/ServiceDefImpl.java
+++ b/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/ServiceDefImpl.java
@@ -23,6 +23,7 @@
 import org.apache.tapestry5.ioc.AnnotationProvider;
 import org.apache.tapestry5.ioc.ObjectCreator;
 import org.apache.tapestry5.ioc.ServiceBuilderResources;
+import org.apache.tapestry5.ioc.def.ServiceDef;
 import org.apache.tapestry5.ioc.def.ServiceDef3;
 import org.apache.tapestry5.ioc.internal.services.AnnotationProviderChain;
 import org.apache.tapestry5.ioc.internal.util.InternalUtils;
@@ -155,4 +156,29 @@
             }
         }).map(InternalUtils.METHOD_TO_AP_MAPPER).toList());
     }
+
+    @Override
+    public int hashCode()
+    {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((serviceId == null) ? 0 : serviceId.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj)
+    {
+        if (this == obj) { return true; }
+        if (obj == null) { return false; }
+        if (!(obj instanceof ServiceDefImpl)) { return false; }
+        ServiceDef other = (ServiceDef) obj;
+        if (serviceId == null)
+        {
+            if (other.getServiceId() != null) { return false; }
+        }
+        else if (!serviceId.equals(other.getServiceId())) { return false; }
+        return true;
+    }
+    
 }
diff --git a/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/util/InternalUtils.java b/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/util/InternalUtils.java
index 96c356b..e9e7806 100644
--- a/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/util/InternalUtils.java
+++ b/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/util/InternalUtils.java
@@ -22,6 +22,7 @@
 import org.apache.tapestry5.ioc.annotations.*;
 import org.apache.tapestry5.ioc.def.*;
 import org.apache.tapestry5.ioc.internal.NullAnnotationProvider;
+import org.apache.tapestry5.ioc.internal.ServiceDefImpl;
 import org.apache.tapestry5.ioc.services.Coercion;
 import org.apache.tapestry5.ioc.services.PlasticProxyFactory;
 import org.apache.tapestry5.ioc.util.ExceptionUtils;
@@ -1025,6 +1026,31 @@
             {
                 return sd.toString();
             }
+            
+            @Override
+            public int hashCode()
+            {
+                final int prime = 31;
+                int result = 1;
+                result = prime * result + ((getServiceId() == null) ? 0 : getServiceId().hashCode());
+                return result;
+            }
+
+            @Override
+            public boolean equals(Object obj)
+            {
+                if (this == obj) { return true; }
+                if (obj == null) { return false; }
+                if (!(obj instanceof ServiceDefImpl)) { return false; }
+                ServiceDef other = (ServiceDef) obj;
+                if (getServiceId() == null)
+                {
+                    if (other.getServiceId() != null) { return false; }
+                }
+                else if (!getServiceId().equals(other.getServiceId())) { return false; }
+                return true;
+            }
+
         };
     }
 
diff --git a/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/modules/TapestryIOCModule.java b/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/modules/TapestryIOCModule.java
index 6e49788..a9af358 100644
--- a/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/modules/TapestryIOCModule.java
+++ b/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/modules/TapestryIOCModule.java
@@ -1,4 +1,4 @@
-// Copyright 2006-2013 The Apache Software Foundation
+// Copyright 2006-2014 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.
@@ -76,6 +76,7 @@
         binder.bind(UpdateListenerHub.class, UpdateListenerHubImpl.class).preventReloading();
         binder.bind(PeriodicExecutor.class, PeriodicExecutorImpl.class);
         binder.bind(OperationAdvisor.class, OperationAdvisorImpl.class);
+        binder.bind(ServiceConfigurationListenerHub.class);
     }
 
     /**
diff --git a/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/services/ServiceConfigurationListener.java b/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/services/ServiceConfigurationListener.java
new file mode 100644
index 0000000..3531423
--- /dev/null
+++ b/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/services/ServiceConfigurationListener.java
@@ -0,0 +1,50 @@
+// Copyright 2014 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.tapestry5.ioc.services;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.tapestry5.ioc.def.ServiceDef;
+
+/**
+ * Interface that defines listeners to services getting their distributed configuration.
+ */
+@SuppressWarnings("rawtypes")
+public interface ServiceConfigurationListener
+{
+    
+    /**
+     * Receives a notification of an ordered configuraton being passed to a service.
+     * @param serviceDef a {@link ServiceDef} identifying the service receiving the configuration.
+     * @param configuration a {@link List} containing the configuration itself.
+     */
+    void onOrderedConfiguration(ServiceDef serviceDef, List configuration);
+
+    /**
+     * Receives a notification of an unordered configuraton being passed to a service.
+     * @param serviceDef a {@link ServiceDef} identifying the service receiving the configuration.
+     * @param configuration a {@link Collection} containing the configuration itself.
+     */
+    void onUnorderedConfiguration(ServiceDef serviceDef, Collection configuration);
+
+    /**
+     * Receives a notification of a mapped configuraton being passed to a service.
+     * @param serviceDef a {@link ServiceDef} identifying the service receiving the configuration.
+     * @param configuration a {@link Map} containing the configuration itself.
+     */
+    void onMappedConfiguration(ServiceDef serviceDef, Map configuration);
+
+}
diff --git a/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/services/ServiceConfigurationListenerHub.java b/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/services/ServiceConfigurationListenerHub.java
new file mode 100644
index 0000000..a7b7980
--- /dev/null
+++ b/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/services/ServiceConfigurationListenerHub.java
@@ -0,0 +1,44 @@
+// Copyright 2014 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.tapestry5.ioc.services;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.tapestry5.ioc.annotations.UsesOrderedConfiguration;
+
+/**
+ * Service that collects the {@link ServiceConfigurationListener}s. Don't use this service directly.
+ */
+@UsesOrderedConfiguration(ServiceConfigurationListener.class)
+final public class ServiceConfigurationListenerHub
+{
+    
+    final private List<ServiceConfigurationListener> listeners;
+    
+    public ServiceConfigurationListenerHub(List<ServiceConfigurationListener> listeners)
+    {
+        super();
+        this.listeners = Collections.unmodifiableList(listeners);
+    }
+
+    /**
+     * Returns the list of service configuration listeners. 
+     */
+    public List<ServiceConfigurationListener> getListeners()
+    {
+        return listeners;
+    }
+    
+}
diff --git a/tapestry-ioc/src/test/groovy/ioc/specs/RegistrySpec.groovy b/tapestry-ioc/src/test/groovy/ioc/specs/RegistrySpec.groovy
index ef0bbc8..d429b0b 100644
--- a/tapestry-ioc/src/test/groovy/ioc/specs/RegistrySpec.groovy
+++ b/tapestry-ioc/src/test/groovy/ioc/specs/RegistrySpec.groovy
@@ -1,8 +1,13 @@
 package ioc.specs
 
+import org.apache.tapestry5.ioc.BarneyModule;
+import org.apache.tapestry5.ioc.CatchAllServiceConfigurationListener;
 import org.apache.tapestry5.ioc.Greeter
+import org.apache.tapestry5.ioc.FredModule
 import org.apache.tapestry5.ioc.GreeterModule
 import org.apache.tapestry5.ioc.HelterModule
+import org.apache.tapestry5.ioc.NameListHolder;
+import org.apache.tapestry5.ioc.StringLookup;
 import org.apache.tapestry5.ioc.internal.services.StartupModule2
 
 class RegistrySpec extends AbstractRegistrySpecification {
@@ -53,4 +58,36 @@
     StartupModule2.staticStartupInvoked
     StartupModule2.instanceStartupInvoked
   }
+  
+  def "ServiceConfigurationListener"() {
+      
+    buildRegistry FredModule, BarneyModule
+    
+    // for a given service, its configuration is only notified to the ServiceConfigurationListeners 
+    // when the service itself is realized, so we call one method of each to force their realization.
+    getService(StringLookup).keys()
+    getService("OrderedNames", NameListHolder).getNames()
+    getService("UnorderedNames", NameListHolder).getNames()
+      
+    when:
+      
+    def listener = getService CatchAllServiceConfigurationListener
+    def mappedConfiguration = listener.getMappedConfigurations().get("StringLookup")
+    def orderedConfiguration = listener.getOrderedConfigurations().get("OrderedNames");
+    def unorderedConfiguration = listener.getUnorderedConfigurations().get("UnorderedNames");
+      
+    then:
+    mappedConfiguration.size() == 4
+    mappedConfiguration.get("fred").equals("FRED")
+    mappedConfiguration.get("wilma").equals("WILMA")
+    mappedConfiguration.get("barney").equals("BARNEY")
+    mappedConfiguration.get("betty").equals("BETTY")
+    orderedConfiguration == ['BARNEY', 'FRED']
+    unorderedConfiguration.size() == 3
+    unorderedConfiguration.contains 'UnorderedNames'
+    unorderedConfiguration.contains 'Beta'
+    unorderedConfiguration.contains 'Gamma'
+    
+  }
+      
 }
diff --git a/tapestry-ioc/src/test/java/org/apache/tapestry5/ioc/CatchAllServiceConfigurationListener.java b/tapestry-ioc/src/test/java/org/apache/tapestry5/ioc/CatchAllServiceConfigurationListener.java
new file mode 100644
index 0000000..b6d97df
--- /dev/null
+++ b/tapestry-ioc/src/test/java/org/apache/tapestry5/ioc/CatchAllServiceConfigurationListener.java
@@ -0,0 +1,68 @@
+// Copyright 2014 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.tapestry5.ioc;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.tapestry5.ioc.def.ServiceDef;
+import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
+import org.apache.tapestry5.ioc.services.ServiceConfigurationListener;
+
+/**
+ * Just for testing {@link ServiceConfigurationListener}.
+ */
+@SuppressWarnings("rawtypes")
+public class CatchAllServiceConfigurationListener implements ServiceConfigurationListener
+{
+    
+    final private Map<String, Map> mappedConfigurations = CollectionFactory.newCaseInsensitiveMap();
+    final private Map<String, List> orderedConfigurations = CollectionFactory.newCaseInsensitiveMap();
+    final private Map<String, Collection> unorderedConfigurations = CollectionFactory.newCaseInsensitiveMap();
+
+    @Override
+    public void onOrderedConfiguration(ServiceDef serviceDef, List configuration)
+    {
+        orderedConfigurations.put(serviceDef.getServiceId(), configuration);
+    }
+
+    @Override
+    public void onUnorderedConfiguration(ServiceDef serviceDef, Collection configuration)
+    {
+        unorderedConfigurations.put(serviceDef.getServiceId(), configuration);
+    }
+
+    @Override
+    public void onMappedConfiguration(ServiceDef serviceDef, Map configuration)
+    {
+        mappedConfigurations.put(serviceDef.getServiceId(), configuration);
+    }
+
+    public Map<String, Map> getMappedConfigurations()
+    {
+        return mappedConfigurations;
+    }
+
+    public Map<String, List> getOrderedConfigurations()
+    {
+        return orderedConfigurations;
+    }
+
+    public Map<String, Collection> getUnorderedConfigurations()
+    {
+        return unorderedConfigurations;
+    }
+
+}
diff --git a/tapestry-ioc/src/test/java/org/apache/tapestry5/ioc/FredModule.java b/tapestry-ioc/src/test/java/org/apache/tapestry5/ioc/FredModule.java
index 7ec3cae..28eac4f 100644
--- a/tapestry-ioc/src/test/java/org/apache/tapestry5/ioc/FredModule.java
+++ b/tapestry-ioc/src/test/java/org/apache/tapestry5/ioc/FredModule.java
@@ -14,10 +14,13 @@
 
 package org.apache.tapestry5.ioc;
 
+import org.apache.tapestry5.ioc.annotations.Contribute;
 import org.apache.tapestry5.ioc.annotations.Match;
 import org.apache.tapestry5.ioc.annotations.Order;
 import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
 import org.apache.tapestry5.ioc.internal.util.InternalUtils;
+import org.apache.tapestry5.ioc.services.ServiceConfigurationListener;
+import org.apache.tapestry5.ioc.services.ServiceConfigurationListenerHub;
 
 import java.util.Collection;
 import java.util.Collections;
@@ -129,4 +132,18 @@
         configuration.add("fred", "FRED");
         configuration.add("wilma", "WILMA");
     }
+    
+    
+    @Contribute(ServiceConfigurationListenerHub.class)
+    public static void configureServiceConfigurationListener(OrderedConfiguration<ServiceConfigurationListener> configuration, 
+            CatchAllServiceConfigurationListener listener) 
+    {
+        configuration.add("CatchAll", listener);
+    }
+    
+    public static void bind(ServiceBinder binder)
+    {
+        binder.bind(CatchAllServiceConfigurationListener.class);
+    }
+    
 }