You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tamaya.apache.org by an...@apache.org on 2016/01/21 07:10:04 UTC

incubator-tamaya git commit: TAMAYA-135: Added support for multiple backends in one change request.

Repository: incubator-tamaya
Updated Branches:
  refs/heads/master faf95ad75 -> 293d242c8


TAMAYA-135: Added support for multiple backends in one change request.


Project: http://git-wip-us.apache.org/repos/asf/incubator-tamaya/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-tamaya/commit/293d242c
Tree: http://git-wip-us.apache.org/repos/asf/incubator-tamaya/tree/293d242c
Diff: http://git-wip-us.apache.org/repos/asf/incubator-tamaya/diff/293d242c

Branch: refs/heads/master
Commit: 293d242c8688ee534a6f144eeeab5e23ce8f4551
Parents: faf95ad
Author: anatole <an...@apache.org>
Authored: Thu Jan 21 07:09:51 2016 +0100
Committer: anatole <an...@apache.org>
Committed: Thu Jan 21 07:09:51 2016 +0100

----------------------------------------------------------------------
 .../mutableconfig/ConfigChangeManager.java      | 174 ++++++++++++++++++-
 .../mutableconfig/ConfigChangeRequest.java      |  23 ++-
 .../internal/DefaultConfigChangeManagerSpi.java |  52 ++++++
 .../MutablePropertiesConfigSupport.java         |  52 ------
 .../PropertiesFileConfigChangeRequest.java      |  11 +-
 .../XmlPropertiesFileConfigChangeRequest.java   |  17 +-
 .../spi/AbstractConfigChangeRequest.java        |  43 +++--
 ...aya.mutableconfig.spi.ConfigChangeManagerSpi |   2 +-
 .../PropertiesFileConfigChangeRequestTest.java  |  37 ++++
 .../asciidoc/extensions/mod_mutable_config.adoc |   8 +-
 10 files changed, 319 insertions(+), 100 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/293d242c/modules/mutable-config/src/main/java/org/apache/tamaya/mutableconfig/ConfigChangeManager.java
----------------------------------------------------------------------
diff --git a/modules/mutable-config/src/main/java/org/apache/tamaya/mutableconfig/ConfigChangeManager.java b/modules/mutable-config/src/main/java/org/apache/tamaya/mutableconfig/ConfigChangeManager.java
index 18502aa..5188e10 100644
--- a/modules/mutable-config/src/main/java/org/apache/tamaya/mutableconfig/ConfigChangeManager.java
+++ b/modules/mutable-config/src/main/java/org/apache/tamaya/mutableconfig/ConfigChangeManager.java
@@ -23,7 +23,15 @@ import org.apache.tamaya.mutableconfig.spi.ConfigChangeManagerSpi;
 import org.apache.tamaya.spi.ServiceContextManager;
 
 import java.net.URI;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
 import java.util.Objects;
+import java.util.UUID;
+
 
 /**
  * Accessor for creating {@link ConfigChangeRequest} instances to change and commit configuration.
@@ -36,20 +44,172 @@ public final class ConfigChangeManager {
     /**
      * Creates a new change request for the given configurationSource
      *
-     * @param configurationSource the configuration source
+     * @param configurationTargets the configuration targets to use to write the changes/config. By passing multiple
+     *                             URIs you can write back changes into multiple configuration backends, e.g.
+     *                             one for redistributing changes using multicast mechanism, a local property file
+     *                             for failover as well as the shared etcd server.
      * @return a new ChangeRequest
      * @throws org.apache.tamaya.ConfigException if the given configurationSource cannot be edited.
      */
-    public static ConfigChangeRequest createChangeRequest(URI configurationSource){
-        Objects.requireNonNull(configurationSource);
+    public static ConfigChangeRequest createChangeRequest(URI... configurationTargets){
+        if(Objects.requireNonNull(configurationTargets).length==0){
+            throw new IllegalArgumentException("At least one target URI is required.");
+        }
+        List<ConfigChangeRequest> targets = new ArrayList<>();
         for(ConfigChangeManagerSpi spi:ServiceContextManager.getServiceContext()
                 .getServices(ConfigChangeManagerSpi.class)){
-            ConfigChangeRequest req = spi.createChangeRequest(configurationSource);
-            if(req!=null){
-                return req;
+            for(URI target:configurationTargets) {
+                ConfigChangeRequest req = spi.createChangeRequest(target);
+                if (req != null) {
+                    targets.add(req);
+                }
+            }
+        }
+        if(targets.isEmpty()) {
+            throw new ConfigException("Not an editable configuration target for: " +
+                    Arrays.toString(configurationTargets));
+        }
+        if(targets.size()==1){
+            return targets.get(0);
+        }
+        return new CompoundConfigChangeRequest(targets);
+    }
+
+
+    /**
+     * Compound request that contains internally multiple change requests. Changes are committed to all members.
+     */
+    private static final class CompoundConfigChangeRequest implements ConfigChangeRequest{
+
+        private final List<ConfigChangeRequest> targets;
+        private final List<URI> backendURIs = new ArrayList<>();
+        private String requestId = UUID.randomUUID().toString();
+
+        CompoundConfigChangeRequest(List<ConfigChangeRequest> targets){
+            this.targets = targets;
+            for(ConfigChangeRequest req:targets){
+                req.setRequestId(requestId);
+                backendURIs.addAll(req.getBackendURIs());
+            }
+        }
+
+        @Override
+        public String getRequestID() {
+            return requestId;
+        }
+
+        @Override
+        public List<URI> getBackendURIs() {
+            return Collections.unmodifiableList(backendURIs);
+        }
+
+        @Override
+        public boolean isWritable(String keyExpression) {
+            for(ConfigChangeRequest req:targets){
+                if(req.isWritable(keyExpression)){
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        @Override
+        public boolean isRemovable(String keyExpression) {
+            for(ConfigChangeRequest req:targets){
+                if(req.isRemovable(keyExpression)){
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        @Override
+        public boolean exists(String keyExpression) {
+            for(ConfigChangeRequest req:targets){
+                if(req.exists(keyExpression)){
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        @Override
+        public ConfigChangeRequest put(String key, String value) {
+            for(ConfigChangeRequest req:targets){
+                if(req.isWritable(key)){
+                    req.put(key, value);
+                }
+            }
+            return this;
+        }
+
+        @Override
+        public ConfigChangeRequest putAll(Map<String, String> properties) {
+            for(ConfigChangeRequest req:targets){
+                for(Map.Entry<String,String> en:properties.entrySet()) {
+                    if (req.isWritable(en.getKey())) {
+                        req.put(en.getKey(), en.getValue());
+                    }
+                }
+            }
+            return this;
+        }
+
+        @Override
+        public ConfigChangeRequest remove(String... keys) {
+            for(ConfigChangeRequest req:targets){
+                for(String key:keys){
+                    if (req.isRemovable(key)) {
+                        req.remove(key);
+                    }
+                }
+            }
+            return this;
+        }
+
+        @Override
+        public ConfigChangeRequest remove(Collection<String> keys) {
+            for(ConfigChangeRequest req:targets){
+                for(String key:keys){
+                    if (req.isRemovable(key)) {
+                        req.remove(key);
+                    }
+                }
+            }
+            return this;
+        }
+
+        @Override
+        public void commit() {
+            for(ConfigChangeRequest req:targets){
+                req.commit();
+            }
+        }
+
+        @Override
+        public void cancel() {
+            for(ConfigChangeRequest req:targets){
+                req.cancel();
+            }
+        }
+
+        @Override
+        public boolean isClosed() {
+            for(ConfigChangeRequest req:targets){
+                if(req.isClosed()){
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        @Override
+        public void setRequestId(String requestId) {
+            if(isClosed()){
+                throw new IllegalStateException("Cannot set requestId, already closed.");
             }
+            this.requestId = Objects.requireNonNull(requestId);
         }
-        throw new ConfigException("Not an editable configuration source: " + configurationSource);
     }
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/293d242c/modules/mutable-config/src/main/java/org/apache/tamaya/mutableconfig/ConfigChangeRequest.java
----------------------------------------------------------------------
diff --git a/modules/mutable-config/src/main/java/org/apache/tamaya/mutableconfig/ConfigChangeRequest.java b/modules/mutable-config/src/main/java/org/apache/tamaya/mutableconfig/ConfigChangeRequest.java
index d1cec76..a192f50 100644
--- a/modules/mutable-config/src/main/java/org/apache/tamaya/mutableconfig/ConfigChangeRequest.java
+++ b/modules/mutable-config/src/main/java/org/apache/tamaya/mutableconfig/ConfigChangeRequest.java
@@ -42,12 +42,12 @@ public interface ConfigChangeRequest {
     String getRequestID();
 
     /**
-     * Identifies the configuration backend that was used to create this request instance and which is
-     * also responsible for writing back the changes on this instance.
+     * Identifies the configuration backends that participate in this request instance and which are
+     * also responsible for writing back the changes applied.
      *
      * @return the backend URI, never null.
      */
-    URI getBackendURI();
+    Collection<URI> getBackendURIs();
 
     /**
      * Checks if a configuration key is writable (or it can be added).
@@ -105,15 +105,6 @@ public interface ConfigChangeRequest {
     ConfigChangeRequest putAll(Map<String, String> properties);
 
     /**
-     * Removes a configuration entry.
-     *
-     * @param keys the property's keys, not null.
-     * @return the property's keys, or the request is read-only.
-     * @throws org.apache.tamaya.ConfigException if the given cannot be removed.
-     */
-    ConfigChangeRequest remove(String... keys);
-
-    /**
      * Removes all given configuration entries. This method should check that all given properties are
      * basically removable, as defined by #isRemovable. If any of the passed keys is not removable during this initial
      * check, the operation should not perform any configuration changes and throw a {@link org.apache.tamaya.ConfigException}. If errors
@@ -139,7 +130,7 @@ public interface ConfigChangeRequest {
      * @return the config change request
      * @throws org.apache.tamaya.ConfigException if any of the given keys could not be removed, or the request is read-only.
      */
-    ConfigChangeRequest removeAll(String... keys);
+    ConfigChangeRequest remove(String... keys);
 
     /**
      * Commits the request. After a commit the change is not editable anymore. All changes applied will be written to
@@ -164,4 +155,10 @@ public interface ConfigChangeRequest {
      */
     boolean isClosed();
 
+    /**
+     * Method to set the request id to be used.
+     * @param requestId the id to  be used, not null
+     * @throws IllegalStateException if the request is already closed.
+     */
+    void setRequestId(String requestId);
 }

http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/293d242c/modules/mutable-config/src/main/java/org/apache/tamaya/mutableconfig/internal/DefaultConfigChangeManagerSpi.java
----------------------------------------------------------------------
diff --git a/modules/mutable-config/src/main/java/org/apache/tamaya/mutableconfig/internal/DefaultConfigChangeManagerSpi.java b/modules/mutable-config/src/main/java/org/apache/tamaya/mutableconfig/internal/DefaultConfigChangeManagerSpi.java
new file mode 100644
index 0000000..d1d8371
--- /dev/null
+++ b/modules/mutable-config/src/main/java/org/apache/tamaya/mutableconfig/internal/DefaultConfigChangeManagerSpi.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.tamaya.mutableconfig.internal;
+
+import org.apache.tamaya.mutableconfig.ConfigChangeRequest;
+import org.apache.tamaya.mutableconfig.spi.ConfigChangeManagerSpi;
+
+import java.io.File;
+import java.net.URI;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Mutable Config Request factory that tries to convert given URIs to file references, if successful, it returns
+ * ConfigChangeRequests fir .properties and .xml files.
+ */
+public class DefaultConfigChangeManagerSpi implements ConfigChangeManagerSpi{
+
+    private static final Logger LOG = Logger.getLogger(XmlPropertiesFileConfigChangeRequest.class.getName());
+
+    @Override
+    public ConfigChangeRequest createChangeRequest(URI uri) {
+        try{
+            File f = new File(uri);
+            if(f.getName().endsWith(".properties")){
+                return new PropertiesFileConfigChangeRequest(f);
+            }else if(f.getName().endsWith(".xml")){
+                return new XmlPropertiesFileConfigChangeRequest(f);
+            }
+        } catch(Exception e){
+            LOG.log(Level.FINEST, "URI not convertible to file, ignoring " + uri, e);
+        }
+        return null;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/293d242c/modules/mutable-config/src/main/java/org/apache/tamaya/mutableconfig/internal/MutablePropertiesConfigSupport.java
----------------------------------------------------------------------
diff --git a/modules/mutable-config/src/main/java/org/apache/tamaya/mutableconfig/internal/MutablePropertiesConfigSupport.java b/modules/mutable-config/src/main/java/org/apache/tamaya/mutableconfig/internal/MutablePropertiesConfigSupport.java
deleted file mode 100644
index 56effe6..0000000
--- a/modules/mutable-config/src/main/java/org/apache/tamaya/mutableconfig/internal/MutablePropertiesConfigSupport.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you 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.tamaya.mutableconfig.internal;
-
-import org.apache.tamaya.mutableconfig.ConfigChangeRequest;
-import org.apache.tamaya.mutableconfig.spi.ConfigChangeManagerSpi;
-
-import java.io.File;
-import java.net.URI;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-/**
- * Mutable Config Request factory that tries to convert given URIs to file references, if successful, it returns
- * ConfigChangeRequests fir .properties and .xml files.
- */
-public class MutablePropertiesConfigSupport implements ConfigChangeManagerSpi{
-
-    private static final Logger LOG = Logger.getLogger(XmlPropertiesFileConfigChangeRequest.class.getName());
-
-    @Override
-    public ConfigChangeRequest createChangeRequest(URI uri) {
-        try{
-            File f = new File(uri);
-            if(f.getName().endsWith(".properties")){
-                return new PropertiesFileConfigChangeRequest(f);
-            }else if(f.getName().endsWith(".xml")){
-                return new XmlPropertiesFileConfigChangeRequest(f);
-            }
-        } catch(Exception e){
-            LOG.log(Level.FINEST, "URI not convertible to file, ignoring " + uri, e);
-        }
-        return null;
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/293d242c/modules/mutable-config/src/main/java/org/apache/tamaya/mutableconfig/internal/PropertiesFileConfigChangeRequest.java
----------------------------------------------------------------------
diff --git a/modules/mutable-config/src/main/java/org/apache/tamaya/mutableconfig/internal/PropertiesFileConfigChangeRequest.java b/modules/mutable-config/src/main/java/org/apache/tamaya/mutableconfig/internal/PropertiesFileConfigChangeRequest.java
index 13cfc30..d06a438 100644
--- a/modules/mutable-config/src/main/java/org/apache/tamaya/mutableconfig/internal/PropertiesFileConfigChangeRequest.java
+++ b/modules/mutable-config/src/main/java/org/apache/tamaya/mutableconfig/internal/PropertiesFileConfigChangeRequest.java
@@ -22,6 +22,7 @@ import org.apache.tamaya.ConfigException;
 import org.apache.tamaya.mutableconfig.spi.AbstractConfigChangeRequest;
 
 import java.io.*;
+import java.net.URI;
 import java.util.Map;
 import java.util.Properties;
 import java.util.logging.Level;
@@ -47,10 +48,12 @@ class PropertiesFileConfigChangeRequest extends AbstractConfigChangeRequest{
         super(file.toURI());
         this.file = file;
         if(file.exists()) {
-            try (InputStream is = getBackendURI().toURL().openStream()) {
-                properties.load(is);
-            } catch (Exception e) {
-                LOG.log(Level.SEVERE, "Failed to load properties from " + file, e);
+            for(URI uri:getBackendURIs()) {
+                try (InputStream is = uri.toURL().openStream()) {
+                    properties.load(is);
+                } catch (Exception e) {
+                    LOG.log(Level.SEVERE, "Failed to load properties from " + file, e);
+                }
             }
         }
     }

http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/293d242c/modules/mutable-config/src/main/java/org/apache/tamaya/mutableconfig/internal/XmlPropertiesFileConfigChangeRequest.java
----------------------------------------------------------------------
diff --git a/modules/mutable-config/src/main/java/org/apache/tamaya/mutableconfig/internal/XmlPropertiesFileConfigChangeRequest.java b/modules/mutable-config/src/main/java/org/apache/tamaya/mutableconfig/internal/XmlPropertiesFileConfigChangeRequest.java
index 6e402ca..f98aceb 100644
--- a/modules/mutable-config/src/main/java/org/apache/tamaya/mutableconfig/internal/XmlPropertiesFileConfigChangeRequest.java
+++ b/modules/mutable-config/src/main/java/org/apache/tamaya/mutableconfig/internal/XmlPropertiesFileConfigChangeRequest.java
@@ -21,7 +21,12 @@ package org.apache.tamaya.mutableconfig.internal;
 import org.apache.tamaya.ConfigException;
 import org.apache.tamaya.mutableconfig.spi.AbstractConfigChangeRequest;
 
-import java.io.*;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
 import java.util.Map;
 import java.util.Properties;
 import java.util.logging.Level;
@@ -47,10 +52,12 @@ class XmlPropertiesFileConfigChangeRequest extends AbstractConfigChangeRequest{
         super(file.toURI());
         this.file = file;
         if(file.exists()) {
-            try (InputStream is = getBackendURI().toURL().openStream()) {
-                properties.loadFromXML(is);
-            } catch (Exception e) {
-                LOG.log(Level.SEVERE, "Failed to load properties from " + file, e);
+            for(URI uri:getBackendURIs()) {
+                try (InputStream is = uri.toURL().openStream()) {
+                    properties.loadFromXML(is);
+                } catch (Exception e) {
+                    LOG.log(Level.SEVERE, "Failed to load properties from " + file, e);
+                }
             }
         }
     }

http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/293d242c/modules/mutable-config/src/main/java/org/apache/tamaya/mutableconfig/spi/AbstractConfigChangeRequest.java
----------------------------------------------------------------------
diff --git a/modules/mutable-config/src/main/java/org/apache/tamaya/mutableconfig/spi/AbstractConfigChangeRequest.java b/modules/mutable-config/src/main/java/org/apache/tamaya/mutableconfig/spi/AbstractConfigChangeRequest.java
index 5b50050..28966e7 100644
--- a/modules/mutable-config/src/main/java/org/apache/tamaya/mutableconfig/spi/AbstractConfigChangeRequest.java
+++ b/modules/mutable-config/src/main/java/org/apache/tamaya/mutableconfig/spi/AbstractConfigChangeRequest.java
@@ -22,15 +22,24 @@ import org.apache.tamaya.ConfigException;
 import org.apache.tamaya.mutableconfig.ConfigChangeRequest;
 
 import java.net.URI;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.UUID;
 
 /**
  * Base class for implementing a ConfigChangeRequest.
  */
 public abstract class AbstractConfigChangeRequest implements ConfigChangeRequest {
 
-    private final URI uri;
-    private final String requestID = UUID.randomUUID().toString();
+    private final List<URI> uris = new ArrayList<>();
+    private String requestID = UUID.randomUUID().toString();
     /**
      * The Properties.
      */
@@ -44,10 +53,15 @@ public abstract class AbstractConfigChangeRequest implements ConfigChangeRequest
     /**
      * Instantiates a new Abstract config change request.
      *
-     * @param uri the uri
+     * @param uris the uris
      */
-    protected AbstractConfigChangeRequest(URI uri){
-        this.uri = Objects.requireNonNull(uri);
+    protected AbstractConfigChangeRequest(URI... uris){
+        for(URI uri:uris){
+            this.uris.add(Objects.requireNonNull(uri));
+        }
+        if(this.uris.isEmpty()){
+            throw new IllegalArgumentException("At least one URI should be provided.");
+        }
     }
 
     /**
@@ -60,8 +74,8 @@ public abstract class AbstractConfigChangeRequest implements ConfigChangeRequest
     }
 
     @Override
-    public final URI getBackendURI() {
-        return uri;
+    public final Collection<URI> getBackendURIs() {
+        return Collections.unmodifiableCollection(uris);
     }
 
     @Override
@@ -78,6 +92,12 @@ public abstract class AbstractConfigChangeRequest implements ConfigChangeRequest
     public abstract boolean exists(String keyExpression);
 
     @Override
+    public void setRequestId(String requestId) {
+        checkClosed();
+        this.requestID = Objects.requireNonNull(requestId);
+    }
+
+    @Override
     public ConfigChangeRequest put(String key, String value) {
         checkClosed();
         this.properties.put(key, value);
@@ -106,13 +126,6 @@ public abstract class AbstractConfigChangeRequest implements ConfigChangeRequest
     }
 
     @Override
-    public ConfigChangeRequest removeAll(String... keys) {
-        checkClosed();
-        Collections.addAll(this.removed, keys);
-        return this;
-    }
-
-    @Override
     public final void commit() {
         checkClosed();
         commitInternal();

http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/293d242c/modules/mutable-config/src/main/resources/META-INF/services/org.apache.tamaya.mutableconfig.spi.ConfigChangeManagerSpi
----------------------------------------------------------------------
diff --git a/modules/mutable-config/src/main/resources/META-INF/services/org.apache.tamaya.mutableconfig.spi.ConfigChangeManagerSpi b/modules/mutable-config/src/main/resources/META-INF/services/org.apache.tamaya.mutableconfig.spi.ConfigChangeManagerSpi
index 6a749ac..6c4ec36 100644
--- a/modules/mutable-config/src/main/resources/META-INF/services/org.apache.tamaya.mutableconfig.spi.ConfigChangeManagerSpi
+++ b/modules/mutable-config/src/main/resources/META-INF/services/org.apache.tamaya.mutableconfig.spi.ConfigChangeManagerSpi
@@ -16,4 +16,4 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-org.apache.tamaya.mutableconfig.internal.MutablePropertiesConfigSupport
\ No newline at end of file
+org.apache.tamaya.mutableconfig.internal.DefaultConfigChangeManagerSpi
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/293d242c/modules/mutable-config/src/test/java/org/apache/tamaya/mutableconfig/internal/PropertiesFileConfigChangeRequestTest.java
----------------------------------------------------------------------
diff --git a/modules/mutable-config/src/test/java/org/apache/tamaya/mutableconfig/internal/PropertiesFileConfigChangeRequestTest.java b/modules/mutable-config/src/test/java/org/apache/tamaya/mutableconfig/internal/PropertiesFileConfigChangeRequestTest.java
index 6829ba1..2f59554 100644
--- a/modules/mutable-config/src/test/java/org/apache/tamaya/mutableconfig/internal/PropertiesFileConfigChangeRequestTest.java
+++ b/modules/mutable-config/src/test/java/org/apache/tamaya/mutableconfig/internal/PropertiesFileConfigChangeRequestTest.java
@@ -121,4 +121,41 @@ public class PropertiesFileConfigChangeRequestTest {
         assertEquals("value2", props.getProperty("key2"));
         assertEquals("value4", props.getProperty("key4"));
     }
+
+    @Test
+    public void testReadWrite_Compound() throws IOException {
+        File f1 = File.createTempFile("testReadWrite_Compound",".xml");
+        f1.delete();
+        File f2 = File.createTempFile("testReadWrite_Compound",".properties");
+        f2.delete();
+        ConfigChangeRequest req = ConfigChangeManager.createChangeRequest(f1.toURI(),f2.toURI());
+        req.put("key1", "value1");
+        Map<String,String> cm = new HashMap<>();
+        cm.put("key2", "value2");
+        cm.put("key3", "value3");
+        req.putAll(cm);
+        req.commit();;
+        assertTrue(req.isClosed());
+        assertTrue(f1.exists());
+        assertTrue(f2.exists());
+        ConfigChangeRequest req2 = ConfigChangeManager.createChangeRequest(f1.toURI(),f2.toURI());
+        assertTrue(req != req2);
+        req2.remove("foo");
+        req2.remove("key3");
+        req2.put("key1", "value1.2");
+        req2.put("key4", "value4");
+        req2.commit();
+        Properties props = new Properties();
+        props.load(f2.toURL().openStream());
+        assertEquals(3, props.size());
+        assertEquals("value1.2", props.getProperty("key1"));
+        assertEquals("value2", props.getProperty("key2"));
+        assertEquals("value4", props.getProperty("key4"));
+        props = new Properties();
+        props.loadFromXML(f1.toURL().openStream());
+        assertEquals(3, props.size());
+        assertEquals("value1.2", props.getProperty("key1"));
+        assertEquals("value2", props.getProperty("key2"));
+        assertEquals("value4", props.getProperty("key4"));
+    }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/293d242c/src/site/asciidoc/extensions/mod_mutable_config.adoc
----------------------------------------------------------------------
diff --git a/src/site/asciidoc/extensions/mod_mutable_config.adoc b/src/site/asciidoc/extensions/mod_mutable_config.adoc
index 7b2f3d1..b9a0668 100644
--- a/src/site/asciidoc/extensions/mod_mutable_config.adoc
+++ b/src/site/asciidoc/extensions/mod_mutable_config.adoc
@@ -72,15 +72,17 @@ To benefit from configuration mutability support you only must add the correspon
 === Core Architecture
 
 The core of the module is the +ConfigChangeManager+ singleton, which creates a +ConfigChangeReuqest+ class for
-a +configurationTarget+ passed. The supported target +URIs+ hereby are determined (and must be documented) by
-the registered backend spis. If not sure you can call +getSupportedURIInfo()+ to see, which kind of URI's
+one or multiple +configurationTargets+ passed. The supported target +URIs+ hereby are determined (and must be
+documented) by the registered backend spis. If not sure you can call +getSupportedURIInfo()+ to see, which kind of URI's
 supported.
 As an example writing configuration entries to an +etcd+ server can be done as follows:
 
 [source,java]
 .Writing configuration to etcd
 --------------------------------------------
-ConfigChangeRequest request = ConfigChangeManager.createChangeRequest(new URI("etc:http://127.0.0.1:4441"));
+ConfigChangeRequest request = ConfigChangeManager.createChangeRequest(
+    new URI("etc:http://127.0.0.1:4441"),
+    new URI("file:/home/etcd/backup-config.properties"));
 ChangeSummary summary = request
                 .set("newKey", "newValue").set("anotherKey", "updatedValue")
                 .remove("valueNotValid")