blob: 6a56db0d5c6716a9707ff8037db5154a033183c9 [file] [log] [blame]
/*
* 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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
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.tasks.ResourceState;
import org.apache.sling.installer.api.tasks.ResourceTransformer;
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.ServiceReference;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.cm.ConfigurationEvent;
import org.osgi.service.cm.ConfigurationListener;
import org.osgi.util.tracker.ServiceTracker;
import org.osgi.util.tracker.ServiceTrackerCustomizer;
@RunWith(PaxExam.class)
public class ConfigInstallTest extends OsgiInstallerTestBase implements ConfigurationListener {
private final List<ConfigurationEvent> events = new LinkedList<ConfigurationEvent>();
private final List<ServiceRegistration<?>> serviceRegistrations = new ArrayList<ServiceRegistration<?>>();
private volatile int installationEvents = 0;
private static final AtomicInteger counter = new AtomicInteger();
@org.ops4j.pax.exam.Configuration
public Option[] config() {
return defaultConfiguration();
}
@Before
public void setUp() {
installationEvents = 0;
setupInstaller();
events.clear();
serviceRegistrations.clear();
serviceRegistrations.add(bundleContext.registerService(ConfigurationListener.class.getName(), this, null));
final InstallationListener il = new InstallationListener() {
@Override
public void onEvent(InstallationEvent event) {
installationEvents++;
}
};
serviceRegistrations.add(bundleContext.registerService(InstallationListener.class.getName(), il, null));
}
@Override
@After
public void tearDown() {
super.tearDown();
for(ServiceRegistration<?> reg : serviceRegistrations) {
reg.unregister();
}
serviceRegistrations.clear();
}
private String uniqueID() {
return counter.incrementAndGet() + "_" + System.currentTimeMillis();
}
/**
* @see org.osgi.service.cm.ConfigurationListener#configurationEvent(org.osgi.service.cm.ConfigurationEvent)
*/
@Override
public void configurationEvent(final ConfigurationEvent e) {
if ( e.getType() == ConfigurationEvent.CM_DELETED || e.getType() == ConfigurationEvent.CM_UPDATED) {
synchronized ( events ) {
events.add(e);
}
}
}
@Test
public void testInstallConfigWindowsPath() throws Exception {
final Dictionary<String, Object> cfgData = new Hashtable<String, Object>();
cfgData.put("foo", "bar");
final String cfgPid = getClass().getSimpleName() + "." + uniqueID();
assertNull("Config " + cfgPid + " must not be found before test", findConfiguration(cfgPid));
// install configs
//Following patterns work fine. Problem occurs one with windows seprator
// - "c:/foo/bar/"
// - "/foo/bar/"
final String uri = "c:\\foo\bar\\";
final InstallableResource result = new MockInstallableResource(uri + cfgPid, copy(cfgData), null, null, 100);
final InstallableResource[] rsrc = new InstallableResource[] {result};
installer.updateResources(URL_SCHEME, rsrc, null);
Configuration cfg = waitForConfiguration("After installing", cfgPid, true);
assertEquals("Config value must match", "bar", cfg.getProperties().get("foo"));
// remove again
installer.updateResources(URL_SCHEME, null, new String[] {rsrc[0].getId()});
waitForConfiguration("After removing for the second time", cfgPid, false);
}
@Test
public void testInstallAndRemoveConfig() throws Exception {
final Dictionary<String, Object> cfgData = new Hashtable<String, Object>();
cfgData.put("foo", "bar");
final String cfgPid = getClass().getSimpleName() + "." + uniqueID();
assertNull("Config " + cfgPid + " must not be found before test", findConfiguration(cfgPid));
// install config
final InstallableResource[] rsrc = getInstallableResource(cfgPid, cfgData);
installer.updateResources(URL_SCHEME, rsrc, null);
Configuration cfg = waitForConfiguration("After installing", cfgPid, true);
assertEquals("Config value must match", "bar", cfg.getProperties().get("foo"));
// remove resource
installer.updateResources(URL_SCHEME, null, new String[] {rsrc[0].getId()});
waitForConfiguration("After removing", cfgPid, false);
// Reinstalling with same digest must work
installer.updateResources(URL_SCHEME, rsrc, null);
cfg = waitForConfiguration("After reinstalling", cfgPid, true);
assertEquals("Config value must match", "bar", cfg.getProperties().get("foo"));
// remove again
installer.updateResources(URL_SCHEME, null, new String[] {rsrc[0].getId()});
waitForConfiguration("After removing for the second time", cfgPid, false);
}
@Test
public void testInstallUpdateRemoveConfigResource() throws Exception {
final Dictionary<String, Object> cfgData = new Hashtable<String, Object>();
cfgData.put("foo", "bar");
final String cfgPid = getClass().getSimpleName() + "." + uniqueID();
// install config
final InstallableResource rsrc = new InstallableResource("/configA/" + cfgPid,
null, cfgData, null, InstallableResource.TYPE_PROPERTIES, 10);
installer.updateResources(URL_SCHEME, new InstallableResource[] {rsrc}, null);
// get config
waitForConfigValue("After installing", cfgPid, "foo", "bar");
// create second configuration
final Dictionary<String, Object> secondData = new Hashtable<String, Object>();
secondData.put("foo", "bla");
final InstallableResource rsrc2 = new InstallableResource("/configB/" + cfgPid,
null, secondData, null, InstallableResource.TYPE_PROPERTIES, 20);
installer.updateResources(URL_SCHEME, new InstallableResource[] {rsrc2}, null);
// get updated config
waitForConfigValue("After updating", cfgPid, "foo", "bla");
// remove config
installer.updateResources(URL_SCHEME, null, new String[] {"/configB/" + cfgPid});
waitForConfigValue("After deleting", cfgPid, "foo", "bar");
}
@Test
public void testInstallUpdateRemoveTemplateConfigResource() throws Exception {
final Dictionary<String, Object> cfgData = new Hashtable<String, Object>();
cfgData.put("foo", "bar");
cfgData.put(InstallableResource.RESOURCE_IS_TEMPLATE, "true");
final String cfgPid = getClass().getSimpleName() + "." + uniqueID();
// install config
final InstallableResource rsrc = new InstallableResource("/configA/" + cfgPid,
null, cfgData, null, InstallableResource.TYPE_PROPERTIES, 10);
installer.updateResources(URL_SCHEME, new InstallableResource[] {rsrc}, null);
// get config
waitForConfigValue("After installing", cfgPid, "foo", "bar");
// create second configuration
final Dictionary<String, Object> secondData = new Hashtable<String, Object>();
secondData.put("foo", "bla");
final InstallableResource rsrc2 = new InstallableResource("/configB/" + cfgPid,
null, secondData, null, InstallableResource.TYPE_PROPERTIES, 20);
installer.updateResources(URL_SCHEME, new InstallableResource[] {rsrc2}, null);
// get updated config
waitForConfigValue("After updating", cfgPid, "foo", "bla");
// remove config
installer.updateResources(URL_SCHEME, null, new String[] {"/configB/" + cfgPid});
waitForConfiguration("After deleting", cfgPid, false);
}
@Test
public void testInstallUpdateRemoveConfigFactoryResource() throws Exception {
final Dictionary<String, Object> cfgData = new Hashtable<String, Object>();
cfgData.put("foo", "bar");
final String cfgFactoryPid = getClass().getSimpleName() + "." + uniqueID();
final String alias = "alias" + uniqueID();
// install factory config
final InstallableResource rsrc = new InstallableResource("/configA/" + cfgFactoryPid + "-" + alias,
null, cfgData, null, InstallableResource.TYPE_PROPERTIES, 10);
installer.updateResources(URL_SCHEME, new InstallableResource[] {rsrc}, null);
// get factory config
waitForFactoryConfigValue("After installing", cfgFactoryPid, "foo", "bar");
// create second factory configuration with same alias
final Dictionary<String, Object> secondData = new Hashtable<String, Object>();
secondData.put("foo", "bla");
final InstallableResource rsrc2 = new InstallableResource("/configB/" + cfgFactoryPid + "-" + alias,
null, secondData, null, InstallableResource.TYPE_PROPERTIES, 20);
installer.updateResources(URL_SCHEME, new InstallableResource[] {rsrc2}, null);
// get updated factory config
waitForFactoryConfigValue("After updating", cfgFactoryPid, "foo", "bla");
// remove factory config
installer.updateResources(URL_SCHEME, null, new String[] {"/configB/" + cfgFactoryPid + "-" + alias});
waitForFactoryConfigValue("After updating", cfgFactoryPid, "foo", "bar");
}
@Test
public void testInstallUpdateRemoveTemplateConfigFactoryResource() throws Exception {
final Dictionary<String, Object> cfgData = new Hashtable<String, Object>();
cfgData.put("foo", "bar");
cfgData.put(InstallableResource.RESOURCE_IS_TEMPLATE, "true");
final String cfgFactoryPid = getClass().getSimpleName() + "." + uniqueID();
final String alias = "alias" + uniqueID();
// install factory config
final InstallableResource rsrc = new InstallableResource("/configA/" + cfgFactoryPid + "-" + alias,
null, cfgData, null, InstallableResource.TYPE_PROPERTIES, 10);
installer.updateResources(URL_SCHEME, new InstallableResource[] {rsrc}, null);
// get factory config
waitForFactoryConfigValue("After installing", cfgFactoryPid, "foo", "bar");
// create second factory configuration
final Dictionary<String, Object> secondData = new Hashtable<String, Object>();
secondData.put("foo", "bla");
final InstallableResource rsrc2 = new InstallableResource("/configB/" + cfgFactoryPid + "-" + alias,
null, secondData, null, InstallableResource.TYPE_PROPERTIES, 20);
installer.updateResources(URL_SCHEME, new InstallableResource[] {rsrc2}, null);
// get updated factory config
waitForFactoryConfigValue("After updating", cfgFactoryPid, "foo", "bla");
// remove config
installer.updateResources(URL_SCHEME, null, new String[] {"/configB/" + cfgFactoryPid + "-" + alias});
waitForFactoryConfiguration("After deleting", cfgFactoryPid, false);
}
@Test
public void testInstallUpdateRemoveConfig() throws Exception {
final Dictionary<String, Object> cfgData = new Hashtable<String, Object>();
cfgData.put("foo", "bar");
final String cfgPid = getClass().getSimpleName() + "." + uniqueID();
// install config
final InstallableResource rsrc = new InstallableResource("/configA/" + cfgPid,
null, cfgData, null, InstallableResource.TYPE_PROPERTIES, 10);
installer.updateResources(URL_SCHEME, new InstallableResource[] {rsrc}, null);
// get config
final Configuration cfg = waitForConfigValue("After installing", cfgPid, "foo", "bar");
// update configuration
final Dictionary<String, Object> secondData = new Hashtable<String, Object>();
secondData.put("foo", "bla");
cfg.update(secondData);
waitForResource(URL_SCHEME + ":/configA/" + cfgPid, ResourceState.IGNORED);
// get updated config
final Configuration secondCfg = waitForConfigValue("After updating", cfgPid, "foo", "bla");
// remove config
secondCfg.delete();
waitForResource(URL_SCHEME + ":/configA/" + cfgPid, ResourceState.INSTALLED);
waitForConfigValue("After deleting", cfgPid, "foo", "bar");
}
@Test
public void testInstallUpdateRemoveTemplateConfig() throws Exception {
final Dictionary<String, Object> cfgData = new Hashtable<String, Object>();
cfgData.put("foo", "bar");
cfgData.put(InstallableResource.RESOURCE_IS_TEMPLATE, "true");
final String cfgPid = getClass().getSimpleName() + "." + uniqueID();
// install config
final InstallableResource rsrc = new InstallableResource("/configA/" + cfgPid,
null, cfgData, null, InstallableResource.TYPE_PROPERTIES, 10);
installer.updateResources(URL_SCHEME, new InstallableResource[] {rsrc}, null);
// get config
final Configuration cfg = waitForConfigValue("After installing", cfgPid, "foo", "bar");
// update configuration
final Dictionary<String, Object> secondData = new Hashtable<String, Object>();
secondData.put("foo", "bla");
cfg.update(secondData);
waitForResource(URL_SCHEME + ":/configA/" + cfgPid, ResourceState.IGNORED);
// get updated config
final Configuration secondCfg = waitForConfigValue("After updating", cfgPid, "foo", "bla");
// remove config
secondCfg.delete();
sleep(200); // we have to wait here as no state change is happening
waitForResource(URL_SCHEME + ":/configA/" + cfgPid, ResourceState.IGNORED);
waitForConfiguration("After deleting", cfgPid, false);
}
@Test
public void testInstallUpdateRemoveConfigFactory() throws Exception {
final Dictionary<String, Object> cfgData = new Hashtable<String, Object>();
cfgData.put("foo", "bar");
final String cfgFactoryPid = getClass().getSimpleName() + "." + uniqueID();
final String alias = "alias" + uniqueID();
// install factory config
final InstallableResource rsrc = new InstallableResource("/configA/" + cfgFactoryPid + "-" + alias,
null, cfgData, null, InstallableResource.TYPE_PROPERTIES, 10);
installer.updateResources(URL_SCHEME, new InstallableResource[] {rsrc}, null);
// get factory config
final Configuration cfg = waitForFactoryConfigValue("After installing", cfgFactoryPid, "foo", "bar");
// update configuration
final Dictionary<String, Object> secondData = new Hashtable<String, Object>();
secondData.put("foo", "bla");
cfg.update(secondData);
waitForResource(URL_SCHEME + ":/configA/" + cfgFactoryPid + "-" + alias, ResourceState.IGNORED);
// get updated factory config
final Configuration secondCfg = waitForFactoryConfigValue("After updating", cfgFactoryPid, "foo", "bla");
// remove factory config
secondCfg.delete();
waitForResource(URL_SCHEME + ":/configA/" + cfgFactoryPid + "-" + alias, ResourceState.INSTALLED);
waitForFactoryConfigValue("After deleting", cfgFactoryPid, "foo", "bar");
}
@Test
public void testInstallUpdateRemoveTemplateConfigFactory() throws Exception {
final Dictionary<String, Object> cfgData = new Hashtable<String, Object>();
cfgData.put("foo", "bar");
cfgData.put(InstallableResource.RESOURCE_IS_TEMPLATE, "true");
final String cfgFactoryPid = getClass().getSimpleName() + "." + uniqueID();
final String alias = "alias" + uniqueID();
// install factory config
final InstallableResource rsrc = new InstallableResource("/configA/" + cfgFactoryPid + "-" + alias,
null, cfgData, null, InstallableResource.TYPE_PROPERTIES, 10);
installer.updateResources(URL_SCHEME, new InstallableResource[] {rsrc}, null);
// get factory config
final Configuration cfg = waitForFactoryConfigValue("After installing", cfgFactoryPid, "foo", "bar");
// update configuration
final Dictionary<String, Object> secondData = new Hashtable<String, Object>();
secondData.put("foo", "bla");
cfg.update(secondData);
waitForResource(URL_SCHEME + ":/configA/" + cfgFactoryPid + "-" + alias, ResourceState.IGNORED);
// get updated factory config
final Configuration secondCfg = waitForFactoryConfigValue("After updating", cfgFactoryPid, "foo", "bla");
// remove config
secondCfg.delete();
sleep(200); // we have to wait here as no state change is happening
waitForResource(URL_SCHEME + ":/configA/" + cfgFactoryPid + "-" + alias, ResourceState.IGNORED);
waitForFactoryConfiguration("After deleting", cfgFactoryPid, false);
}
@Test
public void testDeferredConfigInstall() throws Exception {
// get config admin bundle and wait for service
final Bundle configAdmin = this.getConfigAdminBundle();
assertNotNull("ConfigAdmin bundle must be found", configAdmin);
waitForConfigAdmin(true);
// check that configuration is not available
final String cfgPid = getClass().getSimpleName() + ".deferred." + uniqueID();
assertNull("Config " + cfgPid + " must not be found before test", findConfiguration(cfgPid));
// create new configuration object
final Dictionary<String, Object> cfgData = new Hashtable<String, Object>();
cfgData.put("foo", "bar");
// Configuration installs must be deferred if ConfigAdmin service is stopped
configAdmin.stop();
waitForConfigAdmin(false);
// add new configuration
final InstallableResource[] rsrc = getInstallableResource(cfgPid, cfgData);
installationEvents = 0;
installer.updateResources(URL_SCHEME, rsrc, null);
waitForInstallationEvents(2);
configAdmin.start();
waitForConfigAdmin(true);
waitForConfiguration("Config must be installed once ConfigurationAdmin restarts",
cfgPid, true);
// Remove config and check
installer.updateResources(URL_SCHEME, null, new String[] {rsrc[0].getId()});
waitForConfiguration("Config must be removed once ConfigurationAdmin restarts",
cfgPid, false);
}
@Test
public void testDeferredConfigRemove() throws Exception {
final AtomicInteger transformerCount = new AtomicInteger();
final ServiceTracker<ResourceTransformer, ResourceTransformer> st = new ServiceTracker<ResourceTransformer, ResourceTransformer>(bundleContext,
ResourceTransformer.class, new ServiceTrackerCustomizer<ResourceTransformer, ResourceTransformer>() {
@Override
public void removedService(ServiceReference<ResourceTransformer> reference, ResourceTransformer service) {
bundleContext.ungetService(reference);
transformerCount.decrementAndGet();
}
@Override
public void modifiedService(ServiceReference<ResourceTransformer> reference, ResourceTransformer service) {
}
@Override
public ResourceTransformer addingService(ServiceReference<ResourceTransformer> reference) {
transformerCount.incrementAndGet();
return bundleContext.getService(reference);
}
});
st.open();
final AtomicInteger expectedCount = new AtomicInteger();
final Condition cond = new Condition() {
@Override
boolean isTrue() throws Exception {
return transformerCount.get() == expectedCount.get();
}
@Override
String additionalInfo() {
return "Resource transformer count is " + String.valueOf(transformerCount.get()) +
", should be " + String.valueOf(expectedCount.get());
}
};
// get config admin bundle and wait for service
final Bundle configAdmin = this.getConfigAdminBundle();
assertNotNull("ConfigAdmin bundle must be found", configAdmin);
waitForConfigAdmin(true);
// when everything is up and running, we have two factories
expectedCount.set(2);
waitForCondition(null, cond);
// check that configuration is not available
final String cfgPid = getClass().getSimpleName() + ".deferred." + uniqueID();
assertNull("Config " + cfgPid + " must not be found before test", findConfiguration(cfgPid));
// create and install new configuration object, verify
final Dictionary<String, Object> cfgData = new Hashtable<String, Object>();
cfgData.put("foo", "bar");
final InstallableResource[] rsrc = getInstallableResource(cfgPid, cfgData);
installer.updateResources(URL_SCHEME, rsrc, null);
waitForConfiguration("Config must be installed before stopping ConfigurationAdmin",
cfgPid, true);
this.waitForResource(URL_SCHEME + ":" + rsrc[0].getId(), ResourceState.INSTALLED);
// Configuration uninstalls must be deferred if ConfigAdmin service is stopped
configAdmin.stop();
waitForConfigAdmin(false);
// only bundle transformer
expectedCount.set(1);
waitForCondition(null, cond);
// remove configuration
installationEvents = 0;
installer.updateResources(URL_SCHEME, null, new String[] {rsrc[0].getId()});
waitForInstallationEvents(2);
this.waitForResource(URL_SCHEME + ":" + rsrc[0].getId(), ResourceState.UNINSTALL);
configAdmin.start();
waitForConfigAdmin(true);
// when everything is up and running, we have two transformers again
expectedCount.set(2);
waitForCondition(null, cond);
waitForConfiguration("Config must be removed once ConfigurationAdmin restarts",
cfgPid, false);
st.close();
}
@Test
public void testReinstallSameConfig() throws Exception {
final Dictionary<String, Object> cfgData = new Hashtable<String, Object>();
cfgData.put("foo", "bar");
final String cfgPid = getClass().getSimpleName() + ".reinstall." + uniqueID();
assertNull("Config " + cfgPid + " must not be found before test", findConfiguration(cfgPid));
// Install config directly
ConfigurationAdmin ca = waitForConfigAdmin(true);
final Configuration c = ca.getConfiguration(cfgPid);
c.update(cfgData);
waitForConfigValue("After manual installation", cfgPid, "foo", "bar");
waitForCondition("Expected one ConfigurationEvents since beginning of test", new ConfigCondition(cfgPid, 1));
installer.updateResources(URL_SCHEME, getInstallableResource(cfgPid, cfgData), null);
// Reinstalling with a change must be executed
cfgData.put("foo", "changed");
installer.updateResources(URL_SCHEME, getInstallableResource(cfgPid, cfgData), null);
waitForConfigValue("After changing value", cfgPid, "foo", "changed");
waitForCondition("Expected two ConfigurationEvents since beginning of test", new ConfigCondition(cfgPid, 2));
}
protected final class ConfigCondition extends Condition {
private final String pid;
private final int maxCount;
public ConfigCondition(final String pid, final int count) {
this.pid = pid;
this.maxCount = count;
}
@Override
boolean isTrue() throws Exception {
int count = 0;
synchronized ( events ) {
for(final ConfigurationEvent e : events) {
if ( pid.equals(e.getPid()) ) {
count++;
}
}
}
return count == maxCount;
}
@Override
String additionalInfo() {
final StringBuilder sb = new StringBuilder("Expected ");
sb.append(maxCount);
sb.append(" events for ");
sb.append(pid);
sb.append(". Received events: [");
boolean first = true;
synchronized ( events ) {
for(final ConfigurationEvent e : events) {
if ( !first) {
sb.append(", ");
}
first = false;
sb.append(e.getPid());
sb.append(':');
sb.append(e.getType());
}
}
sb.append("]");
return sb.toString();
}
}
private void waitForInstallationEvents(final int howMany) throws Exception {
final Condition c = new Condition() {
@Override
boolean isTrue() throws Exception {
return installationEvents >= howMany;
}
};
waitForCondition("Wait for " + howMany + " installation events", c);
}
}