SLING-10538 : Support merging of configurations
diff --git a/pom.xml b/pom.xml
index 5740253..09b4e1c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -90,7 +90,7 @@
<dependency>
<groupId>org.apache.sling</groupId>
<artifactId>org.apache.sling.installer.core</artifactId>
- <version>3.9.0</version>
+ <version>3.11.5-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
diff --git a/src/main/java/org/apache/sling/installer/factories/configuration/impl/Activator.java b/src/main/java/org/apache/sling/installer/factories/configuration/impl/Activator.java
index d09a6c5..cdf0092 100644
--- a/src/main/java/org/apache/sling/installer/factories/configuration/impl/Activator.java
+++ b/src/main/java/org/apache/sling/installer/factories/configuration/impl/Activator.java
@@ -18,6 +18,9 @@
*/
package org.apache.sling.installer.factories.configuration.impl;
+import java.util.Arrays;
+import java.util.List;
+
import org.osgi.annotation.bundle.Header;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
@@ -32,23 +35,30 @@
/** Property for bundle location default. */
private static final String PROP_LOCATION_DEFAULT = "sling.installer.config.useMulti";
+ /** Property for configuration merge schemes. */
+ private static final String PROP_MERGE_SCHEMES = "sling.installer.config.mergeSchemes";
+
/** Services listener. */
private ServicesListener listener;
public static String DEFAULT_LOCATION;
+ public static List<String> MERGE_SCHEMES;
+
+
/**
* @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
*/
public void start(final BundleContext context) throws Exception {
- String locationDefault = null;
if ( context.getProperty(PROP_LOCATION_DEFAULT) != null ) {
final Boolean bool = Boolean.valueOf(context.getProperty(PROP_LOCATION_DEFAULT).toString());
if ( bool.booleanValue() ) {
- locationDefault = "?";
+ DEFAULT_LOCATION = "?";
}
}
- DEFAULT_LOCATION = locationDefault;
+ if ( context.getProperty(PROP_MERGE_SCHEMES) != null ) {
+ MERGE_SCHEMES = Arrays.asList(context.getProperty(PROP_MERGE_SCHEMES).split(","));
+ }
this.listener = new ServicesListener(context);
}
diff --git a/src/main/java/org/apache/sling/installer/factories/configuration/impl/ConfigInstallTask.java b/src/main/java/org/apache/sling/installer/factories/configuration/impl/ConfigInstallTask.java
index 7285e86..152633e 100644
--- a/src/main/java/org/apache/sling/installer/factories/configuration/impl/ConfigInstallTask.java
+++ b/src/main/java/org/apache/sling/installer/factories/configuration/impl/ConfigInstallTask.java
@@ -20,9 +20,17 @@
import java.io.IOException;
import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
import org.apache.sling.installer.api.tasks.InstallationContext;
import org.apache.sling.installer.api.tasks.ResourceState;
+import org.apache.sling.installer.api.tasks.TaskResource;
import org.apache.sling.installer.api.tasks.TaskResourceGroup;
import org.apache.sling.installer.factories.configuration.ConfigurationConstants;
import org.apache.sling.installer.factories.configuration.impl.Coordinator.Operation;
@@ -36,8 +44,8 @@
private static final String CONFIG_INSTALL_ORDER = "20-";
- public ConfigInstallTask(final TaskResourceGroup r, final ConfigurationAdmin configAdmin) {
- super(r, configAdmin);
+ public ConfigInstallTask(final TaskResourceGroup group, final ConfigurationAdmin configAdmin) {
+ super(group, configAdmin);
}
@Override
@@ -46,13 +54,40 @@
}
@Override
+ protected Dictionary<String, Object> getDictionary() {
+ Dictionary<String, Object> properties = super.getDictionary();
+
+ if ( Activator.MERGE_SCHEMES != null ) {
+ final List<Dictionary<String, Object>> propertiesList = new ArrayList<>();
+ propertiesList.add(properties);
+ final Iterator<TaskResource> iter = this.getResourceGroup().getActiveResourceIterator();
+ if ( iter != null ) {
+ // skip first active resource
+ iter.next();
+ while ( iter.hasNext()) {
+ final TaskResource rsrc = iter.next();
+
+ if ( Activator.MERGE_SCHEMES.contains(rsrc.getScheme())) {
+ propertiesList.add(rsrc.getDictionary());
+ }
+ }
+ }
+ if ( propertiesList.size() > 1 ) {
+ properties = ConfigUtil.mergeReverseOrder(propertiesList);
+ }
+ }
+ return properties;
+ }
+
+ @Override
public void execute(final InstallationContext ctx) {
synchronized ( Coordinator.SHARED ) {
// Get or create configuration, but do not
// update if the new one has the same values.
+ final Dictionary<String, Object> properties = this.getDictionary();
boolean created = false;
try {
- String location = (String)this.getResource().getDictionary().get(ConfigurationConstants.PROPERTY_BUNDLE_LOCATION);
+ String location = (String)properties.get(ConfigurationConstants.PROPERTY_BUNDLE_LOCATION);
if ( location == null ) {
location = Activator.DEFAULT_LOCATION; // default
} else if ( location.length() == 0 ) {
@@ -65,7 +100,7 @@
config = ConfigUtil.createConfiguration(this.getConfigurationAdmin(), this.factoryPid, this.configPid, location);
created = true;
} else {
- if (ConfigUtil.isSameData(config.getProperties(), getResource().getDictionary())) {
+ if (ConfigUtil.isSameData(config.getProperties(), properties)) {
this.getLogger().debug("Configuration {} already installed with same data, update request ignored: {}",
config.getPid(), getResource());
config = null;
@@ -75,7 +110,7 @@
}
if (config != null) {
- config.update(getDictionary());
+ config.update(properties);
ctx.log("Installed configuration {} from resource {}", config.getPid(), getResource());
this.getLogger().debug("Configuration " + config.getPid()
+ " " + (created ? "created" : "updated")
diff --git a/src/main/java/org/apache/sling/installer/factories/configuration/impl/ConfigTaskCreator.java b/src/main/java/org/apache/sling/installer/factories/configuration/impl/ConfigTaskCreator.java
index 92d824e..6b7559b 100644
--- a/src/main/java/org/apache/sling/installer/factories/configuration/impl/ConfigTaskCreator.java
+++ b/src/main/java/org/apache/sling/installer/factories/configuration/impl/ConfigTaskCreator.java
@@ -18,8 +18,10 @@
*/
package org.apache.sling.installer.factories.configuration.impl;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Dictionary;
+import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
@@ -27,6 +29,9 @@
import org.apache.sling.installer.api.InstallableResource;
import org.apache.sling.installer.api.ResourceChangeListener;
+import org.apache.sling.installer.api.info.InfoProvider;
+import org.apache.sling.installer.api.info.Resource;
+import org.apache.sling.installer.api.info.ResourceGroup;
import org.apache.sling.installer.api.tasks.ChangeStateTask;
import org.apache.sling.installer.api.tasks.InstallTask;
import org.apache.sling.installer.api.tasks.InstallTaskFactory;
@@ -62,10 +67,15 @@
/** Resource change listener. */
private final ResourceChangeListener changeListener;
+ /** Info Provider */
+ private final InfoProvider infoProvider;
+
public ConfigTaskCreator(final ResourceChangeListener listener,
- final ConfigurationAdmin configAdmin) {
+ final ConfigurationAdmin configAdmin,
+ final InfoProvider infoProvider) {
this.changeListener = listener;
this.configAdmin = configAdmin;
+ this.infoProvider = infoProvider;
}
public ServiceRegistration<?> register(final BundleContext bundleContext) {
@@ -80,7 +90,10 @@
ConfigurationListener.class.getName(),
ResourceTransformer.class.getName()
};
- return bundleContext.registerService(serviceInterfaces, this, props);
+ final ServiceRegistration<?> reg = bundleContext.registerService(serviceInterfaces, this, props);
+ this.logger.info("OSGi Configuration support for OSGi installer active, default location={}, merge schemes={}",
+ Activator.DEFAULT_LOCATION, Activator.MERGE_SCHEMES);
+ return reg;
}
/**
@@ -151,6 +164,7 @@
attrs.put(ConfigurationAdmin.SERVICE_FACTORYPID, event.getFactoryPid());
}
+ removeDefaultProperties(event.getPid(), dict);
this.changeListener.resourceAddedOrUpdated(InstallableResource.TYPE_CONFIG, event.getPid(), null, dict, attrs);
} else {
@@ -163,6 +177,40 @@
}
}
+ private void removeDefaultProperties(final String pid, final Dictionary<String, Object> dict) {
+ if ( Activator.MERGE_SCHEMES != null ) {
+ final List<Dictionary<String, Object>> propertiesList = new ArrayList<>();
+ final String entityId = InstallableResource.TYPE_CONFIG.concat(":").concat(pid);
+ boolean done = false;
+ for(final ResourceGroup group : this.infoProvider.getInstallationState().getInstalledResources()) {
+ for(final Resource rsrc : group.getResources()) {
+ if ( rsrc.getEntityId().equals(entityId) ) {
+ done = true;
+ if ( Activator.MERGE_SCHEMES.contains(rsrc.getScheme()) ) {
+ propertiesList.add(rsrc.getDictionary());
+ }
+ }
+ }
+ if ( done ) {
+ break;
+ }
+ }
+ if ( !propertiesList.isEmpty() ) {
+ final Dictionary<String, Object> defaultProps = ConfigUtil.mergeReverseOrder(propertiesList);
+ final Enumeration<String> keyEnum = defaultProps.keys();
+ while ( keyEnum.hasMoreElements() ) {
+ final String key = keyEnum.nextElement();
+ final Object value = defaultProps.get(key);
+
+ final Object newValue = dict.get(key);
+ if ( newValue != null && ConfigUtil.isSameValue(newValue, value)) {
+ dict.remove(key);
+ }
+ }
+ }
+ }
+ }
+
/**
* @see org.apache.sling.installer.api.tasks.ResourceTransformer#transform(org.apache.sling.installer.api.tasks.RegisteredResource)
*/
diff --git a/src/main/java/org/apache/sling/installer/factories/configuration/impl/ConfigUtil.java b/src/main/java/org/apache/sling/installer/factories/configuration/impl/ConfigUtil.java
index 1e42ade..30f3f50 100644
--- a/src/main/java/org/apache/sling/installer/factories/configuration/impl/ConfigUtil.java
+++ b/src/main/java/org/apache/sling/installer/factories/configuration/impl/ConfigUtil.java
@@ -20,10 +20,12 @@
import java.io.IOException;
import java.lang.reflect.Array;
+import java.util.Collections;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Hashtable;
+import java.util.List;
import java.util.Set;
import org.osgi.framework.Constants;
@@ -98,28 +100,8 @@
for(final String key : keysA ) {
final Object valA = a.get(key);
final Object valB = b.get(key);
- if ( valA.getClass().isArray() && valB.getClass().isArray()) {
- final Object[] arrA = convertToObjectArray(valA);
- final Object[] arrB = convertToObjectArray(valB);
- if ( arrA.length != arrB.length ) {
- result = false;
- break;
- }
- for(int i=0; i<arrA.length; i++) {
- if ( !(String.valueOf(arrA[i]).equals(String.valueOf(arrB[i]))) ) {
- result = false;
- break;
- }
- }
- } else if (!valA.getClass().isArray() && !valB.getClass().isArray()) {
- // if no arrays do a string comparison
- if ( !(String.valueOf(valA).equals(String.valueOf(valB))) ) {
- result = false;
- break;
- }
- } else {
- // one value is array the other is not!
+ if ( !isSameValue(valA, valB) ) {
result = false;
break;
}
@@ -129,6 +111,31 @@
return result;
}
+ public static boolean isSameValue(final Object valA, final Object valB) {
+ if ( valA.getClass().isArray() && valB.getClass().isArray()) {
+ final Object[] arrA = convertToObjectArray(valA);
+ final Object[] arrB = convertToObjectArray(valB);
+
+ if ( arrA.length != arrB.length ) {
+ return false;
+ }
+ for(int i=0; i<arrA.length; i++) {
+ if ( !(String.valueOf(arrA[i]).equals(String.valueOf(arrB[i]))) ) {
+ return false;
+ }
+ }
+ } else if (!valA.getClass().isArray() && !valB.getClass().isArray()) {
+ // if no arrays do a string comparison
+ if ( !(String.valueOf(valA).equals(String.valueOf(valB))) ) {
+ return false;
+ }
+ } else {
+ // one value is array the other is not!
+ return false;
+ }
+ return true;
+ }
+
/**
* Remove all ignored properties
*/
@@ -278,4 +285,31 @@
public static String getPIDOfFactoryPID(final String factoryPID, final String name) {
return factoryPID.concat("~").concat(name);
}
+
+ /**
+ * Merge all dictionaries into a single dictionary in reverse order
+ * @param propertiesList The list of dictionaries
+ * @return The merged dictionary
+ */
+ public static Dictionary<String, Object> mergeReverseOrder(final List<Dictionary<String, Object>> propertiesList) {
+ Collections.reverse(propertiesList);
+ final Dictionary<String, Object> properties = new Hashtable<>();
+ for(final Dictionary<String, Object> dict : propertiesList) {
+ merge(properties, dict);
+ }
+ return properties;
+ }
+
+ /**
+ * Merge one dictionary into the other
+ * @param base Base dictionary
+ * @param props Overwriting dictionary
+ */
+ private static void merge(final Dictionary<String, Object> base, final Dictionary<String, Object> props) {
+ final Enumeration<String> keyIter = props.keys();
+ while (keyIter.hasMoreElements() ) {
+ final String key = keyIter.nextElement();
+ base.put(key, props.get(key));
+ }
+ }
}
diff --git a/src/main/java/org/apache/sling/installer/factories/configuration/impl/ServicesListener.java b/src/main/java/org/apache/sling/installer/factories/configuration/impl/ServicesListener.java
index 526bdfe..41e37f9 100644
--- a/src/main/java/org/apache/sling/installer/factories/configuration/impl/ServicesListener.java
+++ b/src/main/java/org/apache/sling/installer/factories/configuration/impl/ServicesListener.java
@@ -21,6 +21,7 @@
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.sling.installer.api.ResourceChangeListener;
+import org.apache.sling.installer.api.info.InfoProvider;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.InvalidSyntaxException;
@@ -49,6 +50,9 @@
/** The listener for the configuration admin. */
private final Listener configAdminListener;
+ /** The listener for the installer info service. */
+ private final Listener infoServiceListener;
+
/** Registration the service. */
private volatile ServiceRegistration<?> configTaskCreatorRegistration;
@@ -60,6 +64,8 @@
this.bundleContext = bundleContext;
this.changeHandlerListener = new Listener(ResourceChangeListener.class.getName());
this.configAdminListener = new Listener(ConfigurationAdmin.class.getName());
+ this.infoServiceListener = new Listener(InfoProvider.class.getName());
+ this.infoServiceListener.start();
this.changeHandlerListener.start();
this.configAdminListener.start();
}
@@ -68,12 +74,13 @@
// check if all services are available
final ResourceChangeListener listener = (ResourceChangeListener)this.changeHandlerListener.getService();
final ConfigurationAdmin configAdmin = (ConfigurationAdmin)this.configAdminListener.getService();
+ final InfoProvider infoProvider = (InfoProvider)this.infoServiceListener.getService();
- if ( configAdmin != null && listener != null ) {
+ if ( configAdmin != null && listener != null && infoProvider != null ) {
if ( configTaskCreator == null ) {
active.set(true);
// start and register osgi installer service
- this.configTaskCreator = new ConfigTaskCreator(listener, configAdmin);
+ this.configTaskCreator = new ConfigTaskCreator(listener, configAdmin, infoProvider);
final ConfigUpdateHandler handler = new ConfigUpdateHandler(configAdmin, this);
configTaskCreatorRegistration = handler.register(this.bundleContext);
}
@@ -107,6 +114,7 @@
* Deactivate this listener.
*/
public void deactivate() {
+ this.infoServiceListener.deactivate();
this.changeHandlerListener.deactivate();
this.configAdminListener.deactivate();
this.stop();