Merge pull request #63 from Jahia/configuration-factory-synchronization

Configuration factory synchronization
diff --git a/assembly/src/main/resources/node.cfg b/assembly/src/main/resources/node.cfg
index 38c841e..5a09661 100644
--- a/assembly/src/main/resources/node.cfg
+++ b/assembly/src/main/resources/node.cfg
@@ -44,4 +44,4 @@
 # Excluded config properties from the sync
 # Some config properties can be considered as local to a node, and should not be sync on the cluster.
 #
-config.excluded.properties = service.factoryPid, felix.fileinstall.filename, felix.fileinstall.dir, felix.fileinstall.tmpdir, org.ops4j.pax.url.mvn.defaultRepositories
+config.excluded.properties = felix.fileinstall.filename, felix.fileinstall.dir, felix.fileinstall.tmpdir, org.ops4j.pax.url.mvn.defaultRepositories
diff --git a/config/src/main/java/org/apache/karaf/cellar/config/ConfigurationEventHandler.java b/config/src/main/java/org/apache/karaf/cellar/config/ConfigurationEventHandler.java
index 910cb7a..b0a1dc2 100644
--- a/config/src/main/java/org/apache/karaf/cellar/config/ConfigurationEventHandler.java
+++ b/config/src/main/java/org/apache/karaf/cellar/config/ConfigurationEventHandler.java
@@ -25,7 +25,6 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.io.IOException;
 import java.util.Dictionary;
 import java.util.Map;
 import java.util.Properties;
@@ -76,27 +75,31 @@
         String pid = event.getId();
 
         if (isAllowed(event.getSourceGroup(), Constants.CATEGORY, pid, EventType.INBOUND)) {
-
-            Properties clusterDictionary = clusterConfigurations.get(pid);
+            Dictionary clusterDictionary = clusterConfigurations.get(pid);
             try {
                 // update the local configuration
-                Configuration[] localConfigurations = configurationAdmin.listConfigurations("(service.pid=" + pid + ")");
+                Configuration localConfiguration = findLocalConfiguration(pid, clusterDictionary);
+
                 if (event.getType() != null && event.getType() == ConfigurationEvent.CM_DELETED) {
                     // delete the configuration
-                    if (localConfigurations != null && localConfigurations.length > 0) {
-                        localConfigurations[0].delete();
-                        deleteStorage(pid);
+                    if (localConfiguration != null) {
+                        deleteConfiguration(localConfiguration);
                     }
                 } else {
-                    if (clusterDictionary != null) {
-                        Configuration localConfiguration = configurationAdmin.getConfiguration(pid, null);
+                    if (clusterDictionary != null && shouldReplicateConfig(clusterDictionary)) {
+                        if (localConfiguration == null) {
+                            // Create new configuration
+                            localConfiguration = createLocalConfiguration(pid, clusterDictionary);
+                        }
                         Dictionary localDictionary = localConfiguration.getProperties();
                         if (localDictionary == null)
                             localDictionary = new Properties();
                         localDictionary = filter(localDictionary);
-                        if (!equals(clusterDictionary, localDictionary)) {
-                            localConfiguration.update((Dictionary) clusterDictionary);
-                            persistConfiguration(configurationAdmin, pid, clusterDictionary);
+                        if (!equals(clusterDictionary, localDictionary) && canDistributeConfig(localDictionary)) {
+                            Dictionary convertedDictionary = convertPropertiesFromCluster(clusterDictionary);
+
+                            localConfiguration.update(convertedDictionary);
+                            persistConfiguration(localConfiguration, clusterDictionary);
                         }
                     }
                 }
diff --git a/config/src/main/java/org/apache/karaf/cellar/config/ConfigurationSupport.java b/config/src/main/java/org/apache/karaf/cellar/config/ConfigurationSupport.java
index 58a4058..83246db 100644
--- a/config/src/main/java/org/apache/karaf/cellar/config/ConfigurationSupport.java
+++ b/config/src/main/java/org/apache/karaf/cellar/config/ConfigurationSupport.java
@@ -15,11 +15,11 @@
 
 import org.apache.karaf.cellar.core.CellarSupport;
 import org.apache.karaf.cellar.core.Configurations;
+import org.osgi.framework.InvalidSyntaxException;
 import org.osgi.service.cm.Configuration;
 import org.osgi.service.cm.ConfigurationAdmin;
 
-import java.io.File;
-import java.io.IOException;
+import java.io.*;
 import java.net.URI;
 import java.net.URL;
 import java.util.*;
@@ -29,7 +29,10 @@
  */
 public class ConfigurationSupport extends CellarSupport {
 
-    private static final String FELIX_FILEINSTALL_FILENAME = "felix.fileinstall.filename";
+    public static final String FELIX_FILEINSTALL_FILENAME = "felix.fileinstall.filename";
+    public static final String KARAF_CELLAR_FILENAME = "karaf.cellar.filename";
+    public static final String KARAF_CELLAR_CONTENT = "karaf.cellar.content";
+    public static final String KARAF_CELLAR_REMOVED = "karaf.cellar.removed";
 
     protected File storage;
 
@@ -39,7 +42,7 @@
      * @param dictionary the source dictionary.
      * @return the corresponding properties.
      */
-    public Properties dictionaryToProperties(Dictionary dictionary) {
+    public static Properties dictionaryToProperties(Dictionary dictionary) {
         Properties properties = new Properties();
         if (dictionary != null) {
             Enumeration keys = dictionary.keys();
@@ -76,19 +79,32 @@
         Enumeration sourceKeys = source.keys();
         while (sourceKeys.hasMoreElements()) {
             Object key = sourceKeys.nextElement();
-            Object sourceValue = source.get(key);
-            Object targetValue = target.get(key);
-            if (sourceValue != null && targetValue == null)
-                return false;
-            if (sourceValue == null && targetValue != null)
-                return false;
-            if (!sourceValue.equals(targetValue))
-                return false;
+            if (!key.equals(org.osgi.framework.Constants.SERVICE_PID)) {
+                Object sourceValue = source.get(key);
+                Object targetValue = target.get(key);
+                if (sourceValue != null && targetValue == null)
+                    return false;
+                if (sourceValue == null && targetValue != null)
+                    return false;
+                if (!sourceValue.equals(targetValue))
+                    return false;
+            }
         }
 
         return true;
     }
 
+    public boolean canDistributeConfig(Dictionary dictionary) {
+        if (dictionary.get(ConfigurationAdmin.SERVICE_FACTORYPID) != null) {
+            return dictionary.get(KARAF_CELLAR_FILENAME) != null;
+        }
+        return true;
+    }
+
+    public boolean shouldReplicateConfig(Dictionary clusterDictionary) {
+        return clusterDictionary.get(KARAF_CELLAR_REMOVED) == null;
+    }
+
     /**
      * Filter a dictionary, and populate a target dictionary.
      *
@@ -101,7 +117,70 @@
             Enumeration sourceKeys = dictionary.keys();
             while (sourceKeys.hasMoreElements()) {
                 String key = (String) sourceKeys.nextElement();
-                if (!isExcludedProperty(key)) {
+                if (key.equals(FELIX_FILEINSTALL_FILENAME)) {
+                    String value = dictionary.get(key).toString();
+                    value = value.substring(value.lastIndexOf("/") + 1);
+                    result.put(KARAF_CELLAR_FILENAME, value);
+                    try {
+                        result.put(KARAF_CELLAR_CONTENT, readFile(new File(storage, value)));
+                    } catch (IOException e) {
+                        // Cannot read file
+                    }
+                } else if (!isExcludedProperty(key)) {
+                    Object value = dictionary.get(key);
+                    result.put(key, value);
+                }
+            }
+        }
+        return result;
+    }
+
+    public Properties getDeletedConfigurationMarker(Dictionary dictionary) {
+        Properties result = new Properties();
+        result.put(org.osgi.framework.Constants.SERVICE_PID, dictionary.get(org.osgi.framework.Constants.SERVICE_PID));
+        result.put(KARAF_CELLAR_FILENAME, dictionary.get(KARAF_CELLAR_FILENAME));
+        result.put(KARAF_CELLAR_REMOVED, true);
+        return result;
+    }
+
+    public Configuration findLocalConfiguration(String pid, Dictionary dictionary) throws IOException, InvalidSyntaxException {
+        String filter;
+        Object filename = dictionary != null ? dictionary.get(KARAF_CELLAR_FILENAME) : null;
+        if (filename != null) {
+            String uri = new File(storage, filename.toString()).toURI().toString();
+            filter = "(|(" + FELIX_FILEINSTALL_FILENAME + "=" + uri + ")(" + KARAF_CELLAR_FILENAME + "=" + dictionary.get(KARAF_CELLAR_FILENAME) + ")(" + org.osgi.framework.Constants.SERVICE_PID + "=" + pid + "))";
+        } else {
+            filter = "(" + org.osgi.framework.Constants.SERVICE_PID + "=" + pid + ")";
+        }
+
+        Configuration[] localConfigurations = configurationAdmin.listConfigurations(filter);
+
+        return (localConfigurations != null && localConfigurations.length > 0) ? localConfigurations[0] : null;
+    }
+
+    public Configuration createLocalConfiguration(String pid, Dictionary clusterDictionary) throws IOException {
+        Configuration localConfiguration;
+        Object factoryPid = clusterDictionary.get(ConfigurationAdmin.SERVICE_FACTORYPID);
+        if (factoryPid != null) {
+            localConfiguration = configurationAdmin.createFactoryConfiguration(factoryPid.toString(), null);
+        } else {
+            localConfiguration = configurationAdmin.getConfiguration(pid, null);
+        }
+        return localConfiguration;
+    }
+
+    public Dictionary convertPropertiesFromCluster(Dictionary dictionary) {
+        Dictionary result = new Properties();
+        if (dictionary != null) {
+            Enumeration sourceKeys = dictionary.keys();
+            while (sourceKeys.hasMoreElements()) {
+                String key = (String) sourceKeys.nextElement();
+                if (key.equals(KARAF_CELLAR_FILENAME)) {
+                    String value = dictionary.get(key).toString();
+                    result.put(FELIX_FILEINSTALL_FILENAME, new File(storage, value).toURI().toString());
+                } else if (key.equals(KARAF_CELLAR_CONTENT)) {
+                    // skip
+                } else {
                     Object value = dictionary.get(key);
                     result.put(key, value);
                 }
@@ -138,77 +217,96 @@
 
     /**
      * Persist a configuration to a storage.
-     *
-     * @param admin the configuration admin service.
-     * @param pid the configuration PID to store.
-     * @param props the properties to store, linked to the configuration PID.
+     * @param cfg the configuration to store.
      */
-    protected void persistConfiguration(ConfigurationAdmin admin, String pid, Dictionary props) {
+    protected void persistConfiguration(Configuration cfg, Dictionary clusterDictionary) {
         try {
-            if (pid.matches(".*-.*-.*-.*-.*")) {
-                // it's UUID
+            File storageFile = getStorageFile(cfg.getProperties());
+
+            if (storageFile == null && cfg.getProperties().get(ConfigurationAdmin.SERVICE_FACTORYPID) != null) {
+                storageFile = new File(storage, cfg.getPid() + ".cfg");
+            }
+            if (storageFile == null) {
+                // it's a factory configuration without filename specified, cannot save
                 return;
             }
-            File storageFile = new File(storage, pid + ".cfg");
-            Configuration cfg = admin.getConfiguration(pid, null);
-            if (cfg != null && cfg.getProperties() != null) {
-                Object val = cfg.getProperties().get(FELIX_FILEINSTALL_FILENAME);
-                try {
-                    if (val instanceof URL) {
-                        storageFile = new File(((URL) val).toURI());
+
+            String content = clusterDictionary == null ? null : (String) clusterDictionary.get(KARAF_CELLAR_CONTENT);
+
+            if (content == null) {
+                org.apache.felix.utils.properties.Properties p = new org.apache.felix.utils.properties.Properties(storageFile);
+                List<String> propertiesToRemove = new ArrayList<String>();
+                Set<String> set = p.keySet();
+
+                for (String key : set) {
+                    if (!org.osgi.framework.Constants.SERVICE_PID.equals(key)
+                            && !ConfigurationAdmin.SERVICE_FACTORYPID.equals(key)
+                            && !KARAF_CELLAR_FILENAME.equals(key)
+                            && !FELIX_FILEINSTALL_FILENAME.equals(key)) {
+                        propertiesToRemove.add(key);
                     }
-                    if (val instanceof URI) {
-                        storageFile = new File((URI) val);
+                }
+
+                for (String key : propertiesToRemove) {
+                    p.remove(key);
+                }
+                Dictionary props = cfg.getProperties();
+                for (Enumeration<String> keys = props.keys(); keys.hasMoreElements(); ) {
+                    String key = keys.nextElement();
+                    if (!org.osgi.framework.Constants.SERVICE_PID.equals(key)
+                            && !ConfigurationAdmin.SERVICE_FACTORYPID.equals(key)
+                            && !KARAF_CELLAR_FILENAME.equals(key)
+                            && !FELIX_FILEINSTALL_FILENAME.equals(key)) {
+                        p.put(key, (String) props.get(key));
                     }
-                    if (val instanceof String) {
-                        storageFile = new File(new URL((String) val).toURI());
-                    }
-                } catch (Exception e) {
-                    throw new IOException(e.getMessage(), e);
                 }
+
+                // save the cfg file
+                storage.mkdirs();
+                p.save();
+            } else {
+                writeFile(storageFile, content);
             }
-
-            org.apache.felix.utils.properties.Properties p = new org.apache.felix.utils.properties.Properties(storageFile);
-            List<String> propertiesToRemove = new ArrayList<String>();
-            Set<String> set = p.keySet();
-
-            for (String key : set) {
-                if (!org.osgi.framework.Constants.SERVICE_PID.equals(key)
-                        && !ConfigurationAdmin.SERVICE_FACTORYPID.equals(key)
-                        && !FELIX_FILEINSTALL_FILENAME.equals(key)) {
-                    propertiesToRemove.add(key);
-                }
-            }
-
-            for (String key : propertiesToRemove) {
-                p.remove(key);
-            }
-
-            for (Enumeration<String> keys = props.keys(); keys.hasMoreElements(); ) {
-                String key = keys.nextElement();
-                if (!org.osgi.framework.Constants.SERVICE_PID.equals(key)
-                        && !ConfigurationAdmin.SERVICE_FACTORYPID.equals(key)
-                        && !FELIX_FILEINSTALL_FILENAME.equals(key)) {
-                    p.put(key, (String) props.get(key));
-                }
-            }
-
-            // save the cfg file
-            storage.mkdirs();
-            p.save();
         } catch (Exception e) {
-            // nothing to do
+            LOGGER.error("CELLAR CONFIG: Issue when trying to persist configuration file", e);
         }
     }
 
+    private File getStorageFile(Dictionary properties) throws IOException {
+        File storageFile = null;
+        Object val = properties.get(FELIX_FILEINSTALL_FILENAME);
+        try {
+            if (val instanceof URL) {
+                storageFile = new File(((URL) val).toURI());
+            }
+            if (val instanceof URI) {
+                storageFile = new File((URI) val);
+            }
+            if (val instanceof String) {
+                storageFile = new File(new URL((String) val).toURI());
+            }
+        } catch (Exception e) {
+            throw new IOException(e.getMessage(), e);
+        }
+        return storageFile;
+    }
+
+    public String getKarafFilename(Dictionary dictionary) {
+        return (String) filter(dictionary).get(KARAF_CELLAR_FILENAME);
+    }
+
     /**
-     * Delete the storage of a configuration.
+     * Delete the configuration.
      *
-     * @param pid the configuration PID to delete.
+     * @param localConfiguration the configuration PID to delete.
      */
-    protected void deleteStorage(String pid) {
-        File cfgFile = new File(storage, pid + ".cfg");
-        cfgFile.delete();
+    protected void deleteConfiguration(Configuration localConfiguration) throws IOException {
+        String filename = getKarafFilename(localConfiguration.getProperties());
+        localConfiguration.delete();
+        File cfgFile = new File(storage, filename == null ? (localConfiguration.getPid() + ".cfg") : filename);
+        if (cfgFile.exists()) {
+            cfgFile.delete();
+        }
     }
 
     public File getStorage() {
@@ -219,4 +317,29 @@
         this.storage = storage;
     }
 
+    private String readFile(File file) throws IOException {
+        BufferedReader reader = new BufferedReader(new FileReader(file));
+
+        try {
+            String line = reader.readLine();
+            StringBuilder sb = new StringBuilder();
+
+            while(line != null){
+                sb.append(line).append("\n");
+                line = reader.readLine();
+            }
+            return sb.toString();
+        } finally {
+            reader.close();
+        }
+    }
+    private void writeFile(File file, String content) throws IOException {
+        BufferedWriter writer = new BufferedWriter(new FileWriter(file));
+        try {
+            writer.write(content);
+        } finally {
+            writer.close();
+        }
+    }
+
 }
diff --git a/config/src/main/java/org/apache/karaf/cellar/config/ConfigurationSynchronizer.java b/config/src/main/java/org/apache/karaf/cellar/config/ConfigurationSynchronizer.java
index b171bff..e02040c 100644
--- a/config/src/main/java/org/apache/karaf/cellar/config/ConfigurationSynchronizer.java
+++ b/config/src/main/java/org/apache/karaf/cellar/config/ConfigurationSynchronizer.java
@@ -28,10 +28,7 @@
 import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
-import java.util.Dictionary;
-import java.util.Map;
-import java.util.Properties;
-import java.util.Set;
+import java.util.*;
 
 /**
  * The ConfigurationSynchronizer is called when Cellar starts or when a node joins a cluster group.
@@ -125,20 +122,25 @@
 
                 // get configurations on the cluster to update local configurations
                 for (String pid : clusterConfigurations.keySet()) {
-                    if (isAllowed(group, Constants.CATEGORY, pid, EventType.INBOUND)) {
+                    if (isAllowed(group, Constants.CATEGORY, pid, EventType.INBOUND) && shouldReplicateConfig(clusterConfigurations.get(pid))) {
                         Dictionary clusterDictionary = clusterConfigurations.get(pid);
                         try {
                             // update the local configuration if needed
-                            Configuration localConfiguration = configurationAdmin.getConfiguration(pid, null);
+                            Configuration localConfiguration = findLocalConfiguration(pid, clusterDictionary);
+                            if (localConfiguration == null) {
+                                // Create new configuration
+                                localConfiguration = createLocalConfiguration(pid, clusterDictionary);
+                            }
                             Dictionary localDictionary = localConfiguration.getProperties();
                             if (localDictionary == null)
                                 localDictionary = new Properties();
 
                             localDictionary = filter(localDictionary);
-                            if (!equals(clusterDictionary, localDictionary)) {
+                            if (!equals(clusterDictionary, localDictionary) && canDistributeConfig(localDictionary) && shouldReplicateConfig(clusterDictionary)) {
                                 LOGGER.debug("CELLAR CONFIG: updating configration {} on node", pid);
+                                clusterDictionary = convertPropertiesFromCluster(clusterDictionary);
                                 localConfiguration.update((Dictionary) clusterDictionary);
-                                persistConfiguration(configurationAdmin, pid, clusterDictionary);
+                                persistConfiguration(localConfiguration, clusterDictionary);
                             }
                         } catch (IOException ex) {
                             LOGGER.error("CELLAR CONFIG: failed to read local configuration", ex);
@@ -148,16 +150,26 @@
                 // cleanup the local configurations not present on the cluster if the node is not the first one in the cluster
                 if (clusterManager.listNodesByGroup(group).size() > 1) {
                     try {
+                        Set<String> filenames = new HashSet();
+                        for (Properties configuration : clusterConfigurations.values()) {
+                            if (shouldReplicateConfig(configuration)) {
+                                filenames.add(getKarafFilename(configuration));
+                            }
+                        }
+                        filenames.remove(null);
                         for (Configuration configuration : configurationAdmin.listConfigurations(null)) {
                             String pid = configuration.getPid();
-                            if (!clusterConfigurations.containsKey(pid) && isAllowed(group, Constants.CATEGORY, pid, EventType.INBOUND)) {
-                                configuration.delete();
+                            if ((!clusterConfigurations.containsKey(pid) || !shouldReplicateConfig(clusterConfigurations.get(pid))) && !filenames.contains(getKarafFilename(configuration.getProperties())) && isAllowed(group, Constants.CATEGORY, pid, EventType.INBOUND)) {
+                                LOGGER.debug("CELLAR CONFIG: deleting local configuration {} which is not present in cluster", pid);
+                                deleteConfiguration(configuration);
                             }
                         }
                     } catch (Exception e) {
                         LOGGER.warn("Can't get local configurations", e);
                     }
                 }
+            } catch (Exception ex) {
+                LOGGER.error("CELLAR CONFIG: failed to read cluster configuration", ex);
             } finally {
                 Thread.currentThread().setContextClassLoader(originalClassLoader);
             }
@@ -206,7 +218,7 @@
                                 eventProducer.produce(event);
                             } else {
                                 Dictionary clusterDictionary = clusterConfigurations.get(pid);
-                                if (!equals(clusterDictionary, localDictionary)) {
+                                if (!equals(clusterDictionary, localDictionary) && canDistributeConfig(localDictionary)) {
                                     LOGGER.debug("CELLAR CONFIG: updating configuration pid {} on the cluster", pid);
                                     // update cluster configurations
                                     clusterConfigurations.put(pid, dictionaryToProperties(localDictionary));
@@ -224,14 +236,7 @@
                     // clean configurations on the cluster not present locally
                     for (String pid : clusterConfigurations.keySet()) {
                         if (isAllowed(group, Constants.CATEGORY, pid, EventType.OUTBOUND)) {
-                            boolean found = false;
-                            for (Configuration configuration : configurationAdmin.listConfigurations(null)) {
-                                if (configuration.getPid().equals(pid)) {
-                                    found = true;
-                                    break;
-                                }
-                            }
-                            if (!found) {
+                            if (findLocalConfiguration(pid,clusterConfigurations.get(pid)) == null) {
                                 clusterConfigurations.remove(pid);
                             }
                         }
diff --git a/config/src/main/java/org/apache/karaf/cellar/config/LocalConfigurationListener.java b/config/src/main/java/org/apache/karaf/cellar/config/LocalConfigurationListener.java
index 20ce8c5..2e319d8 100644
--- a/config/src/main/java/org/apache/karaf/cellar/config/LocalConfigurationListener.java
+++ b/config/src/main/java/org/apache/karaf/cellar/config/LocalConfigurationListener.java
@@ -70,8 +70,17 @@
                         if (event.getType() == ConfigurationEvent.CM_DELETED) {
 
                             if (clusterConfigurations.containsKey(pid)) {
-                                // update the configurations in the cluster group
-                                clusterConfigurations.remove(pid);
+                                String filename = (String) clusterConfigurations.get(pid).get(KARAF_CELLAR_FILENAME);
+                                List<String> matchingPids = new ArrayList<String>();
+                                for (Map.Entry<String, Properties> entry : clusterConfigurations.entrySet()) {
+                                    if (filename.equals(entry.getValue().get(KARAF_CELLAR_FILENAME))) {
+                                        matchingPids.add(entry.getKey());
+                                    }
+                                }
+                                for (String matchingPid : matchingPids) {
+                                    // update the configurations in the cluster group
+                                    clusterConfigurations.put(matchingPid, getDeletedConfigurationMarker(clusterConfigurations.get(matchingPid)));
+                                }
                                 // send the cluster event
                                 ClusterConfigurationEvent clusterConfigurationEvent = new ClusterConfigurationEvent(pid);
                                 clusterConfigurationEvent.setType(event.getType());
@@ -80,7 +89,6 @@
                                 clusterConfigurationEvent.setLocal(clusterManager.getNode());
                                 eventProducer.produce(clusterConfigurationEvent);
                             }
-
                         } else {
 
                             Configuration conf = configurationAdmin.getConfiguration(pid, null);
@@ -89,7 +97,7 @@
 
                             Properties distributedDictionary = clusterConfigurations.get(pid);
 
-                            if (!equals(localDictionary, distributedDictionary)) {
+                            if (!equals(localDictionary, distributedDictionary) && canDistributeConfig(localDictionary)) {
                                 // update the configurations in the cluster group
                                 clusterConfigurations.put(pid, dictionaryToProperties(localDictionary));
                                 // send the cluster event
diff --git a/config/src/main/java/org/apache/karaf/cellar/config/internal/osgi/Activator.java b/config/src/main/java/org/apache/karaf/cellar/config/internal/osgi/Activator.java
index e601a92..5dea8c6 100644
--- a/config/src/main/java/org/apache/karaf/cellar/config/internal/osgi/Activator.java
+++ b/config/src/main/java/org/apache/karaf/cellar/config/internal/osgi/Activator.java
@@ -97,6 +97,7 @@
         localConfigurationListener.setGroupManager(groupManager);
         localConfigurationListener.setConfigurationAdmin(configurationAdmin);
         localConfigurationListener.setEventProducer(eventProducer);
+        localConfigurationListener.setStorage(storage);
         localConfigurationListener.init();
         register(ConfigurationListener.class, localConfigurationListener);
 
diff --git a/config/src/main/java/org/apache/karaf/cellar/config/shell/ListCommand.java b/config/src/main/java/org/apache/karaf/cellar/config/shell/ListCommand.java
index 11740cb..567d5da 100644
--- a/config/src/main/java/org/apache/karaf/cellar/config/shell/ListCommand.java
+++ b/config/src/main/java/org/apache/karaf/cellar/config/shell/ListCommand.java
@@ -26,10 +26,7 @@
 import org.apache.karaf.shell.api.action.lifecycle.Service;
 import org.osgi.service.cm.Configuration;
 
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Properties;
+import java.util.*;
 
 @Command(scope = "cluster", name = "config-list", description = "List the configurations in a cluster group")
 @Service
@@ -90,6 +87,9 @@
                         if (onlyCluster)
                             continue;
                     }
+                    if (state.getClusterPids() != null && !state.getClusterPids().isEmpty()) {
+                        located += " (cluster = " + state.getClusterPids() + ")";
+                    }
 
                     String blocked = "";
                     boolean inbound = support.isAllowed(group, Constants.CATEGORY, pid, EventType.INBOUND);
@@ -126,32 +126,51 @@
 
     private Map<String, ConfigurationState> gatherConfigurations() throws Exception {
         Map<String, ConfigurationState> configurations = new HashMap<String, ConfigurationState>();
+        Map<String, List<ConfigurationState>> configurationsByFileName = new HashMap<String, List<ConfigurationState>>();
 
         // retrieve cluster configurations
         Map<String, Properties> clusterConfigurations = clusterManager.getMap(Constants.CONFIGURATION_MAP + Configurations.SEPARATOR + groupName);
         for (String key : clusterConfigurations.keySet()) {
             Properties properties = clusterConfigurations.get(key);
             ConfigurationState state = new ConfigurationState();
+            state.setPid(key);
             state.setProperties(properties);
             state.setCluster(true);
             state.setLocal(false);
             configurations.put(key, state);
+            String filename = properties.getProperty(ConfigurationSupport.KARAF_CELLAR_FILENAME);
+            if (filename != null) {
+                configurationsByFileName.putIfAbsent(filename, new ArrayList<ConfigurationState>());
+                configurationsByFileName.get(filename).add(state);
+            }
         }
 
         // retrieve local configurations
         for (Configuration configuration : configurationAdmin.listConfigurations(null)) {
             String key = configuration.getPid();
-            if (configurations.containsKey(key)) {
-                ConfigurationState state = configurations.get(key);
-                state.setLocal(true);
-            } else {
-                ConfigurationState state = new ConfigurationState();
-                state.setLocal(true);
+
+            String filename = (String) configuration.getProperties().get(ConfigurationSupport.KARAF_CELLAR_FILENAME);
+
+            ConfigurationState state = configurations.get(key);
+            if (state == null) {
+                state = new ConfigurationState();
                 state.setCluster(false);
-                ConfigurationSupport support = new ConfigurationSupport();
-                state.setProperties(support.dictionaryToProperties(configuration.getProperties()));
+                state.setProperties(ConfigurationSupport.dictionaryToProperties(configuration.getProperties()));
                 configurations.put(key, state);
             }
+            state.setLocal(true);
+
+            if (filename != null && configurationsByFileName.containsKey(filename)) {
+                state.setCluster(true);
+                state.setClusterPids(new ArrayList<String>());
+                List<ConfigurationState> states = configurationsByFileName.get(filename);
+                for (ConfigurationState otherState : states) {
+                    if (!otherState.getPid().equals(key)) {
+                        configurations.remove(otherState.getPid());
+                        state.getClusterPids().add(otherState.getPid());
+                    }
+                }
+            }
         }
 
         return configurations;
@@ -162,6 +181,8 @@
         private Properties properties;
         private boolean cluster;
         private boolean local;
+        private String pid;
+        private List<String> clusterPids;
 
         public Properties getProperties() {
             return properties;
@@ -186,6 +207,22 @@
         public void setLocal(boolean local) {
             this.local = local;
         }
+
+        public String getPid() {
+            return pid;
+        }
+
+        public void setPid(String pid) {
+            this.pid = pid;
+        }
+
+        public List<String> getClusterPids() {
+            return clusterPids;
+        }
+
+        public void setClusterPids(List<String> clusterPids) {
+            this.clusterPids = clusterPids;
+        }
     }
 
 }