SLING-7786 : Use R7 configuration admin supporting named factory configurations
diff --git a/src/test/java/org/apache/sling/installer/it/ConfigUpdateTest.java b/src/test/java/org/apache/sling/installer/it/ConfigUpdateTest.java
new file mode 100644
index 0000000..6074c0e
--- /dev/null
+++ b/src/test/java/org/apache/sling/installer/it/ConfigUpdateTest.java
@@ -0,0 +1,306 @@
+/*
+ * 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.sling.installer.it;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import static org.junit.Assert.*;
+
+import org.apache.sling.installer.api.InstallableResource;
+import org.apache.sling.installer.api.event.InstallationEvent;
+import org.apache.sling.installer.api.event.InstallationListener;
+import org.apache.sling.installer.api.info.InfoProvider;
+import org.apache.sling.installer.api.info.InstallationState;
+import org.apache.sling.installer.api.info.Resource;
+import org.apache.sling.installer.api.info.ResourceGroup;
+import org.apache.sling.installer.api.tasks.ResourceState;
+import org.apache.sling.installer.api.tasks.TaskResource;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Constants;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.cm.ConfigurationAdmin;
+
+/**
+ * This test tests updating the configuration factory bundle from a pre 1.2.0 version to a 1.2.0+
+ * version which is using the support for named factory configurations of Configuration Admin 1.6.0+.
+ */
+@RunWith(PaxExam.class)
+public class ConfigUpdateTest extends OsgiInstallerTestBase {
+
+ static private final String GROUP_ID = "org.apache.sling";
+ static private final String ARTIFACT_ID = "org.apache.sling.installer.factory.configuration";
+ static private final String OLD_VERSION = "1.1.2";
+
+ private static final String FACTORY_PID = "org.apache.sling.factory.test";
+ private static final String NAME_1 = "myname1";
+
+ private static final String SCHEME = "myscheme";
+
+ @org.ops4j.pax.exam.Configuration
+ public Option[] config() {
+ return defaultConfiguration();
+ }
+
+ private Bundle getConfigFactoryBundle() {
+ for(final Bundle b : this.bundleContext.getBundles()) {
+ if ( ARTIFACT_ID.equals(b.getSymbolicName())) {
+ return b;
+ }
+ }
+ throw new IllegalStateException("Config factory bundle not found");
+ }
+
+ private void updateConfigFactoryBundle() throws Exception {
+ final Bundle b = getConfigFactoryBundle();
+ b.stop();
+ final String urlString = org.ops4j.pax.exam.CoreOptions.mavenBundle(GROUP_ID, ARTIFACT_ID, OsgiInstallerTestBase.CONFIG_VERSION).getURL();
+ final URL url = new URL(urlString);
+ try ( final InputStream is = url.openStream()) {
+ b.update(is);
+ }
+ b.start();
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ // we need the old config factory first
+ final Bundle b = getConfigFactoryBundle();
+ b.stop();
+ final String urlString = org.ops4j.pax.exam.CoreOptions.mavenBundle(GROUP_ID, ARTIFACT_ID, OLD_VERSION).getURL();
+ final URL url = new URL(urlString);
+ try ( final InputStream is = url.openStream()) {
+ b.update(is);
+ }
+ b.start();
+ super.setup();
+ setupInstaller();
+ }
+
+ private InstallableResource[] createTestConfigs() {
+ final Dictionary<String, Object> props = new Hashtable<>();
+ props.put("key", "value");
+ props.put("id", NAME_1);
+
+ // we need to specify a path as config factory < 1.2.0 has a bug in handling the id if a path is missing
+ final InstallableResource rsrc = new InstallableResource("configs/" + FACTORY_PID + "-" + NAME_1 + ".cfg",
+ null, props, "1", InstallableResource.TYPE_CONFIG, null);
+
+ return new InstallableResource[] {rsrc};
+ }
+
+ private void assertConfig(final String name, final boolean checkNew) throws Exception {
+ final ConfigurationAdmin ca = this.getService(ConfigurationAdmin.class);
+ final Configuration[] cfgs = ca.listConfigurations("(&(" + ConfigurationAdmin.SERVICE_FACTORYPID + "=" + FACTORY_PID + ")" +
+ "(id=" + name + "))");
+ assertNotNull(cfgs);
+ assertEquals(1, cfgs.length);
+ final Configuration c = cfgs[0];
+ assertEquals("value", c.getProperties().get("key"));
+ assertEquals(name, c.getProperties().get("id"));
+
+ if ( !checkNew) {
+ assertFalse(c.getPid().equals(FACTORY_PID + "~" + name));
+ final Configuration[] cfgs1 = ca.listConfigurations("(" + Constants.SERVICE_PID + "=" + FACTORY_PID + "~" + name + ")");
+ assertTrue(cfgs1 == null || cfgs1.length == 0);
+ assertNull(c.getProperties().get("modified"));
+ }
+ if ( checkNew) {
+ final Configuration[] cfgs1 = ca.listConfigurations("(" + Constants.SERVICE_PID + "=" + FACTORY_PID + "~" + name + ")");
+ assertNotNull(cfgs1);
+ assertEquals(1, cfgs1.length);
+ final Configuration c1 = cfgs1[0];
+ assertEquals("value", c1.getProperties().get("key"));
+ assertEquals(name, c1.getProperties().get("id"));
+ assertEquals(FACTORY_PID, c1.getFactoryPid());
+ assertEquals(Boolean.TRUE, c.getProperties().get("modified"));
+ }
+ }
+
+ @Override
+ @After
+ public void tearDown() {
+ try {
+ final Configuration[] cfgs = this.getService(ConfigurationAdmin.class).listConfigurations(null);
+ if ( cfgs != null ) {
+ for(final Configuration c : cfgs) {
+ c.delete();
+ }
+ }
+ } catch ( final IOException | InvalidSyntaxException ignore) {
+ // ignore
+ }
+ super.tearDown();
+ }
+
+ /**
+ * Simply updating the bundle should not change anything
+ */
+ @Test public void testBundleUpdate() throws Exception {
+ final InstallableResource[] resources = createTestConfigs();
+ final ResourceInstallationListener listener = new ResourceInstallationListener(resources.length);
+ final ServiceRegistration<InstallationListener> reg = this.bundleContext.registerService(InstallationListener.class, listener, null);
+ try {
+ installer.registerResources(SCHEME, resources);
+
+ listener.waitForInstall();
+ } finally {
+ reg.unregister();
+ }
+ // check for configuration
+ assertConfig(NAME_1, false);
+
+ updateConfigFactoryBundle();
+
+ assertConfig(NAME_1, false);
+ }
+
+ /**
+ * Simply updating the bundle and then updating the config with the same contents should not change anything
+ */
+ @Test public void testBundleAndConfigRegisterWithoutChange() throws Exception {
+ testBundleUpdate();
+
+ final InstallableResource[] resources = createTestConfigs();
+ final ResourceInstallationListener listener = new ResourceInstallationListener(resources.length);
+ final ServiceRegistration<InstallationListener> reg = this.bundleContext.registerService(InstallationListener.class, listener, null);
+ try {
+ installer.registerResources(SCHEME, resources);
+
+ listener.waitForInstall();
+ } finally {
+ reg.unregister();
+ }
+ // check for configuration
+ assertConfig(NAME_1, false);
+ }
+
+ /**
+ * Updating the bundle and then updating the config with a new config should convert the configurations
+ */
+ @Test public void testBundleAndConfigRegisterWithChange() throws Exception {
+ testBundleUpdate();
+
+ final InstallableResource[] resources = createTestConfigs();
+ for(final InstallableResource rsrc : resources) {
+ rsrc.getDictionary().put("modified", Boolean.TRUE);
+ }
+ final ResourceInstallationListener listener = new ResourceInstallationListener(resources.length);
+ final ServiceRegistration<InstallationListener> reg = this.bundleContext.registerService(InstallationListener.class, listener, null);
+ try {
+ installer.registerResources(SCHEME, resources);
+
+ listener.waitForInstall();
+ } finally {
+ reg.unregister();
+ }
+ // check for configuration
+ assertConfig(NAME_1, true);
+ }
+
+ /**
+ * Updating the bundle and then updating the config with a new config should convert the configurations
+ */
+ @Test public void testBundleAndConfigUpdateWithChange() throws Exception {
+ testBundleUpdate();
+
+ final InstallableResource[] resources = createTestConfigs();
+ for(final InstallableResource rsrc : resources) {
+ rsrc.getDictionary().put("modified", Boolean.TRUE);
+ }
+ final ResourceInstallationListener listener = new ResourceInstallationListener(resources.length);
+ final ServiceRegistration<InstallationListener> reg = this.bundleContext.registerService(InstallationListener.class, listener, null);
+ try {
+ installer.updateResources(SCHEME, resources, null);
+
+ listener.waitForInstall();
+ } finally {
+ reg.unregister();
+ }
+ // check for configuration
+ assertConfig(NAME_1, true);
+ }
+
+ private class ResourceInstallationListener implements InstallationListener {
+
+ private final AtomicInteger processedBundles = new AtomicInteger(0);
+ private final AtomicBoolean doneProcessing = new AtomicBoolean(false);
+
+ private final int count;
+
+ public ResourceInstallationListener(final int count) {
+ this.count = count;
+ }
+
+ @Override
+ public void onEvent(final InstallationEvent event) {
+ if ( event.getType() == InstallationEvent.TYPE.PROCESSED ) {
+ final TaskResource rsrc = (TaskResource) event.getSource();
+ if ( rsrc.getScheme().equals(SCHEME) ) {
+ if ( rsrc.getState() == ResourceState.IGNORED || rsrc.getState() == ResourceState.INSTALLED ) {
+ processedBundles.incrementAndGet();
+ }
+ }
+ } else if ( event.getType() == InstallationEvent.TYPE.SUSPENDED && processedBundles.get() > 0 ) {
+ doneProcessing.set(true);
+ }
+
+ }
+
+ public void waitForInstall() {
+ final long startTime = System.currentTimeMillis();
+ while ( !doneProcessing.get() && startTime + 10000 > System.currentTimeMillis() ) {
+ sleep(200);
+ }
+ if ( processedBundles.get() < count ) {
+ final InfoProvider infoProvider = getService(InfoProvider.class);
+ int bundlesCount = 0;
+ while ( bundlesCount < count ) {
+ bundlesCount = 0;
+ final InstallationState state = infoProvider.getInstallationState();
+ for(final ResourceGroup group : state.getInstalledResources()) {
+ for(final Resource rsrc : group.getResources()) {
+ if ( rsrc.getScheme().equals(SCHEME) ) {
+ bundlesCount++;
+ }
+ }
+ }
+ for(final ResourceGroup group : state.getActiveResources()) {
+ for(final Resource rsrc : group.getResources()) {
+ if ( rsrc.getScheme().equals(SCHEME) ) {
+ bundlesCount++;
+ }
+ }
+ }
+ sleep(200);
+ }
+ }
+ }
+ }
+}
diff --git a/src/test/java/org/apache/sling/installer/it/OsgiInstallerTestBase.java b/src/test/java/org/apache/sling/installer/it/OsgiInstallerTestBase.java
index 6fd293c..da6eaec 100644
--- a/src/test/java/org/apache/sling/installer/it/OsgiInstallerTestBase.java
+++ b/src/test/java/org/apache/sling/installer/it/OsgiInstallerTestBase.java
@@ -70,7 +70,7 @@
/** Base class for OsgiInstaller testing */
public class OsgiInstallerTestBase implements FrameworkListener {
private final static String POM_VERSION = System.getProperty("osgi.installer.pom.version", "POM_VERSION_NOT_SET");
- private final static String CONFIG_VERSION = System.getProperty("installer.configuration.version", "INSTALLER_VERSION_NOT_SET");
+ public final static String CONFIG_VERSION = System.getProperty("installer.configuration.version", "INSTALLER_VERSION_NOT_SET");
public final static String JAR_EXT = ".jar";
private volatile int packageRefreshEventsCount;