FELIX-3780 - allow DA to be configured by ConfigAdmin:
- register the DA implementation also as managed service with PID
"org.apache.felix.deploymentadmin";
- it accepts a single property "stopUnaffectedBundle" which should
be "true" or "false";
- when no configuration is supplied, the fallback is to use the old
framework/system property:
"org.apache.felix.deploymentadmin.stopunaffectedbundle".
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1588290 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/Activator.java b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/Activator.java
index 7013e78..217a84a 100644
--- a/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/Activator.java
+++ b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/Activator.java
@@ -18,9 +18,13 @@
*/
package org.apache.felix.deploymentadmin;
+import java.util.Dictionary;
+import java.util.Hashtable;
+
import org.apache.felix.dm.DependencyActivatorBase;
import org.apache.felix.dm.DependencyManager;
import org.osgi.framework.BundleContext;
+import org.osgi.service.cm.ManagedService;
import org.osgi.service.deploymentadmin.DeploymentAdmin;
import org.osgi.service.event.EventAdmin;
import org.osgi.service.log.LogService;
@@ -34,8 +38,13 @@
public class Activator extends DependencyActivatorBase {
public void init(BundleContext context, DependencyManager manager) throws Exception {
+ String[] ifaces = { DeploymentAdmin.class.getName(), ManagedService.class.getName() };
+
+ Dictionary props = new Hashtable();
+ props.put(Constants.SERVICE_PID, DeploymentAdminImpl.PID);
+
manager.add(createComponent()
- .setInterface(DeploymentAdmin.class.getName(), null)
+ .setInterface(ifaces, props)
.setImplementation(DeploymentAdminImpl.class)
.add(createServiceDependency()
.setService(PackageAdmin.class)
diff --git a/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/DeploymentAdminImpl.java b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/DeploymentAdminImpl.java
index 4a3b497..70ff456 100644
--- a/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/DeploymentAdminImpl.java
+++ b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/DeploymentAdminImpl.java
@@ -48,6 +48,8 @@
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Version;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.cm.ManagedService;
import org.osgi.service.deploymentadmin.DeploymentAdmin;
import org.osgi.service.deploymentadmin.DeploymentException;
import org.osgi.service.deploymentadmin.DeploymentPackage;
@@ -56,7 +58,9 @@
import org.osgi.service.log.LogService;
import org.osgi.service.packageadmin.PackageAdmin;
-public class DeploymentAdminImpl implements DeploymentAdmin {
+public class DeploymentAdminImpl implements DeploymentAdmin, ManagedService {
+ /** Configuration PID used to dynamically configure DA at runtime. */
+ public static final String PID = "org.apache.felix.deploymentadmin";
public static final String PACKAGE_DIR = "packages";
public static final String TEMP_DIR = "temp";
@@ -66,19 +70,31 @@
public static final String TEMP_POSTFIX = "";
private static final long TIMEOUT = 10000;
+ /** Configuration key used for dynamic configuration of DA. */
+ public static final String KEY_STOP_UNAFFECTED_BUNDLE = "stopUnaffectedBundle";
private volatile BundleContext m_context; /* will be injected by dependencymanager */
private volatile PackageAdmin m_packageAdmin; /* will be injected by dependencymanager */
private volatile EventAdmin m_eventAdmin; /* will be injected by dependencymanager */
private volatile LogService m_log; /* will be injected by dependencymanager */
private volatile DeploymentSessionImpl m_session = null;
+ private volatile Boolean m_stopUnaffectedBundles = null;
+
private final Map m_packages = new HashMap();
private final Semaphore m_semaphore = new Semaphore();
/**
- * Create new instance of this <code>DeploymentAdmin</code>.
+ * Creates a new {@link DeploymentAdminImpl} instance.
*/
public DeploymentAdminImpl() {
+ // Nop
+ }
+
+ /**
+ * Creates a new {@link DeploymentAdminImpl} instance.
+ */
+ DeploymentAdminImpl(BundleContext context) {
+ m_context = context;
}
public boolean cancel() {
@@ -350,6 +366,37 @@
}
}
+ public void updated(Dictionary properties) throws ConfigurationException {
+ Boolean stopUnaffectedBundles = null;
+ if (properties != null) {
+ Object value = properties.get(KEY_STOP_UNAFFECTED_BUNDLE);
+ if (value == null || !(value instanceof String || value instanceof Boolean)) {
+ throw new ConfigurationException(KEY_STOP_UNAFFECTED_BUNDLE, "missing value!");
+ }
+
+ if (value instanceof Boolean) {
+ stopUnaffectedBundles = (Boolean) value;
+ } else {
+ stopUnaffectedBundles = Boolean.valueOf(value.toString());
+ }
+ }
+ m_stopUnaffectedBundles = stopUnaffectedBundles;
+ }
+
+ public boolean isStopUnaffectedBundles() {
+ Boolean stopUnaffectedBundles = m_stopUnaffectedBundles;
+ if (stopUnaffectedBundles == null) {
+ String prop = m_context.getProperty(PID + "." + KEY_STOP_UNAFFECTED_BUNDLE);
+ if (prop == null) {
+ prop = m_context.getProperty(PID + "." + KEY_STOP_UNAFFECTED_BUNDLE.toLowerCase());
+ }
+ if (prop != null) {
+ stopUnaffectedBundles = Boolean.valueOf(prop);
+ }
+ }
+ return (stopUnaffectedBundles == null) ? true : stopUnaffectedBundles.booleanValue();
+ }
+
private List createInstallCommandChain() {
List commandChain = new ArrayList();
diff --git a/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/DeploymentSessionImpl.java b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/DeploymentSessionImpl.java
index 6c89186..425a7b1 100644
--- a/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/DeploymentSessionImpl.java
+++ b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/DeploymentSessionImpl.java
@@ -196,4 +196,8 @@
public AbstractDeploymentPackage getSourceAbstractDeploymentPackage() {
return m_source;
}
+
+ public boolean isStopUnaffectedBundles() {
+ return m_admin.isStopUnaffectedBundles();
+ }
}
\ No newline at end of file
diff --git a/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/StopBundleCommand.java b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/StopBundleCommand.java
index fa75d90..2d9d423 100644
--- a/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/StopBundleCommand.java
+++ b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/StopBundleCommand.java
@@ -41,8 +41,6 @@
public class StopBundleCommand extends Command {
protected void doExecute(DeploymentSessionImpl session) throws Exception {
- String stopUnaffectedBundle = System.getProperty("org.apache.felix.deploymentadmin.stopunaffectedbundle", "true");
-
LogService log = session.getLog();
AbstractDeploymentPackage target = session.getTargetAbstractDeploymentPackage();
@@ -54,7 +52,7 @@
String symbolicName = bundleInfos[i].getSymbolicName();
Bundle bundle = target.getBundle(symbolicName);
if (bundle != null) {
- if ("false".equalsIgnoreCase(stopUnaffectedBundle) && omitBundleStop(session, symbolicName)) {
+ if (omitBundleStop(session, symbolicName)) {
continue;
}
if (isFragmentBundle(bundle)) {
@@ -87,7 +85,9 @@
* deployment package. Returns <code>false</code> otherwise.
*/
private boolean omitBundleStop(DeploymentSessionImpl session, String symbolicName) {
- boolean result = false;
+ boolean stopUnaffectedBundle = session.isStopUnaffectedBundles();
+
+ boolean result = stopUnaffectedBundle;
BundleInfoImpl sourceBundleInfo = session.getSourceAbstractDeploymentPackage().getBundleInfoByName(symbolicName);
BundleInfoImpl targetBundleInfo = session.getTargetAbstractDeploymentPackage().getBundleInfoByName(symbolicName);
boolean fixPackageMissing = sourceBundleInfo != null && sourceBundleInfo.isMissing();
diff --git a/deploymentadmin/deploymentadmin/src/test/java/org/apache/felix/deploymentadmin/DeploymentAdminImplTest.java b/deploymentadmin/deploymentadmin/src/test/java/org/apache/felix/deploymentadmin/DeploymentAdminImplTest.java
new file mode 100644
index 0000000..4c9c90b
--- /dev/null
+++ b/deploymentadmin/deploymentadmin/src/test/java/org/apache/felix/deploymentadmin/DeploymentAdminImplTest.java
@@ -0,0 +1,168 @@
+/*
+ * 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.felix.deploymentadmin;
+
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Map;
+
+import junit.framework.TestCase;
+
+import org.mockito.Matchers;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.cm.ConfigurationException;
+
+/**
+ * Test cases for {@link DeploymentAdminImpl}.
+ */
+public class DeploymentAdminImplTest extends TestCase
+{
+ private static final String CONF_KEY = DeploymentAdminImpl.KEY_STOP_UNAFFECTED_BUNDLE;
+ private static final String SYS_PROP = DeploymentAdminImpl.PID + "." + CONF_KEY.toLowerCase();
+ private static final String SYS_PROP_LOWERCASE = DeploymentAdminImpl.PID + "." + CONF_KEY.toLowerCase();
+
+ private final Map m_fwProperties = new HashMap();
+
+ /**
+ * Tests the configuration values of {@link DeploymentAdminImpl} without any explicit configuration.
+ */
+ public void testDefaultConfigurationOk()
+ {
+ DeploymentAdminImpl da = createDeploymentAdmin();
+
+ assertTrue(da.isStopUnaffectedBundles());
+ }
+
+ /**
+ * Tests the configuration values of {@link DeploymentAdminImpl} without any explicit configuration.
+ */
+ public void testExplicitConfigurationOk() throws ConfigurationException
+ {
+ Dictionary dict = new Hashtable();
+ dict.put(CONF_KEY, "false");
+
+ DeploymentAdminImpl da = createDeploymentAdmin();
+ da.updated(dict);
+
+ // Should use the explicit configured value...
+ assertFalse(da.isStopUnaffectedBundles());
+
+ da.updated(null);
+
+ // Should use the system wide value...
+ assertTrue(da.isStopUnaffectedBundles());
+ }
+
+ /**
+ * Tests that an explicit configuration cannot miss any properties.
+ */
+ public void testExplicitConfigurationWithMissingValueFail() throws ConfigurationException
+ {
+ Dictionary dict = new Hashtable();
+
+ DeploymentAdminImpl da = createDeploymentAdmin();
+ try
+ {
+ da.updated(dict);
+ fail("ConfigurationException expected!");
+ }
+ catch (ConfigurationException e)
+ {
+ assertEquals(CONF_KEY, e.getProperty());
+ }
+ }
+
+ /**
+ * Tests the configuration values of {@link DeploymentAdminImpl} without any explicit configuration.
+ */
+ public void testFrameworkConfigurationOk()
+ {
+ m_fwProperties.put(SYS_PROP, "false");
+
+ DeploymentAdminImpl da = createDeploymentAdmin();
+
+ assertFalse(da.isStopUnaffectedBundles());
+ }
+
+ /**
+ * Tests the configuration values of {@link DeploymentAdminImpl} without any explicit configuration.
+ */
+ public void testSystemConfigurationOk()
+ {
+ System.setProperty(SYS_PROP, "false");
+
+ try
+ {
+ DeploymentAdminImpl da = createDeploymentAdmin();
+
+ assertFalse(da.isStopUnaffectedBundles());
+ }
+ finally
+ {
+ System.clearProperty(SYS_PROP);
+ }
+
+ System.setProperty(SYS_PROP_LOWERCASE, "false");
+
+ try
+ {
+ DeploymentAdminImpl da = createDeploymentAdmin();
+
+ assertFalse(da.isStopUnaffectedBundles());
+ }
+ finally
+ {
+ System.clearProperty(SYS_PROP_LOWERCASE);
+ }
+ }
+
+ protected void setUp() throws Exception
+ {
+ m_fwProperties.clear();
+ }
+
+ private DeploymentAdminImpl createDeploymentAdmin()
+ {
+ return new DeploymentAdminImpl(createMockBundleContext());
+ }
+
+ private BundleContext createMockBundleContext()
+ {
+ BundleContext result = (BundleContext) Mockito.mock(BundleContext.class);
+ Mockito.when(result.getProperty(Matchers.anyString())).thenAnswer(new Answer()
+ {
+ public Object answer(InvocationOnMock invocation) throws Throwable
+ {
+ String prop = (String) invocation.getArguments()[0];
+
+ Object result = m_fwProperties.get(prop);
+ if (result == null)
+ {
+ result = System.getProperty(prop);
+ }
+ return result;
+ }
+ });
+ return result;
+ }
+}
diff --git a/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/BaseIntegrationTest.java b/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/BaseIntegrationTest.java
index b3e1bc5..7fac396 100644
--- a/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/BaseIntegrationTest.java
+++ b/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/BaseIntegrationTest.java
@@ -48,6 +48,7 @@
import org.osgi.framework.FrameworkListener;
import org.osgi.framework.Version;
import org.osgi.framework.wiring.FrameworkWiring;
+import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.deploymentadmin.DeploymentAdmin;
import org.osgi.service.deploymentadmin.DeploymentException;
import org.osgi.service.deploymentadmin.DeploymentPackage;
@@ -66,7 +67,9 @@
@Inject
protected volatile BundleContext m_context;
@Inject
- private volatile DeploymentAdmin m_deploymentAdmin;
+ protected volatile DeploymentAdmin m_deploymentAdmin;
+ @Inject
+ protected volatile ConfigurationAdmin m_configAdmin;
protected volatile AtomicInteger m_gate = new AtomicInteger(0);
protected volatile String m_testBundleBasePath;
diff --git a/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/DeploymentAdminTest.java b/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/DeploymentAdminTest.java
index adfc51a..fa03aba 100644
--- a/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/DeploymentAdminTest.java
+++ b/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/DeploymentAdminTest.java
@@ -21,13 +21,18 @@
import static org.osgi.service.deploymentadmin.DeploymentException.CODE_BUNDLE_NAME_ERROR;
import static org.osgi.service.deploymentadmin.DeploymentException.CODE_OTHER_ERROR;
+import java.util.Dictionary;
+import java.util.Hashtable;
+
import org.apache.felix.deploymentadmin.itest.util.DeploymentPackageBuilder;
import org.apache.felix.deploymentadmin.itest.util.DeploymentPackageBuilder.JarManifestManipulatingFilter;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.ops4j.pax.exam.junit.PaxExam;
+import org.osgi.service.cm.Configuration;
import org.osgi.service.deploymentadmin.DeploymentAdmin;
import org.osgi.service.deploymentadmin.DeploymentException;
+import org.osgi.service.deploymentadmin.DeploymentPackage;
/**
* Generic tests for {@link DeploymentAdmin}.
@@ -35,6 +40,52 @@
@RunWith(PaxExam.class)
public class DeploymentAdminTest extends BaseIntegrationTest {
+ /**
+ * Tests that we can update the configuration of {@link DeploymentAdmin} at runtime. Based on the test case for FELIX-4184, see
+ * {@link org.apache.felix.deploymentadmin.itest.InstallFixPackageTest#testInstallAndUpdateImplementationBundleWithSeparateAPIBundle_FELIX4184()}
+ */
+ @Test
+ public void testUpdateConfigurationOk() throws Exception
+ {
+ Dictionary props = new Hashtable();
+ props.put("stopUnaffectedBundle", Boolean.FALSE);
+
+ Configuration config = m_configAdmin.getConfiguration("org.apache.felix.deploymentadmin", null);
+ config.update(props);
+
+ Thread.sleep(100);
+
+ // This test case will only work if stopUnaffectedBundle is set to 'false'...
+ try {
+ // first, install a deployment package with implementation and api bundles in version 1.0.0
+ DeploymentPackageBuilder dpBuilder = createDeploymentPackageBuilder("a", "1.0.0");
+ dpBuilder.add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundleimpl1", "bundleimpl1", "1.0.0")));
+ dpBuilder.add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundleapi1", "bundleapi1", "1.0.0")));
+
+ DeploymentPackage dp1 = installDeploymentPackage(dpBuilder);
+ assertNotNull("No deployment package returned?!", dp1);
+
+ assertEquals("Expected a single deployment package?!", 1, countDeploymentPackages());
+
+ // then, install a fix package with implementation and api bundles in version 2.0.0
+ dpBuilder = createDeploymentPackageBuilder("a", "2.0.0").setFixPackage("[1.0.0,2.0.0]");
+ dpBuilder.add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundleimpl2", "bundleimpl2", "2.0.0")));
+ dpBuilder.add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundleapi2", "bundleapi2", "2.0.0")));
+
+ DeploymentPackage dp2 = installDeploymentPackage(dpBuilder);
+ assertNotNull("No deployment package returned?!", dp2);
+
+ awaitRefreshPackagesEvent();
+
+ assertBundleExists(getSymbolicName("bundleimpl"), "2.0.0");
+ assertBundleExists(getSymbolicName("bundleapi"), "2.0.0");
+ assertBundleNotExists(getSymbolicName("bundleimpl"), "1.0.0");
+ assertBundleNotExists(getSymbolicName("bundleapi"), "1.0.0");
+ } finally {
+ config.delete();
+ }
+ }
+
@Test
public void testBundleSymbolicNameMustMatchManifestEntry() throws Exception {
DeploymentPackageBuilder dpBuilder = createNewDeploymentPackageBuilder("1.0.0");