Bug 538717 - Implement ConfigAdmin version 1.6

Change-Id: I61152d0aeec13440535a64758022ed2e13d2fa62
Signed-off-by: Thomas Watson <tjwatson@us.ibm.com>
diff --git a/bundles/org.eclipse.equinox.cm/META-INF/MANIFEST.MF b/bundles/org.eclipse.equinox.cm/META-INF/MANIFEST.MF
index 7419ee7..b9171b0 100644
--- a/bundles/org.eclipse.equinox.cm/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.equinox.cm/META-INF/MANIFEST.MF
@@ -4,10 +4,10 @@
 Bundle-Vendor: %providerName
 Bundle-Localization: plugin
 Bundle-SymbolicName: org.eclipse.equinox.cm
-Bundle-Version: 1.3.100.qualifier
+Bundle-Version: 1.4.0.qualifier
 Bundle-Activator: org.eclipse.equinox.internal.cm.Activator
 Import-Package: org.osgi.framework;version="1.7.0",
- org.osgi.service.cm;version="[1.5,1.6)",
+ org.osgi.service.cm;version="[1.6,1.7)",
  org.osgi.service.log;version="1.3.0",
  org.osgi.service.event;version="1.0"; resolution:=optional,
  org.osgi.util.tracker;version="1.3.1"
@@ -15,5 +15,9 @@
 Provide-Capability: 
  osgi.service;
   objectClass:List<String>="org.osgi.service.cm.ConfigurationAdmin";
-  uses:="org.osgi.service.cm"
+  uses:="org.osgi.service.cm",
+ osgi.implementation;
+  osgi.implementation="osgi.cm";
+  uses:="org.osgi.service.cm";
+  version:Version="1.6"
 Automatic-Module-Name: org.eclipse.equinox.cm
diff --git a/bundles/org.eclipse.equinox.cm/pom.xml b/bundles/org.eclipse.equinox.cm/pom.xml
index 0959f5b..648411b 100644
--- a/bundles/org.eclipse.equinox.cm/pom.xml
+++ b/bundles/org.eclipse.equinox.cm/pom.xml
@@ -19,6 +19,6 @@
   </parent>
   <groupId>org.eclipse.equinox</groupId>
   <artifactId>org.eclipse.equinox.cm</artifactId>
-  <version>1.3.100-SNAPSHOT</version>
+  <version>1.4.0-SNAPSHOT</version>
   <packaging>eclipse-plugin</packaging>
 </project>
diff --git a/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ConfigurationAdminFactory.java b/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ConfigurationAdminFactory.java
index 7d8139a..72cec99 100644
--- a/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ConfigurationAdminFactory.java
+++ b/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ConfigurationAdminFactory.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2005, 2018 Cognos Incorporated, IBM Corporation and others.
+ * Copyright (c) 2005, 2019 Cognos Incorporated, IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -28,6 +28,7 @@
 public class ConfigurationAdminFactory implements ServiceFactory<ConfigurationAdmin>, BundleListener {
 
 	static private final Permission allConfigurationPermission = new ConfigurationPermission("*", ConfigurationPermission.CONFIGURE); //$NON-NLS-1$
+	static private final Permission allAttributePermission = new ConfigurationPermission("*", ConfigurationPermission.ATTRIBUTE); //$NON-NLS-1$
 	private final EventDispatcher eventDispatcher;
 	private final PluginManager pluginManager;
 	private final LogTracker log;
@@ -105,6 +106,17 @@
 		return true;
 	}
 
+	public void checkAttributePermission(String location) throws SecurityException {
+		SecurityManager sm = System.getSecurityManager();
+		if (sm != null) {
+			if (location == null) {
+				sm.checkPermission(allAttributePermission);
+			} else {
+				sm.checkPermission(new ConfigurationPermission(location, ConfigurationPermission.ATTRIBUTE));
+			}
+		}
+	}
+
 	void log(int level, String message) {
 		log.log(level, message);
 	}
@@ -139,7 +151,7 @@
 		}
 	}
 
-	void modifyConfiguration(ServiceReference<?> reference, Dictionary<String, Object> properties) {
-		pluginManager.modifyConfiguration(reference, properties);
+	Dictionary<String, Object> modifyConfiguration(ServiceReference<?> reference, ConfigurationImpl config) {
+		return pluginManager.modifyConfiguration(reference, config);
 	}
 }
\ No newline at end of file
diff --git a/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ConfigurationAdminImpl.java b/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ConfigurationAdminImpl.java
index 083e7cb..1cb31fb 100644
--- a/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ConfigurationAdminImpl.java
+++ b/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ConfigurationAdminImpl.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2005, 2018 Cognos Incorporated, IBM Corporation and others..
+ * Copyright (c) 2005, 2019 Cognos Incorporated, IBM Corporation and others..
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -39,32 +39,41 @@
 
 	@Override
 	public Configuration createFactoryConfiguration(String factoryPid) {
-		return internalGetConfiguration(factoryPid, bundleLocation, true, true);
-
+		return internalGetConfiguration(factoryPid, bundleLocation, true, true, null);
 	}
 
 	@Override
 	public Configuration createFactoryConfiguration(String factoryPid, String location) {
-		return internalGetConfiguration(factoryPid, location, true, false);
+		return internalGetConfiguration(factoryPid, location, true, false, null);
 	}
 
 	@Override
 	public Configuration getConfiguration(String pid) {
-		return internalGetConfiguration(pid, bundleLocation, false, true);
+		return internalGetConfiguration(pid, bundleLocation, false, true, null);
 	}
 
 	@Override
 	public Configuration getConfiguration(String pid, String location) {
-		return internalGetConfiguration(pid, location, false, false);
+		return internalGetConfiguration(pid, location, false, false, null);
 	}
 
-	private Configuration internalGetConfiguration(String pid, String location, boolean factory, boolean bind) {
+	@Override
+	public Configuration getFactoryConfiguration(String factoryPid, String name) {
+		return internalGetConfiguration(factoryPid, bundleLocation, true, true, name);
+	}
+
+	@Override
+	public Configuration getFactoryConfiguration(String factoryPid, String name, String location) {
+		return internalGetConfiguration(factoryPid, location, true, false, name);
+	}
+
+	private Configuration internalGetConfiguration(String pid, String location, boolean factory, boolean bind, String name) {
 		checkPID(pid);
 		this.configurationAdminFactory.checkConfigurePermission(location, bundleLocation);
 
 		ConfigurationImpl config;
 		if (factory) {
-			config = configurationStore.createFactoryConfiguration(pid, location, bind);
+			config = configurationStore.getFactoryConfiguration(pid, location, bind, name);
 		} else {
 			config = configurationStore.getConfiguration(pid, location, bind);
 		}
diff --git a/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ConfigurationDictionary.java b/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ConfigurationDictionary.java
index cb9a214..3465f73 100644
--- a/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ConfigurationDictionary.java
+++ b/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ConfigurationDictionary.java
@@ -146,8 +146,8 @@
 				Object copyOfArray = Array.newInstance(value.getClass().getComponentType(), arrayLength);
 				System.arraycopy(value, 0, copyOfArray, 0, arrayLength);
 				result.configurationProperties.put(key, copyOfArray);
-			} else if (value instanceof Vector)
-				result.configurationProperties.put(key, ((Vector<?>) value).clone());
+			} else if (value instanceof Collection)
+				result.configurationProperties.put(key, new ArrayList<>((Collection<?>) value));
 			else
 				result.configurationProperties.put(key, value);
 		}
diff --git a/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ConfigurationImpl.java b/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ConfigurationImpl.java
index d9664d3..e9c36f1 100644
--- a/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ConfigurationImpl.java
+++ b/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ConfigurationImpl.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2005, 2018 Cognos Incorporated, IBM Corporation and others.
+ * Copyright (c) 2005, 2019 Cognos Incorporated, IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -19,8 +19,7 @@
 import java.lang.reflect.Array;
 import java.util.*;
 import java.util.concurrent.locks.ReentrantLock;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.Constants;
+import org.osgi.framework.*;
 import org.osgi.service.cm.*;
 
 /**
@@ -32,22 +31,26 @@
 	final static String LOCATION_BOUND = "org.eclipse.equinox.cm.location.bound"; //$NON-NLS-1$
 	final static String PROPERTIES_NULL = "org.eclipse.equinox.cm.properties.null"; //$NON-NLS-1$
 	final static String CHANGE_COUNT = "org.eclipse.equinox.cm.change.count"; //$NON-NLS-1$
+	final static String READ_ONLY = "org.eclipse.equinox.cm.readonly"; //$NON-NLS-1$
 
 	private final ConfigurationAdminFactory configurationAdminFactory;
 	private final ConfigurationStore configurationStore;
-	/** @GuardedBy this*/
-	private String bundleLocation;
 	private final String factoryPid;
 	private final String pid;
+	/** @GuardedBy lock*/
+	private String bundleLocation;
+	/** @GuardedBy lock*/
 	private ConfigurationDictionary dictionary;
-	/** @GuardedBy this*/
+	/** @GuardedBy lock*/
 	private boolean deleted = false;
-	/** @GuardedBy this*/
+	/** @GuardedBy lock*/
 	private boolean bound = false;
-	/** @GuardedBy this*/
+	/** @GuardedBy lock*/
 	private long changeCount;
-	/** @GuardedBy this*/
+	/** @GuardedBy lock*/
 	private Object storageToken;
+	/** @GuardedBy lock*/
+	private boolean readOnly = false;
 	private final ReentrantLock lock = new ReentrantLock();
 
 	public ConfigurationImpl(ConfigurationAdminFactory configurationAdminFactory, ConfigurationStore configurationStore, String factoryPid, String pid, String bundleLocation, boolean bind) {
@@ -70,6 +73,8 @@
 		this.bound = boundProp == null ? false : boundProp.booleanValue();
 		Long changeCountProp = (Long) dictionary.remove(CHANGE_COUNT);
 		this.changeCount = changeCountProp == null ? 0 : changeCountProp.longValue();
+		Boolean readOnlyProp = (Boolean) dictionary.remove(READ_ONLY);
+		this.readOnly = readOnlyProp == null ? false : readOnlyProp.booleanValue();
 		Boolean nullProps = (Boolean) dictionary.remove(PROPERTIES_NULL);
 		if (nullProps == null || !nullProps.booleanValue()) {
 			updateDictionary(dictionary);
@@ -91,8 +96,8 @@
 	}
 
 	boolean bind(String callerLocation) {
+		lock();
 		try {
-			lock();
 			if (bundleLocation == null) {
 				bundleLocation = callerLocation;
 				bound = true;
@@ -111,8 +116,8 @@
 	}
 
 	boolean isBound() {
+		lock();
 		try {
-			lock();
 			return bound;
 		} finally {
 			unlock();
@@ -120,8 +125,8 @@
 	}
 
 	void unbind(Bundle bundle) {
+		lock();
 		try {
-			lock();
 			String callerLocation = ConfigurationAdminImpl.getLocation(bundle);
 			if (bound && callerLocation.equals(bundleLocation)) {
 				bundleLocation = null;
@@ -143,9 +148,10 @@
 	@Override
 	public void delete() {
 		Object deleteToken;
+		lock();
 		try {
-			lock();
 			checkDeleted();
+			checkReadOnly();
 			deleted = true;
 			configurationAdminFactory.notifyConfigurationDeleted(this, factoryPid != null);
 			configurationAdminFactory.dispatchEvent(ConfigurationEvent.CM_DELETED, factoryPid, pid);
@@ -162,9 +168,15 @@
 			throw new IllegalStateException("deleted"); //$NON-NLS-1$
 	}
 
+	private void checkReadOnly() {
+		if (readOnly) {
+			throw new ReadOnlyConfigurationException("read only"); //$NON-NLS-1$
+		}
+	}
+
 	String getLocation() {
+		lock();
 		try {
-			lock();
 			return bundleLocation;
 		} finally {
 			unlock();
@@ -173,8 +185,8 @@
 
 	@Override
 	public String getBundleLocation() {
+		lock();
 		try {
-			lock();
 			checkDeleted();
 			configurationAdminFactory.checkConfigurePermission(bundleLocation, null);
 			if (bundleLocation != null)
@@ -186,8 +198,8 @@
 	}
 
 	String getFactoryPid(boolean checkDeleted) {
+		lock();
 		try {
-			lock();
 			if (checkDeleted)
 				checkDeleted();
 			return factoryPid;
@@ -202,8 +214,8 @@
 	}
 
 	String getPid(boolean checkDeleted) {
+		lock();
 		try {
-			lock();
 			if (checkDeleted)
 				checkDeleted();
 			return pid;
@@ -219,8 +231,8 @@
 
 	@Override
 	public Dictionary<String, Object> getProperties() {
+		lock();
 		try {
-			lock();
 			checkDeleted();
 			if (dictionary == null)
 				return null;
@@ -234,8 +246,8 @@
 	}
 
 	Dictionary<String, Object> getAllProperties(boolean includeStorageKeys) {
+		lock();
 		try {
-			lock();
 			if (deleted)
 				return null;
 			Dictionary<String, Object> copy = getProperties();
@@ -252,17 +264,21 @@
 		}
 	}
 
-	private static void fileAutoProperties(Dictionary<String, Object> dictionary, ConfigurationImpl config, boolean includeLoc, boolean includeStorageKey) {
+	static void fileAutoProperties(Dictionary<String, Object> dictionary, ConfigurationImpl config, boolean includeLoc, boolean includeStorageKey) {
 		dictionary.put(Constants.SERVICE_PID, config.getPid(false));
 		String factoryPid = config.getFactoryPid(false);
 		if (factoryPid != null) {
 			dictionary.put(ConfigurationAdmin.SERVICE_FACTORYPID, factoryPid);
+		} else {
+			dictionary.remove(ConfigurationAdmin.SERVICE_FACTORYPID);
 		}
 		if (includeLoc) {
 			String loc = config.getLocation();
 			if (loc != null) {
 				dictionary.put(ConfigurationAdmin.SERVICE_BUNDLELOCATION, loc);
 			}
+		} else {
+			dictionary.remove(ConfigurationAdmin.SERVICE_BUNDLELOCATION);
 		}
 		if (includeStorageKey) {
 			if (config.dictionary == null) {
@@ -272,13 +288,14 @@
 			if (config.isBound()) {
 				dictionary.put(LOCATION_BOUND, Boolean.TRUE);
 			}
+			dictionary.put(READ_ONLY, Boolean.valueOf(config.readOnly));
 		}
 	}
 
 	@Override
 	public void setBundleLocation(String bundleLocation) {
+		lock();
 		try {
-			lock();
 			checkDeleted();
 			configurationAdminFactory.checkConfigurePermission(this.bundleLocation, null);
 			configurationAdminFactory.checkConfigurePermission(bundleLocation, null);
@@ -300,9 +317,10 @@
 
 	@Override
 	public void update() throws IOException {
+		lock();
 		try {
-			lock();
 			checkDeleted();
+			checkReadOnly();
 			if (dictionary == null)
 				dictionary = new ConfigurationDictionary();
 			changeCount++;
@@ -315,19 +333,176 @@
 
 	@Override
 	public void update(Dictionary<String, ?> properties) throws IOException {
+		lock();
 		try {
-			lock();
-			checkDeleted();
-			updateDictionary(properties);
-			changeCount++;
-			save();
-			configurationAdminFactory.notifyConfigurationUpdated(this, factoryPid != null);
-			configurationAdminFactory.dispatchEvent(ConfigurationEvent.CM_UPDATED, factoryPid, pid);
+			doUpdate(properties, false);
 		} finally {
 			unlock();
 		}
 	}
 
+	@Override
+	public boolean updateIfDifferent(Dictionary<String, ?> properties) throws IOException {
+		lock();
+		try {
+			return doUpdate(properties, true);
+		} finally {
+			unlock();
+		}
+	}
+
+	private boolean same(Dictionary<String, ?> properties) {
+		if (dictionary == null) {
+			return false;
+		}
+		if (dictionary.size() != properties.size()) {
+			return false;
+		}
+
+		Enumeration<String> keys = properties.keys();
+		while (keys.hasMoreElements()) {
+			String key = keys.nextElement();
+			if (dictionary.get(key) == null) {
+				return false;
+			}
+			Object current = dictionary.get(key);
+			Object newValue = properties.get(key);
+			if (current.getClass().isArray()) {
+				if (!newValue.getClass().isArray()) {
+					return false;
+				}
+				if (!current.getClass().getComponentType().equals(newValue.getClass().getComponentType())) {
+					current = convertIfPossible(current);
+					newValue = convertIfPossible(newValue);
+					if (!current.getClass().getComponentType().equals(newValue.getClass().getComponentType())) {
+						return false;
+					}
+				}
+				Class<?> currentComponentType = current.getClass().getComponentType();
+				if (long.class.isAssignableFrom(currentComponentType)) {
+					if (!Arrays.equals((long[]) current, (long[]) newValue)) {
+						return false;
+					}
+				} else if (int.class.isAssignableFrom(currentComponentType)) {
+					if (!Arrays.equals((int[]) current, (int[]) newValue)) {
+						return false;
+					}
+				} else if (short.class.isAssignableFrom(currentComponentType)) {
+					if (!Arrays.equals((short[]) current, (short[]) newValue)) {
+						return false;
+					}
+				} else if (char.class.isAssignableFrom(currentComponentType)) {
+					if (!Arrays.equals((char[]) current, (char[]) newValue)) {
+						return false;
+					}
+				} else if (byte.class.isAssignableFrom(currentComponentType)) {
+					if (!Arrays.equals((byte[]) current, (byte[]) newValue)) {
+						return false;
+					}
+				} else if (double.class.isAssignableFrom(currentComponentType)) {
+					if (!Arrays.equals((double[]) current, (double[]) newValue)) {
+						return false;
+					}
+				} else if (float.class.isAssignableFrom(currentComponentType)) {
+					if (!Arrays.equals((float[]) current, (float[]) newValue)) {
+						return false;
+					}
+				} else if (boolean.class.isAssignableFrom(currentComponentType)) {
+					if (!Arrays.equals((boolean[]) current, (boolean[]) newValue)) {
+						return false;
+					}
+				} else {
+					if (!Arrays.equals((Object[]) current, (Object[]) newValue)) {
+						return false;
+					}
+				}
+
+			} else {
+				if (!current.equals(newValue)) {
+					return false;
+				}
+			}
+		}
+		return true;
+	}
+
+	private Object convertIfPossible(Object array) {
+		Class<?> componentType = array.getClass().getComponentType();
+		if (Long.class.isAssignableFrom(componentType)) {
+			Long[] original = (Long[]) array;
+			long[] converted = new long[original.length];
+			for (int i = 0; i < original.length; i++) {
+				converted[i] = original[i];
+			}
+			return converted;
+		} else if (Integer.class.isAssignableFrom(componentType)) {
+			Integer[] original = (Integer[]) array;
+			int[] converted = new int[original.length];
+			for (int i = 0; i < original.length; i++) {
+				converted[i] = original[i];
+			}
+			return converted;
+		} else if (Short.class.isAssignableFrom(componentType)) {
+			Short[] original = (Short[]) array;
+			short[] converted = new short[original.length];
+			for (int i = 0; i < original.length; i++) {
+				converted[i] = original[i];
+			}
+			return converted;
+		} else if (Character.class.isAssignableFrom(componentType)) {
+			Character[] original = (Character[]) array;
+			char[] converted = new char[original.length];
+			for (int i = 0; i < original.length; i++) {
+				converted[i] = original[i];
+			}
+			return converted;
+		} else if (Byte.class.isAssignableFrom(componentType)) {
+			Byte[] original = (Byte[]) array;
+			byte[] converted = new byte[original.length];
+			for (int i = 0; i < original.length; i++) {
+				converted[i] = original[i];
+			}
+			return converted;
+		} else if (Double.class.isAssignableFrom(componentType)) {
+			Double[] original = (Double[]) array;
+			double[] converted = new double[original.length];
+			for (int i = 0; i < original.length; i++) {
+				converted[i] = original[i];
+			}
+			return converted;
+		} else if (Float.class.isAssignableFrom(componentType)) {
+			Float[] original = (Float[]) array;
+			float[] converted = new float[original.length];
+			for (int i = 0; i < original.length; i++) {
+				converted[i] = original[i];
+			}
+			return converted;
+		} else if (Boolean.class.isAssignableFrom(componentType)) {
+			Boolean[] original = (Boolean[]) array;
+			boolean[] converted = new boolean[original.length];
+			for (int i = 0; i < original.length; i++) {
+				converted[i] = original[i];
+			}
+			return converted;
+
+		}
+		return array;
+	}
+
+	private boolean doUpdate(Dictionary<String, ?> properties, boolean checkSame) throws IOException {
+		checkDeleted();
+		checkReadOnly();
+		if (checkSame && same(properties)) {
+			return false;
+		}
+		updateDictionary(properties);
+		changeCount++;
+		save();
+		configurationAdminFactory.notifyConfigurationUpdated(this, factoryPid != null);
+		configurationAdminFactory.dispatchEvent(ConfigurationEvent.CM_UPDATED, factoryPid, pid);
+		return true;
+	}
+
 	private void save() throws IOException {
 		checkLocked();
 		storageToken = configurationStore.saveConfiguration(pid, this, this.storageToken);
@@ -370,8 +545,8 @@
 	}
 
 	boolean isDeleted() {
+		lock();
 		try {
-			lock();
 			return deleted;
 		} finally {
 			unlock();
@@ -380,12 +555,62 @@
 
 	@Override
 	public long getChangeCount() {
+		lock();
 		try {
-			lock();
 			checkDeleted();
 			return changeCount;
 		} finally {
 			unlock();
 		}
 	}
+
+	@Override
+	public Dictionary<String, Object> getProcessedProperties(ServiceReference<?> reference) {
+		return configurationAdminFactory.modifyConfiguration(reference, this);
+	}
+
+	@Override
+	public void addAttributes(ConfigurationAttribute... attrs) throws IOException {
+		lock();
+		try {
+			configurationAdminFactory.checkAttributePermission(bundleLocation);
+			for (ConfigurationAttribute attr : attrs) {
+				if (ConfigurationAttribute.READ_ONLY.equals(attr)) {
+					readOnly = true;
+				}
+			}
+			save();
+		} finally {
+			unlock();
+		}
+	}
+
+	@Override
+	public Set<ConfigurationAttribute> getAttributes() {
+		lock();
+		try {
+			if (readOnly) {
+				return EnumSet.of(ConfigurationAttribute.READ_ONLY);
+			}
+			return Collections.emptySet();
+		} finally {
+			unlock();
+		}
+	}
+
+	@Override
+	public void removeAttributes(ConfigurationAttribute... attrs) throws IOException {
+		lock();
+		try {
+			configurationAdminFactory.checkAttributePermission(bundleLocation);
+			for (ConfigurationAttribute attr : attrs) {
+				if (ConfigurationAttribute.READ_ONLY.equals(attr)) {
+					readOnly = false;
+				}
+			}
+			save();
+		} finally {
+			unlock();
+		}
+	}
 }
\ No newline at end of file
diff --git a/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ConfigurationStore.java b/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ConfigurationStore.java
index 38dd965..08e0454 100644
--- a/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ConfigurationStore.java
+++ b/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ConfigurationStore.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2005, 2018 Cognos Incorporated, IBM Corporation and others.
+ * Copyright (c) 2005, 2019 Cognos Incorporated, IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -177,8 +177,18 @@
 		return config;
 	}
 
-	public synchronized ConfigurationImpl createFactoryConfiguration(String factoryPid, String location, boolean bind) {
-		String pid = factoryPid + "-" + new Date().getTime() + "-" + createdPidCount++; //$NON-NLS-1$ //$NON-NLS-2$
+	public synchronized ConfigurationImpl getFactoryConfiguration(String factoryPid, String location, boolean bind, String name) {
+		String pid;
+		if (name == null) {
+			pid = factoryPid + "-" + new Date().getTime() + "-" + createdPidCount++; //$NON-NLS-1$ //$NON-NLS-2$
+		} else {
+			pid = factoryPid + "~" + name; //$NON-NLS-1$
+			ConfigurationImpl config = configurations.get(pid);
+			if (config != null) {
+				return config;
+			}
+		}
+
 		ConfigurationImpl config = new ConfigurationImpl(configurationAdminFactory, this, factoryPid, pid, location, bind);
 		configurations.put(pid, config);
 		return config;
diff --git a/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ManagedServiceFactoryTracker.java b/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ManagedServiceFactoryTracker.java
index 5d71039..04bea73 100644
--- a/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ManagedServiceFactoryTracker.java
+++ b/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ManagedServiceFactoryTracker.java
@@ -74,8 +74,7 @@
 				ManagedServiceFactory serviceFactory = getService(ref);
 				if (hasLocPermission && serviceFactory != null) {
 					if (isMultiple || config.bind(ConfigurationAdminImpl.getLocation(ref.getBundle()))) {
-						Dictionary<String, Object> properties = config.getProperties();
-						configurationAdminFactory.modifyConfiguration(ref, properties);
+						Dictionary<String, Object> properties = configurationAdminFactory.modifyConfiguration(ref, config);
 						asynchUpdated(serviceFactory, config.getPid(), properties);
 					}
 				}
@@ -120,8 +119,7 @@
 					if (delete) {
 						asynchDeleted(serviceFactory, config.getPid());
 					} else if (update) {
-						Dictionary<String, Object> properties = config.getProperties();
-						configurationAdminFactory.modifyConfiguration(ref, properties);
+						Dictionary<String, Object> properties = configurationAdminFactory.modifyConfiguration(ref, config);
 						asynchUpdated(serviceFactory, config.getPid(), properties);
 					}
 					// do not break on !isMultiple since we need to check if the other refs apply no matter what
@@ -218,8 +216,7 @@
 							boolean hasLocPermission = configurationAdminFactory.checkTargetPermission(location, reference);
 							if (hasLocPermission) {
 								if (shouldBind && configs[i].bind(ConfigurationAdminImpl.getLocation(reference.getBundle())) || !shouldBind) {
-									Dictionary<String, Object> properties = configs[i].getProperties();
-									configurationAdminFactory.modifyConfiguration(reference, properties);
+									Dictionary<String, Object> properties = configurationAdminFactory.modifyConfiguration(reference, configs[i]);
 									asynchUpdated(serviceFactory, configs[i].getPid(), properties);
 									foundConfig = true;
 								} else {
diff --git a/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ManagedServiceTracker.java b/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ManagedServiceTracker.java
index 2665cae..32d3d25 100644
--- a/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ManagedServiceTracker.java
+++ b/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/ManagedServiceTracker.java
@@ -78,8 +78,7 @@
 				ManagedService service = getService(ref);
 				if (hasLocPermission && service != null) {
 					if (isMultiple || config.bind(ConfigurationAdminImpl.getLocation(ref.getBundle()))) {
-						Dictionary<String, Object> properties = config.getProperties();
-						configurationAdminFactory.modifyConfiguration(ref, properties);
+						Dictionary<String, Object> properties = configurationAdminFactory.modifyConfiguration(ref, config);
 						asynchUpdated(service, properties);
 					}
 				}
@@ -129,8 +128,7 @@
 						}
 						updateManagedService(qualifiedPidLists, ref, service);
 					} else if (update) {
-						Dictionary<String, Object> properties = config.getProperties();
-						configurationAdminFactory.modifyConfiguration(ref, properties);
+						Dictionary<String, Object> properties = configurationAdminFactory.modifyConfiguration(ref, config);
 						asynchUpdated(service, properties);
 					}
 					// do not break on !isMultiple since we need to check if the other refs apply no matter what
@@ -226,8 +224,7 @@
 							boolean hasLocPermission = configurationAdminFactory.checkTargetPermission(location, reference);
 							if (hasLocPermission) {
 								if ((shouldBind && config.bind(ConfigurationAdminImpl.getLocation(reference.getBundle()))) || !shouldBind) {
-									Dictionary<String, Object> properties = config.getProperties();
-									configurationAdminFactory.modifyConfiguration(reference, properties);
+									Dictionary<String, Object> properties = configurationAdminFactory.modifyConfiguration(reference, config);
 									asynchUpdated(service, properties);
 									foundConfig = true;
 									break qualifiedPids;
diff --git a/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/PluginManager.java b/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/PluginManager.java
index c2bb067..0704b93 100644
--- a/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/PluginManager.java
+++ b/bundles/org.eclipse.equinox.cm/src/org/eclipse/equinox/internal/cm/PluginManager.java
@@ -15,7 +15,8 @@
 package org.eclipse.equinox.internal.cm;
 
 import java.util.*;
-import org.osgi.framework.*;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
 import org.osgi.service.cm.ConfigurationPlugin;
 import org.osgi.util.tracker.ServiceTracker;
 
@@ -37,26 +38,63 @@
 		pluginTracker.close();
 	}
 
-	public void modifyConfiguration(ServiceReference<?> managedReference, Dictionary<String, Object> properties) {
+	public Dictionary<String, Object> modifyConfiguration(ServiceReference<?> managedReference, ConfigurationImpl config) {
+		Dictionary<String, Object> properties = config.getProperties();
 		if (properties == null)
-			return;
+			return null;
 
 		ServiceReference<ConfigurationPlugin>[] references = pluginTracker.getServiceReferences();
 		for (int i = 0; i < references.length; ++i) {
-			String[] pids = (String[]) references[i].getProperty(ConfigurationPlugin.CM_TARGET);
+			Collection<?> pids = getStringProperty(references[i].getProperty(ConfigurationPlugin.CM_TARGET));
 			if (pids != null) {
-				String pid = (String) properties.get(Constants.SERVICE_PID);
-				if (!Arrays.asList(pids).contains(pid))
+				String pid = config.getFactoryPid();
+				if (pid == null) {
+					pid = config.getPid();
+				}
+				if (!pids.contains(pid))
 					continue;
 			}
 			ConfigurationPlugin plugin = pluginTracker.getService(references[i]);
-			if (plugin != null)
-				plugin.modifyConfiguration(managedReference, properties);
+			if (plugin != null) {
+				int rank = getRank(references[i]);
+				if (rank < 0 || rank > 1000) {
+					plugin.modifyConfiguration(managedReference, ((ConfigurationDictionary) properties).copy());
+				} else {
+					plugin.modifyConfiguration(managedReference, properties);
+					ConfigurationImpl.fileAutoProperties(properties, config, false, false);
+				}
+			}
 		}
+		return properties;
+	}
+
+	@SuppressWarnings("unchecked")
+	private Collection<Object> getStringProperty(Object value) {
+		if (value == null)
+			return null;
+		if (value instanceof String) {
+			return Collections.singleton(value);
+		}
+		if (value instanceof String[]) {
+			return Arrays.asList((Object[]) value);
+		}
+		if (value instanceof Collection) {
+			return (Collection<Object>) value;
+		}
+		return null;
+	}
+
+	static final Integer ZERO = Integer.valueOf(0);
+
+	static Integer getRank(ServiceReference<ConfigurationPlugin> ref) {
+		Object ranking = ref.getProperty(ConfigurationPlugin.CM_RANKING);
+		if (ranking == null || !(ranking instanceof Integer))
+			return ZERO;
+		return ((Integer) ranking);
 	}
 
 	private static class PluginTracker extends ServiceTracker<ConfigurationPlugin, ConfigurationPlugin> {
-		final Integer ZERO = Integer.valueOf(0);
+
 		private TreeSet<ServiceReference<ConfigurationPlugin>> serviceReferences = new TreeSet<>(new Comparator<ServiceReference<ConfigurationPlugin>>() {
 			@Override
 			public int compare(ServiceReference<ConfigurationPlugin> s1, ServiceReference<ConfigurationPlugin> s2) {
@@ -68,13 +106,6 @@
 				// we reverse the order which means services with higher service.ranking properties are called first
 				return -(s1.compareTo(s2));
 			}
-
-			private Integer getRank(ServiceReference<ConfigurationPlugin> ref) {
-				Object ranking = ref.getProperty(ConfigurationPlugin.CM_RANKING);
-				if (ranking == null || !(ranking instanceof Integer))
-					return ZERO;
-				return ((Integer) ranking);
-			}
 		});
 
 		public PluginTracker(BundleContext context) {