You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@karaf.apache.org by gn...@apache.org on 2014/04/30 20:04:53 UTC
[1/7] git commit: [KARAF-2888] Add a few unit tests for the deployer,
more needed
Repository: karaf
Updated Branches:
refs/heads/master 703c62f97 -> bd9167729
[KARAF-2888] Add a few unit tests for the deployer, more needed
Project: http://git-wip-us.apache.org/repos/asf/karaf/repo
Commit: http://git-wip-us.apache.org/repos/asf/karaf/commit/e334faed
Tree: http://git-wip-us.apache.org/repos/asf/karaf/tree/e334faed
Diff: http://git-wip-us.apache.org/repos/asf/karaf/diff/e334faed
Branch: refs/heads/master
Commit: e334faed45eff1b7a6b961dac809aa006123b238
Parents: c082817
Author: Guillaume Nodet <gn...@gmail.com>
Authored: Wed Apr 30 18:02:58 2014 +0200
Committer: Guillaume Nodet <gn...@gmail.com>
Committed: Wed Apr 30 20:04:27 2014 +0200
----------------------------------------------------------------------
.../internal/resolver/ResourceBuilder.java | 13 +-
.../internal/resolver/ResourceImpl.java | 4 +-
.../features/internal/region/SubsystemTest.java | 75 +----
.../features/internal/service/DeployerTest.java | 299 +++++++++++++++++++
.../features/internal/support/TestBundle.java | 262 ++++++++++++++++
.../internal/support/TestDownloadManager.java | 88 ++++++
.../features/internal/service/data1/a100.mf | 6 +
.../features/internal/service/data1/a101.mf | 6 +
.../internal/service/data1/features.xml | 29 ++
9 files changed, 710 insertions(+), 72 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/karaf/blob/e334faed/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceBuilder.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceBuilder.java b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceBuilder.java
index 9ad1012..2c17732 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceBuilder.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceBuilder.java
@@ -60,7 +60,10 @@ public final class ResourceBuilder {
}
public static ResourceImpl build(String uri, Map<String, String> headerMap) throws BundleException {
+ return build(new ResourceImpl(), uri, headerMap);
+ }
+ public static ResourceImpl build(ResourceImpl resource, String uri, Map<String, String> headerMap) throws BundleException {
// Verify that only manifest version 2 is specified.
String manifestVersion = getManifestVersion(headerMap);
if (manifestVersion == null || !manifestVersion.equals("2")) {
@@ -89,7 +92,15 @@ public final class ResourceBuilder {
// Now that we have symbolic name and version, create the resource
String type = headerMap.get(Constants.FRAGMENT_HOST) == null ? IdentityNamespace.TYPE_BUNDLE : IdentityNamespace.TYPE_FRAGMENT;
- ResourceImpl resource = new ResourceImpl(bundleSymbolicName, type, bundleVersion);
+ {
+ Map<String, String> dirs = new HashMap<>();
+ Map<String, Object> attrs = new HashMap<>();
+ attrs.put(IdentityNamespace.IDENTITY_NAMESPACE, bundleSymbolicName);
+ attrs.put(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE, type);
+ attrs.put(IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE, bundleVersion);
+ CapabilityImpl identity = new CapabilityImpl(resource, IdentityNamespace.IDENTITY_NAMESPACE, dirs, attrs);
+ resource.addCapability(identity);
+ }
if (uri != null) {
Map<String, Object> attrs = new HashMap<>();
attrs.put(ContentNamespace.CAPABILITY_URL_ATTRIBUTE, uri);
http://git-wip-us.apache.org/repos/asf/karaf/blob/e334faed/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceImpl.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceImpl.java b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceImpl.java
index 99ffb52..756c440 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceImpl.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceImpl.java
@@ -31,8 +31,8 @@ import org.osgi.resource.Resource;
*/
public class ResourceImpl implements Resource {
- private final List<Capability> caps;
- private final List<Requirement> reqs;
+ protected final List<Capability> caps;
+ protected final List<Requirement> reqs;
/**
* CAUTION: This constructor does not ensure that the resource
http://git-wip-us.apache.org/repos/asf/karaf/blob/e334faed/features/core/src/test/java/org/apache/karaf/features/internal/region/SubsystemTest.java
----------------------------------------------------------------------
diff --git a/features/core/src/test/java/org/apache/karaf/features/internal/region/SubsystemTest.java b/features/core/src/test/java/org/apache/karaf/features/internal/region/SubsystemTest.java
index dc371a1..42c66d7 100644
--- a/features/core/src/test/java/org/apache/karaf/features/internal/region/SubsystemTest.java
+++ b/features/core/src/test/java/org/apache/karaf/features/internal/region/SubsystemTest.java
@@ -16,11 +16,6 @@
*/
package org.apache.karaf.features.internal.region;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -29,13 +24,10 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
-import java.util.jar.JarOutputStream;
-import java.util.jar.Manifest;
import org.apache.karaf.features.FeaturesService;
-import org.apache.karaf.features.internal.download.StreamProvider;
import org.apache.karaf.features.internal.service.RepositoryImpl;
-import org.apache.karaf.features.internal.download.simple.SimpleDownloader;
+import org.apache.karaf.features.internal.support.TestDownloadManager;
import org.junit.Test;
import org.osgi.framework.namespace.IdentityNamespace;
import org.osgi.framework.wiring.BundleRevision;
@@ -61,7 +53,7 @@ public class SubsystemTest {
addToMapSet(expected, "root", "c/1.0.0");
addToMapSet(expected, "root/apps1", "b/1.0.0");
- SubsystemResolver resolver = new SubsystemResolver(new TestDownloadManager("data1"));
+ SubsystemResolver resolver = new SubsystemResolver(new TestDownloadManager(getClass(), "data1"));
resolver.resolve(Arrays.asList(repo.getFeatures()),
features,
Collections.<String, Set<BundleRevision>>emptyMap(),
@@ -92,7 +84,7 @@ public class SubsystemTest {
addToMapSet(expected, "root/apps2", "c/1.0.0");
addToMapSet(expected, "root/apps2#f1", "a/1.0.0");
- SubsystemResolver resolver = new SubsystemResolver(new TestDownloadManager("data2"));
+ SubsystemResolver resolver = new SubsystemResolver(new TestDownloadManager(getClass(), "data2"));
resolver.resolve(Arrays.asList(repo.getFeatures()),
features,
Collections.<String, Set<BundleRevision>>emptyMap(),
@@ -113,7 +105,7 @@ public class SubsystemTest {
Map<String, Set<String>> expected = new HashMap<String, Set<String>>();
addToMapSet(expected, "root/apps1", "a/1.0.1");
- SubsystemResolver resolver = new SubsystemResolver(new TestDownloadManager("data3"));
+ SubsystemResolver resolver = new SubsystemResolver(new TestDownloadManager(getClass(), "data3"));
resolver.resolve(Arrays.asList(repo.getFeatures()),
features,
Collections.<String, Set<BundleRevision>>emptyMap(),
@@ -133,7 +125,7 @@ public class SubsystemTest {
Map<String, Set<String>> expected = new HashMap<String, Set<String>>();
addToMapSet(expected, "root/apps1", "a/1.0.0");
- SubsystemResolver resolver = new SubsystemResolver(new TestDownloadManager("data4"));
+ SubsystemResolver resolver = new SubsystemResolver(new TestDownloadManager(getClass(), "data4"));
resolver.resolve(Arrays.asList(repo.getFeatures()),
features,
Collections.<String, Set<BundleRevision>>emptyMap(),
@@ -155,7 +147,7 @@ public class SubsystemTest {
addToMapSet(expected, "root/apps1", "a/1.0.0");
addToMapSet(expected, "root/apps1", "b/1.0.0");
- SubsystemResolver resolver = new SubsystemResolver(new TestDownloadManager("data4"));
+ SubsystemResolver resolver = new SubsystemResolver(new TestDownloadManager(getClass(), "data4"));
resolver.resolve(Arrays.asList(repo.getFeatures()),
features,
Collections.<String, Set<BundleRevision>>emptyMap(),
@@ -223,59 +215,4 @@ public class SubsystemTest {
+ cap.getAttributes().get(IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE);
}
- class TestDownloadManager extends SimpleDownloader {
-
- private final String dir;
-
- TestDownloadManager(String dir) {
- this.dir = dir;
- }
-
- @Override
- protected StreamProvider createProvider(String location) throws MalformedURLException {
- return new TestProvider(location);
- }
-
- class TestProvider implements StreamProvider {
- private final IOException exception;
- private final Map<String, String> headers;
- private final byte[] data;
-
- TestProvider(String location) {
- Map<String, String> headers = null;
- byte[] data = null;
- IOException exception = null;
- try {
- Manifest man = new Manifest(getClass().getResourceAsStream(dir +"/" + location + ".mf"));
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- JarOutputStream jos = new JarOutputStream(baos, man);
- jos.close();
- data = baos.toByteArray();
- headers = new HashMap<String, String>();
- for (Map.Entry attr : man.getMainAttributes().entrySet()) {
- headers.put(attr.getKey().toString(), attr.getValue().toString());
- }
- } catch (IOException e) {
- exception = e;
- }
- this.headers = headers;
- this.data = data;
- this.exception = exception;
- }
-
- @Override
- public InputStream open() throws IOException {
- if (exception != null)
- throw exception;
- return new ByteArrayInputStream(data);
- }
-
- @Override
- public Map<String, String> getMetadata() throws IOException {
- if (exception != null)
- throw exception;
- return headers;
- }
- }
- }
}
http://git-wip-us.apache.org/repos/asf/karaf/blob/e334faed/features/core/src/test/java/org/apache/karaf/features/internal/service/DeployerTest.java
----------------------------------------------------------------------
diff --git a/features/core/src/test/java/org/apache/karaf/features/internal/service/DeployerTest.java b/features/core/src/test/java/org/apache/karaf/features/internal/service/DeployerTest.java
new file mode 100644
index 0000000..42c7c49
--- /dev/null
+++ b/features/core/src/test/java/org/apache/karaf/features/internal/service/DeployerTest.java
@@ -0,0 +1,299 @@
+/*
+ * 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.karaf.features.internal.service;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.Set;
+import java.util.jar.Manifest;
+
+import org.apache.felix.utils.version.VersionRange;
+import org.apache.karaf.features.Feature;
+import org.apache.karaf.features.FeatureEvent;
+import org.apache.karaf.features.internal.support.TestBundle;
+import org.apache.karaf.features.internal.support.TestDownloadManager;
+import org.easymock.EasyMock;
+import org.easymock.IAnswer;
+import org.easymock.IArgumentMatcher;
+import org.junit.Test;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleException;
+
+import static org.apache.karaf.features.FeaturesService.*;
+import static org.apache.karaf.features.internal.util.MapUtils.addToMapSet;
+import static org.easymock.EasyMock.anyInt;
+import static org.easymock.EasyMock.anyObject;
+
+public class DeployerTest {
+
+ @Test
+ public void testInstallSimpleFeature() throws Exception {
+
+ String dataDir = "data1";
+
+ TestDownloadManager manager = new TestDownloadManager(getClass(), dataDir);
+
+ RepositoryImpl repo = new RepositoryImpl(getClass().getResource(dataDir + "/features.xml").toURI());
+ repo.load(true);
+ Feature f100 = repo.getFeatures()[0];
+ Feature f101 = repo.getFeatures()[1];
+
+ Deployer.DeployCallback callback = EasyMock.createMock(Deployer.DeployCallback.class);
+ Deployer deployer = new Deployer(manager, callback);
+
+ callback.print(EasyMock.anyString(), EasyMock.anyBoolean());
+ EasyMock.expectLastCall().anyTimes();
+ callback.replaceDigraph(EasyMock.<Map<String, Map<String, Map<String, Set<String>>>>>anyObject(),
+ EasyMock.<Map<String, Set<Long>>>anyObject());
+ EasyMock.expectLastCall();
+ callback.saveState(EasyMock.<State>anyObject());
+ EasyMock.expectLastCall();
+ callback.installFeatureConfigs(f100);
+ EasyMock.expectLastCall();
+ callback.resolveBundles(EasyMock.<Set<Bundle>>anyObject());
+ EasyMock.expectLastCall();
+ callback.callListeners(EasyMock.<FeatureEvent>anyObject());
+ EasyMock.expectLastCall();
+
+ Bundle bundleA = createTestBundle(1, Bundle.ACTIVE, dataDir, "a100");
+ EasyMock.expect(callback.installBundle(EasyMock.eq(ROOT_REGION), EasyMock.eq("a100"), EasyMock.<InputStream>anyObject()))
+ .andReturn(bundleA);
+
+ EasyMock.replay(callback);
+
+ Deployer.DeploymentState dstate = new Deployer.DeploymentState();
+ dstate.state = new State();
+ dstate.bundles = new HashMap<>();
+ dstate.bundlesPerRegion = new HashMap<>();
+ dstate.features = new HashMap<>();
+ dstate.features.put(f100.getId(), f100);
+ dstate.features.put(f101.getId(), f101);
+ dstate.filtersPerRegion = new HashMap<>();
+ dstate.filtersPerRegion.put(ROOT_REGION, new HashMap<String, Map<String, Set<String>>>());
+
+ Deployer.DeploymentRequest request = new Deployer.DeploymentRequest();
+ request.bundleUpdateRange = DEFAULT_BUNDLE_UPDATE_RANGE;
+ request.featureResolutionRange = DEFAULT_FEATURE_RESOLUTION_RANGE;
+ request.globalRepository = null;
+ request.options = EnumSet.noneOf(Option.class);
+ request.overrides = Collections.emptySet();
+ request.stateChanges = Collections.emptyMap();
+ request.updateSnaphots = UPDATE_SNAPSHOTS_NONE;
+ request.requestedFeatures = new HashMap<>();
+ addToMapSet(request.requestedFeatures, ROOT_REGION, f100.getName() + "/" + new VersionRange(f100.getVersion(), true));
+
+ deployer.deploy(dstate, request);
+
+ EasyMock.verify(callback);
+ }
+
+ @Test
+ public void testUpdateSimpleFeature() throws Exception {
+
+ final String dataDir = "data1";
+
+ TestDownloadManager manager = new TestDownloadManager(getClass(), dataDir);
+
+ RepositoryImpl repo = new RepositoryImpl(getClass().getResource(dataDir + "/features.xml").toURI());
+ repo.load(true);
+ Feature f100 = repo.getFeatures()[0];
+ Feature f101 = repo.getFeatures()[1];
+
+ Deployer.DeployCallback callback = EasyMock.createMock(Deployer.DeployCallback.class);
+ Deployer deployer = new Deployer(manager, callback);
+
+ final TestBundle bundleA = createTestBundle(1L, Bundle.ACTIVE, dataDir, "a100");
+
+ callback.print(EasyMock.anyString(), EasyMock.anyBoolean());
+ EasyMock.expectLastCall().anyTimes();
+
+ callback.stopBundle(EasyMock.eq(bundleA), anyInt());
+ EasyMock.expectLastCall().andStubAnswer(new IAnswer<Object>() {
+ @Override
+ public Object answer() throws Throwable {
+ bundleA.state = Bundle.RESOLVED;
+ return null;
+ }
+ });
+ callback.updateBundle(EasyMock.eq(bundleA), EasyMock.<InputStream>anyObject());
+ EasyMock.expectLastCall().andStubAnswer(new IAnswer<Object>() {
+ @Override
+ public Object answer() throws Throwable {
+ URL loc = getClass().getResource(dataDir + "/" + "a101" + ".mf");
+ Manifest man = new Manifest(loc.openStream());
+ Hashtable<String, String> headers = new Hashtable<>();
+ for (Map.Entry attr : man.getMainAttributes().entrySet()) {
+ headers.put(attr.getKey().toString(), attr.getValue().toString());
+ }
+ bundleA.update(headers);
+ return null;
+ }
+ });
+ callback.startBundle(EasyMock.eq(bundleA));
+ EasyMock.expectLastCall();
+
+ callback.replaceDigraph(EasyMock.<Map<String, Map<String, Map<String, Set<String>>>>>anyObject(),
+ EasyMock.<Map<String, Set<Long>>>anyObject());
+ EasyMock.expectLastCall();
+ callback.saveState(EasyMock.<State>anyObject());
+ EasyMock.expectLastCall();
+ callback.installFeatureConfigs(f101);
+ EasyMock.expectLastCall();
+ callback.resolveBundles(EasyMock.eq(Collections.<Bundle>singleton(bundleA)));
+ EasyMock.expectLastCall();
+ callback.refreshPackages(EasyMock.eq(Collections.<Bundle>singleton(bundleA)));
+ EasyMock.expectLastCall();
+ callback.callListeners(FeatureEventMatcher.eq(new FeatureEvent(f100, FeatureEvent.EventType.FeatureUninstalled, false)));
+ EasyMock.expectLastCall();
+ callback.callListeners(FeatureEventMatcher.eq(new FeatureEvent(f101, FeatureEvent.EventType.FeatureInstalled, false)));
+ EasyMock.expectLastCall();
+
+ EasyMock.replay(callback);
+
+ Deployer.DeploymentState dstate = new Deployer.DeploymentState();
+ dstate.state = new State();
+ addToMapSet(dstate.state.installedFeatures, ROOT_REGION, f100.getId());
+ addToMapSet(dstate.state.managedBundles, ROOT_REGION, 1L);
+ dstate.bundles = new HashMap<>();
+ dstate.bundles.put(1L, bundleA);
+ dstate.bundlesPerRegion = new HashMap<>();
+ addToMapSet(dstate.bundlesPerRegion, ROOT_REGION, 1L);
+ dstate.features = new HashMap<>();
+ dstate.features.put(f100.getId(), f100);
+ dstate.features.put(f101.getId(), f101);
+ dstate.filtersPerRegion = new HashMap<>();
+ dstate.filtersPerRegion.put(ROOT_REGION, new HashMap<String, Map<String, Set<String>>>());
+
+ Deployer.DeploymentRequest request = new Deployer.DeploymentRequest();
+ request.bundleUpdateRange = DEFAULT_BUNDLE_UPDATE_RANGE;
+ request.featureResolutionRange = DEFAULT_FEATURE_RESOLUTION_RANGE;
+ request.globalRepository = null;
+ request.options = EnumSet.noneOf(Option.class);
+ request.overrides = Collections.emptySet();
+ request.stateChanges = Collections.emptyMap();
+ request.updateSnaphots = UPDATE_SNAPSHOTS_NONE;
+ request.requestedFeatures = new HashMap<>();
+ addToMapSet(request.requestedFeatures, ROOT_REGION, f101.getName() + "/" + new VersionRange(f101.getVersion(), true));
+
+ deployer.deploy(dstate, request);
+
+ EasyMock.verify(callback);
+ }
+
+ @Test
+ public void testUpdateServiceBundle() throws Exception {
+
+ String dataDir = "data1";
+
+ TestDownloadManager manager = new TestDownloadManager(getClass(), dataDir);
+
+ RepositoryImpl repo = new RepositoryImpl(getClass().getResource(dataDir + "/features.xml").toURI());
+ repo.load(true);
+ Feature f1 = repo.getFeatures()[0];
+
+ Bundle serviceBundle = createTestBundle(1, Bundle.ACTIVE, dataDir, "a100");
+
+ Deployer.DeployCallback callback = EasyMock.createMock(Deployer.DeployCallback.class);
+ Deployer deployer = new Deployer(manager, callback);
+
+ callback.print(EasyMock.anyString(), EasyMock.anyBoolean());
+ EasyMock.expectLastCall().anyTimes();
+ callback.replaceDigraph(EasyMock.<Map<String, Map<String, Map<String, Set<String>>>>>anyObject(),
+ EasyMock.<Map<String, Set<Long>>>anyObject());
+ EasyMock.expectLastCall();
+ callback.saveState(EasyMock.<State>anyObject());
+ EasyMock.expectLastCall();
+ callback.installFeatureConfigs(f1);
+ EasyMock.expectLastCall();
+ callback.resolveBundles(EasyMock.<Set<Bundle>>anyObject());
+ EasyMock.expectLastCall();
+ callback.callListeners(EasyMock.<FeatureEvent>anyObject());
+ EasyMock.expectLastCall();
+
+ EasyMock.replay(callback);
+
+ Deployer.DeploymentState dstate = new Deployer.DeploymentState();
+ dstate.state = new State();
+ dstate.bundles = new HashMap<>();
+ dstate.bundles.put(serviceBundle.getBundleId(), serviceBundle);
+ dstate.bundlesPerRegion = new HashMap<>();
+ addToMapSet(dstate.bundlesPerRegion, ROOT_REGION, serviceBundle.getBundleId());
+ dstate.features = Collections.singletonMap(f1.getId(), f1);
+ dstate.filtersPerRegion = new HashMap<>();
+ dstate.filtersPerRegion.put(ROOT_REGION, new HashMap<String, Map<String, Set<String>>>());
+
+ Deployer.DeploymentRequest request = new Deployer.DeploymentRequest();
+ request.bundleUpdateRange = DEFAULT_BUNDLE_UPDATE_RANGE;
+ request.featureResolutionRange = DEFAULT_FEATURE_RESOLUTION_RANGE;
+ request.globalRepository = null;
+ request.options = EnumSet.noneOf(Option.class);
+ request.overrides = Collections.emptySet();
+ request.stateChanges = Collections.emptyMap();
+ request.updateSnaphots = UPDATE_SNAPSHOTS_NONE;
+ request.requestedFeatures = new HashMap<>();
+ addToMapSet(request.requestedFeatures, ROOT_REGION, f1.getName());
+
+ deployer.deploy(dstate, request);
+
+ EasyMock.verify(callback);
+ }
+
+ private TestBundle createTestBundle(long bundleId, int state, String dir, String name) throws IOException, BundleException {
+ URL loc = getClass().getResource(dir + "/" + name + ".mf");
+ Manifest man = new Manifest(loc.openStream());
+ Hashtable<String, String> headers = new Hashtable<>();
+ for (Map.Entry attr : man.getMainAttributes().entrySet()) {
+ headers.put(attr.getKey().toString(), attr.getValue().toString());
+ }
+ return new TestBundle(bundleId, name, state, headers);
+ }
+
+ static class FeatureEventMatcher implements IArgumentMatcher {
+ final FeatureEvent expected;
+
+ FeatureEventMatcher(FeatureEvent expected) {
+ this.expected = expected;
+ }
+
+ public static FeatureEvent eq(FeatureEvent expected) {
+ EasyMock.reportMatcher(new FeatureEventMatcher(expected));
+ return null;
+ }
+
+ public boolean matches(Object argument) {
+ if (!(argument instanceof FeatureEvent)) {
+ return false;
+ }
+ FeatureEvent arg = (FeatureEvent) argument;
+ return arg.getFeature() == expected.getFeature()
+ && arg.getType() == expected.getType()
+ && arg.isReplay() == expected.isReplay();
+ }
+
+ public void appendTo(StringBuffer buffer) {
+
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/karaf/blob/e334faed/features/core/src/test/java/org/apache/karaf/features/internal/support/TestBundle.java
----------------------------------------------------------------------
diff --git a/features/core/src/test/java/org/apache/karaf/features/internal/support/TestBundle.java b/features/core/src/test/java/org/apache/karaf/features/internal/support/TestBundle.java
new file mode 100644
index 0000000..6f71731
--- /dev/null
+++ b/features/core/src/test/java/org/apache/karaf/features/internal/support/TestBundle.java
@@ -0,0 +1,262 @@
+/*
+ * 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.karaf.features.internal.support;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.security.cert.X509Certificate;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.felix.resolver.Util;
+import org.apache.karaf.features.internal.resolver.ResourceBuilder;
+import org.apache.karaf.features.internal.resolver.ResourceImpl;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.Version;
+import org.osgi.framework.startlevel.BundleStartLevel;
+import org.osgi.framework.wiring.BundleCapability;
+import org.osgi.framework.wiring.BundleRequirement;
+import org.osgi.framework.wiring.BundleRevision;
+import org.osgi.framework.wiring.BundleWiring;
+
+import static org.apache.karaf.features.internal.resolver.ResourceUtils.getUri;
+
+public class TestBundle extends ResourceImpl implements BundleRevision, Bundle, BundleStartLevel {
+
+ private final long bundleId;
+ private final String location;
+ private final Hashtable<String, String> headers = new Hashtable<>();
+ public int state;
+
+ public TestBundle(long bundleId, String location, int state, Hashtable<String, String> headers) throws BundleException {
+ this.bundleId = bundleId;
+ this.location = location;
+ this.state = state;
+ update(headers);
+ }
+
+ public void update(Hashtable<String, String> headers) throws BundleException {
+ this.headers.clear();
+ this.headers.putAll(headers);
+ this.caps.clear();
+ this.reqs.clear();
+ ResourceBuilder.build(this, location, headers);
+ }
+
+ @Override
+ public int getState() {
+ return state;
+ }
+
+ @Override
+ public void start(int options) throws BundleException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void start() throws BundleException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void stop(int options) throws BundleException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void stop() throws BundleException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void update(InputStream input) throws BundleException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void update() throws BundleException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void uninstall() throws BundleException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Dictionary<String, String> getHeaders() {
+ return headers;
+ }
+
+ @Override
+ public long getBundleId() {
+ return bundleId;
+ }
+
+ @Override
+ public String getLocation() {
+ return getUri(this);
+ }
+
+ @Override
+ public ServiceReference<?>[] getRegisteredServices() {
+ return new ServiceReference<?>[0];
+ }
+
+ @Override
+ public ServiceReference<?>[] getServicesInUse() {
+ return new ServiceReference<?>[0];
+ }
+
+ @Override
+ public boolean hasPermission(Object permission) {
+ return true;
+ }
+
+ @Override
+ public URL getResource(String name) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Dictionary<String, String> getHeaders(String locale) {
+ return getHeaders();
+ }
+
+ @Override
+ public Class<?> loadClass(String name) throws ClassNotFoundException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Enumeration<URL> getResources(String name) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Enumeration<String> getEntryPaths(String path) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public URL getEntry(String path) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public long getLastModified() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Enumeration<URL> findEntries(String path, String filePattern, boolean recurse) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public BundleContext getBundleContext() {
+ return null;
+ }
+
+ @Override
+ public Map<X509Certificate, List<X509Certificate>> getSignerCertificates(int signersType) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public <A> A adapt(Class<A> type) {
+ if (type.isInstance(this)) {
+ return type.cast(this);
+ }
+ return null;
+ }
+
+ @Override
+ public File getDataFile(String filename) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getSymbolicName() {
+ return Util.getSymbolicName(this);
+ }
+
+ @Override
+ public Version getVersion() {
+ return Util.getVersion(this);
+ }
+
+ @Override
+ public List<BundleCapability> getDeclaredCapabilities(String namespace) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public List<BundleRequirement> getDeclaredRequirements(String namespace) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getTypes() {
+ return 0;
+ }
+
+ @Override
+ public BundleWiring getWiring() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Bundle getBundle() {
+ return this;
+ }
+
+ @Override
+ public int compareTo(Bundle o) {
+ long thisBundleId = this.getBundleId();
+ long thatBundleId = o.getBundleId();
+ return (thisBundleId < thatBundleId ? -1 : (thisBundleId == thatBundleId ? 0 : 1));
+ }
+
+ @Override
+ public int getStartLevel() {
+ return 0;
+ }
+
+ @Override
+ public void setStartLevel(int startlevel) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isPersistentlyStarted() {
+ return false;
+ }
+
+ @Override
+ public boolean isActivationPolicyUsed() {
+ return false;
+ }
+}
http://git-wip-us.apache.org/repos/asf/karaf/blob/e334faed/features/core/src/test/java/org/apache/karaf/features/internal/support/TestDownloadManager.java
----------------------------------------------------------------------
diff --git a/features/core/src/test/java/org/apache/karaf/features/internal/support/TestDownloadManager.java b/features/core/src/test/java/org/apache/karaf/features/internal/support/TestDownloadManager.java
new file mode 100644
index 0000000..999c212
--- /dev/null
+++ b/features/core/src/test/java/org/apache/karaf/features/internal/support/TestDownloadManager.java
@@ -0,0 +1,88 @@
+/*
+ * 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.karaf.features.internal.support;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+
+import org.apache.karaf.features.internal.download.StreamProvider;
+import org.apache.karaf.features.internal.download.simple.SimpleDownloader;
+
+public class TestDownloadManager extends SimpleDownloader {
+
+ private final Class loader;
+ private final String dir;
+
+ public TestDownloadManager(Class loader, String dir) {
+ this.loader = loader;
+ this.dir = dir;
+ }
+
+ @Override
+ protected StreamProvider createProvider(String location) throws MalformedURLException {
+ return new TestProvider(location);
+ }
+
+ class TestProvider implements StreamProvider {
+ private final IOException exception;
+ private final Map<String, String> headers;
+ private final byte[] data;
+
+ TestProvider(String location) {
+ Map<String, String> headers = null;
+ byte[] data = null;
+ IOException exception = null;
+ try {
+ Manifest man = new Manifest(loader.getResourceAsStream(dir + "/" + location + ".mf"));
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ JarOutputStream jos = new JarOutputStream(baos, man);
+ jos.close();
+ data = baos.toByteArray();
+ headers = new HashMap<>();
+ for (Map.Entry attr : man.getMainAttributes().entrySet()) {
+ headers.put(attr.getKey().toString(), attr.getValue().toString());
+ }
+ } catch (IOException e) {
+ exception = e;
+ }
+ this.headers = headers;
+ this.data = data;
+ this.exception = exception;
+ }
+
+ @Override
+ public InputStream open() throws IOException {
+ if (exception != null)
+ throw exception;
+ return new ByteArrayInputStream(data);
+ }
+
+ @Override
+ public Map<String, String> getMetadata() throws IOException {
+ if (exception != null)
+ throw exception;
+ return headers;
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/karaf/blob/e334faed/features/core/src/test/resources/org/apache/karaf/features/internal/service/data1/a100.mf
----------------------------------------------------------------------
diff --git a/features/core/src/test/resources/org/apache/karaf/features/internal/service/data1/a100.mf b/features/core/src/test/resources/org/apache/karaf/features/internal/service/data1/a100.mf
new file mode 100644
index 0000000..b64f9c1
--- /dev/null
+++ b/features/core/src/test/resources/org/apache/karaf/features/internal/service/data1/a100.mf
@@ -0,0 +1,6 @@
+Manifest-Version: 1
+Bundle-ManifestVersion: 2
+Bundle-SymbolicName: a
+Bundle-Version: 1.0.0
+Provide-Capability: ns;ns=a
+
http://git-wip-us.apache.org/repos/asf/karaf/blob/e334faed/features/core/src/test/resources/org/apache/karaf/features/internal/service/data1/a101.mf
----------------------------------------------------------------------
diff --git a/features/core/src/test/resources/org/apache/karaf/features/internal/service/data1/a101.mf b/features/core/src/test/resources/org/apache/karaf/features/internal/service/data1/a101.mf
new file mode 100644
index 0000000..20ad3d0
--- /dev/null
+++ b/features/core/src/test/resources/org/apache/karaf/features/internal/service/data1/a101.mf
@@ -0,0 +1,6 @@
+Manifest-Version: 1
+Bundle-ManifestVersion: 2
+Bundle-SymbolicName: a
+Bundle-Version: 1.0.1
+Provide-Capability: ns;ns=a
+
http://git-wip-us.apache.org/repos/asf/karaf/blob/e334faed/features/core/src/test/resources/org/apache/karaf/features/internal/service/data1/features.xml
----------------------------------------------------------------------
diff --git a/features/core/src/test/resources/org/apache/karaf/features/internal/service/data1/features.xml b/features/core/src/test/resources/org/apache/karaf/features/internal/service/data1/features.xml
new file mode 100644
index 0000000..653d930
--- /dev/null
+++ b/features/core/src/test/resources/org/apache/karaf/features/internal/service/data1/features.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+
+-->
+<features name="test" xmlns="http://karaf.apache.org/xmlns/features/v1.3.0">
+
+ <feature name="f" version="1.0.0">
+ <bundle>a100</bundle>
+ </feature>
+
+ <feature name="f" version="1.0.1">
+ <bundle>a101</bundle>
+ </feature>
+
+</features>
\ No newline at end of file
[4/7] [KARAF-2888] Extract the deployment for easier unit testing
Posted by gn...@apache.org.
http://git-wip-us.apache.org/repos/asf/karaf/blob/9e2ada3c/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java
index d902a0e..099d175 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java
@@ -25,17 +25,13 @@ import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
-import java.util.Comparator;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
-import java.util.SortedMap;
-import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
@@ -45,21 +41,14 @@ import java.util.concurrent.Executors;
import org.apache.felix.utils.version.VersionRange;
import org.apache.felix.utils.version.VersionTable;
-import org.apache.karaf.features.BundleInfo;
import org.apache.karaf.features.Feature;
import org.apache.karaf.features.FeatureEvent;
import org.apache.karaf.features.FeaturesListener;
import org.apache.karaf.features.FeaturesService;
import org.apache.karaf.features.Repository;
import org.apache.karaf.features.RepositoryEvent;
-import org.apache.karaf.features.internal.download.StreamProvider;
-import org.apache.karaf.features.internal.region.SubsystemResolver;
-import org.apache.karaf.features.internal.util.ChecksumUtils;
import org.apache.karaf.features.internal.util.JsonReader;
import org.apache.karaf.features.internal.util.JsonWriter;
-import org.apache.karaf.features.internal.util.Macro;
-import org.apache.karaf.features.internal.util.MapUtils;
-import org.apache.karaf.features.internal.util.MultiException;
import org.apache.karaf.util.collections.CopyOnWriteArrayIdentityList;
import org.eclipse.equinox.region.Region;
import org.eclipse.equinox.region.RegionDigraph;
@@ -67,63 +56,26 @@ import org.eclipse.equinox.region.RegionFilterBuilder;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
-import org.osgi.framework.Constants;
import org.osgi.framework.FrameworkEvent;
import org.osgi.framework.FrameworkListener;
-import org.osgi.framework.ServiceReference;
+import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.Version;
import org.osgi.framework.startlevel.BundleStartLevel;
import org.osgi.framework.startlevel.FrameworkStartLevel;
-import org.osgi.framework.wiring.BundleRevision;
-import org.osgi.framework.wiring.BundleWire;
-import org.osgi.framework.wiring.BundleWiring;
import org.osgi.framework.wiring.FrameworkWiring;
-import org.osgi.resource.Capability;
-import org.osgi.resource.Resource;
-import org.osgi.resource.Wire;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import static org.apache.felix.resolver.Util.getSymbolicName;
-import static org.apache.felix.resolver.Util.getVersion;
-import static org.apache.karaf.features.internal.resolver.ResourceUtils.TYPE_SUBSYSTEM;
-import static org.apache.karaf.features.internal.resolver.ResourceUtils.getFeatureId;
-import static org.apache.karaf.features.internal.resolver.ResourceUtils.getUri;
import static org.apache.karaf.features.internal.service.StateStorage.toStringStringSetMap;
import static org.apache.karaf.features.internal.util.MapUtils.addToMapSet;
-import static org.apache.karaf.features.internal.util.MapUtils.apply;
import static org.apache.karaf.features.internal.util.MapUtils.copy;
-import static org.apache.karaf.features.internal.util.MapUtils.diff;
-import static org.apache.karaf.features.internal.util.MapUtils.flatten;
-import static org.apache.karaf.features.internal.util.MapUtils.map;
-import static org.apache.karaf.features.internal.util.MapUtils.removeFromMapSet;
-import static org.osgi.framework.Bundle.ACTIVE;
-import static org.osgi.framework.Bundle.RESOLVED;
-import static org.osgi.framework.Bundle.STARTING;
-import static org.osgi.framework.Bundle.STOPPING;
-import static org.osgi.framework.Bundle.STOP_TRANSIENT;
-import static org.osgi.framework.Bundle.UNINSTALLED;
-import static org.osgi.framework.namespace.IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE;
-import static org.osgi.framework.namespace.IdentityNamespace.IDENTITY_NAMESPACE;
-import static org.osgi.resource.Namespace.CAPABILITY_EFFECTIVE_DIRECTIVE;
-import static org.osgi.resource.Namespace.EFFECTIVE_ACTIVE;
/**
*
*/
-public class FeaturesServiceImpl implements FeaturesService {
-
- public static final String UPDATE_SNAPSHOTS_NONE = "none";
- public static final String UPDATE_SNAPSHOTS_CRC = "crc";
- public static final String UPDATE_SNAPSHOTS_ALWAYS = "always";
- public static final String DEFAULT_UPDATE_SNAPSHOTS = UPDATE_SNAPSHOTS_CRC;
-
- public static final String DEFAULT_FEATURE_RESOLUTION_RANGE = "${range;[====,====]}";
- public static final String DEFAULT_BUNDLE_UPDATE_RANGE = "${range;[==,=+)}";
+public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCallback {
private static final Logger LOGGER = LoggerFactory.getLogger(FeaturesServiceImpl.class);
- private static final String SNAPSHOT = "SNAPSHOT";
- private static final String MAVEN = "mvn:";
/**
* Our bundle.
@@ -149,13 +101,13 @@ public class FeaturesServiceImpl implements FeaturesService {
private final String overrides;
/**
* Range to use when a version is specified on a feature dependency.
- * The default is {@link FeaturesServiceImpl#DEFAULT_FEATURE_RESOLUTION_RANGE}
+ * The default is {@link org.apache.karaf.features.FeaturesService#DEFAULT_FEATURE_RESOLUTION_RANGE}
*/
private final String featureResolutionRange;
/**
* Range to use when verifying if a bundle should be updated or
* new bundle installed.
- * The default is {@link FeaturesServiceImpl#DEFAULT_BUNDLE_UPDATE_RANGE}
+ * The default is {@link org.apache.karaf.features.FeaturesService#DEFAULT_BUNDLE_UPDATE_RANGE}
*/
private final String bundleUpdateRange;
/**
@@ -331,7 +283,7 @@ public class FeaturesServiceImpl implements FeaturesService {
listeners.remove(listener);
}
- protected void callListeners(FeatureEvent event) {
+ public void callListeners(FeatureEvent event) {
if (eventAdminListener != null) {
eventAdminListener.featureEvent(event);
}
@@ -885,36 +837,37 @@ public class FeaturesServiceImpl implements FeaturesService {
}
}
- static class DeploymentState {
- Map<Long, Bundle> bundles;
- Map<String, Feature> features;
- Map<String, Set<Long>> bundlesPerRegion;
- Map<String, Map<String, Map<String, Set<String>>>> filtersPerRegion;
- }
-
- protected DeploymentState getDeploymentState() throws Exception {
- DeploymentState state = new DeploymentState();
+ protected Deployer.DeploymentState getDeploymentState(State state) throws Exception {
+ Deployer.DeploymentState dstate = new Deployer.DeploymentState();
+ // State
+ dstate.state = state;
+ // Service bundle
+ dstate.serviceBundle = bundle;
+ // Start level
+ FrameworkStartLevel fsl = systemBundleContext.getBundle().adapt(FrameworkStartLevel.class);
+ dstate.initialBundleStartLevel = fsl.getInitialBundleStartLevel();
+ dstate.currentStartLevel = fsl.getStartLevel();
// Bundles
- state.bundles = new HashMap<>();
+ dstate.bundles = new HashMap<>();
for (Bundle bundle : systemBundleContext.getBundles()) {
- state.bundles.put(bundle.getBundleId(), bundle);
+ dstate.bundles.put(bundle.getBundleId(), bundle);
}
// Features
- state.features = new HashMap<>();
+ dstate.features = new HashMap<>();
for (Map<String, Feature> m : getFeatures().values()) {
for (Feature feature : m.values()) {
String id = feature.getName() + "/" + VersionTable.getVersion(feature.getVersion());
- state.features.put(id, feature);
+ dstate.features.put(id, feature);
}
}
// Region -> bundles mapping
// Region -> policy mapping
- state.bundlesPerRegion = new HashMap<>();
- state.filtersPerRegion = new HashMap<>();
+ dstate.bundlesPerRegion = new HashMap<>();
+ dstate.filtersPerRegion = new HashMap<>();
RegionDigraph clone = digraph.copy();
for (Region region : clone.getRegions()) {
// Get bundles
- state.bundlesPerRegion.put(region.getName(), new HashSet<>(region.getBundleIds()));
+ dstate.bundlesPerRegion.put(region.getName(), new HashSet<>(region.getBundleIds()));
// Get policies
Map<String, Map<String, Set<String>>> edges = new HashMap<>();
for (RegionDigraph.FilteredRegion fr : clone.getEdges(region)) {
@@ -927,1030 +880,150 @@ public class FeaturesServiceImpl implements FeaturesService {
}
edges.put(fr.getRegion().getName(), policy);
}
- state.filtersPerRegion.put(region.getName(), edges);
+ dstate.filtersPerRegion.put(region.getName(), edges);
}
// Return
- return state;
+ return dstate;
}
+ private Deployer.DeploymentRequest getDeploymentRequest(Map<String, Set<String>> requestedFeatures, Map<String, Map<String, RequestedState>> stateChanges, EnumSet<Option> options) {
+ Deployer.DeploymentRequest request = new Deployer.DeploymentRequest();
+ request.bundleUpdateRange = bundleUpdateRange;
+ request.featureResolutionRange = featureResolutionRange;
+ request.globalRepository = globalRepository;
+ request.overrides = overrides;
+ request.requestedFeatures = requestedFeatures;
+ request.stateChanges = stateChanges;
+ request.options = options;
+ return request;
+ }
+
+
+
public void doInstallFeatures(Map<String, Set<String>> requestedFeatures, // all request features
Map<String, Map<String, RequestedState>> stateChanges, // features state changes
State state, // current state
EnumSet<Option> options // installation options
) throws Exception {
- boolean noRefreshUnmanaged = options.contains(Option.NoAutoRefreshUnmanagedBundles);
- boolean noRefreshManaged = options.contains(Option.NoAutoRefreshManagedBundles);
- boolean noRefresh = options.contains(Option.NoAutoRefreshBundles);
- boolean noStart = options.contains(Option.NoAutoStartBundles);
- boolean verbose = options.contains(Option.Verbose);
- boolean simulate = options.contains(Option.Simulate);
- boolean noManageBundles = options.contains(Option.NoAutoManageBundles);
-
- // TODO: add an option to unmanage bundles instead of uninstalling those
-
- DeploymentState dstate = getDeploymentState();
-
- Map<String, Set<Long>> managedBundles = copy(state.managedBundles);
-
- Map<String, Set<Bundle>> unmanagedBundles = apply(diff(dstate.bundlesPerRegion, state.managedBundles),
- map(dstate.bundles));
-
- // Resolve
- // TODO: requirements
- // TODO: bundles
-
- SubsystemResolver resolver = new SubsystemResolver();
- resolver.resolve(
- dstate.features.values(),
- requestedFeatures,
- apply(unmanagedBundles, adapt(BundleRevision.class)),
- Overrides.loadOverrides(this.overrides),
- featureResolutionRange,
- globalRepository);
-
- Map<String, StreamProvider> providers = resolver.getProviders();
- Map<String, Set<Resource>> featuresPerRegion = resolver.getFeaturesPerRegions();
- Map<String, Set<String>> installedFeatures = apply(featuresPerRegion, featureId());
- Map<String, Set<String>> newFeatures = diff(installedFeatures, state.installedFeatures);
- Map<String, Set<String>> delFeatures = diff(state.installedFeatures, installedFeatures);
-
- //
- // Compute requested features state
- //
- Map<String, Map<String, String>> stateFeatures = copy(state.stateFeatures);
- for (Map.Entry<String, Set<String>> entry : delFeatures.entrySet()) {
- Map<String, String> map = stateFeatures.get(entry.getKey());
- if (map != null) {
- map.entrySet().removeAll(entry.getValue());
- if (map.isEmpty()) {
- stateFeatures.remove(entry.getKey());
- }
- }
- }
- for (Map.Entry<String, Map<String, RequestedState>> entry1 : stateChanges.entrySet()) {
- String region = entry1.getKey();
- Map<String, String> regionStates = stateFeatures.get(region);
- if (regionStates != null) {
- for (Map.Entry<String, RequestedState> entry2 : entry1.getValue().entrySet()) {
- String feature = entry2.getKey();
- if (regionStates.containsKey(feature)) {
- regionStates.put(feature, entry2.getValue().name());
- }
- }
- }
- }
- for (Map.Entry<String, Set<String>> entry : newFeatures.entrySet()) {
- for (String feature : entry.getValue()) {
- Map<String, String> map = stateFeatures.get(entry.getKey());
- if (map == null) {
- map = new HashMap<>();
- stateFeatures.put(entry.getKey(), map);
- }
- map.put(feature, noStart ? RequestedState.Installed.name() : RequestedState.Started.name());
- }
- }
-
- // Compute information for each bundle
- Map<String, Map<String, BundleInfo>> bundleInfos = resolver.getBundleInfos();
-
- //
- // Compute deployment
- //
- Deployment deployment = computeDeployment(dstate, resolver, state);
-
- //
- // Compute the set of bundles to refresh
- //
- Set<Bundle> toRefresh = new TreeSet<>(new BundleComparator()); // sort is only used for display
- for (RegionDeployment regionDeployment : deployment.regions.values()) {
- toRefresh.addAll(regionDeployment.toDelete);
- toRefresh.addAll(regionDeployment.toUpdate.keySet());
- }
- if (!noRefreshManaged) {
- computeBundlesToRefresh(toRefresh, dstate.bundles.values(), deployment.resToBnd, resolver.getWiring());
- }
- if (noRefreshUnmanaged) {
- toRefresh.removeAll(flatten(unmanagedBundles));
- }
-
- // Automatically turn unmanaged bundles into managed bundles
- // if they are required by a feature and no other unmanaged
- // bundles have a requirement on it
- Set<Bundle> toManage = new TreeSet<>(new BundleComparator()); // sort is only used for display
- if (!noManageBundles) {
- Set<Resource> features = resolver.getFeatures().keySet();
- Set<? extends Resource> unmanaged = apply(flatten(unmanagedBundles), adapt(BundleRevision.class));
- Set<Resource> requested = new HashSet<>();
- // Gather bundles required by a feature
- for (List<Wire> wires : resolver.getWiring().values()) {
- for (Wire wire : wires) {
- if (features.contains(wire.getRequirer()) && unmanaged.contains(wire.getProvider())) {
- requested.add(wire.getProvider());
- }
- }
- }
- // Now, we know which bundles are completely unmanaged
- unmanaged.removeAll(requested);
- // Check if bundles have wires from really unmanaged bundles
- for (List<Wire> wires : resolver.getWiring().values()) {
- for (Wire wire : wires) {
- if (requested.contains(wire.getProvider()) && unmanaged.contains(wire.getRequirer())) {
- requested.remove(wire.getProvider());
- }
- }
- }
- if (!requested.isEmpty()) {
- Map<Long, String> bundleToRegion = new HashMap<>();
- for (Map.Entry<String, Set<Long>> entry : dstate.bundlesPerRegion.entrySet()) {
- for (long id : entry.getValue()) {
- bundleToRegion.put(id, entry.getKey());
- }
- }
- for (Resource rev : requested) {
- Bundle bundle = ((BundleRevision) rev).getBundle();
- long id = bundle.getBundleId();
- addToMapSet(managedBundles, bundleToRegion.get(id), id);
- toManage.add(bundle);
- }
- }
- }
-
- //
- // Log deployment
- //
- logDeployment(deployment, verbose);
-
- if (simulate) {
- if (!noRefresh && !toRefresh.isEmpty()) {
- print(" Bundles to refresh:", verbose);
- for (Bundle bundle : toRefresh) {
- print(" " + bundle.getSymbolicName() + " / " + bundle.getVersion(), verbose);
- }
- }
- if (!toManage.isEmpty()) {
- print(" Managing bundle:", verbose);
- for (Bundle bundle : toManage) {
- print(" " + bundle.getSymbolicName() + " / " + bundle.getVersion(), verbose);
- }
- }
- return;
- }
-
- Set<Bundle> toStart = new HashSet<>();
- Set<Bundle> toResolve = new HashSet<>();
- Set<Bundle> toStop = new HashSet<>();
-
- //
- // Execute deployment
- //
- // #1: stop bundles that needs to be updated or uninstalled in order
- // #2: uninstall needed bundles
- // #3: update regions
- // #4: update bundles
- // #5: install bundles
- // #6: save state
- // #7: install configuration
- // #8: refresh bundles
- // #9: start bundles in order
- // #10: send events
- //
-
- //
- // Compute bundle states
- //
- Map<Resource, RequestedState> states = new HashMap<>();
- for (Map.Entry<String, Set<Resource>> entry : resolver.getFeaturesPerRegions().entrySet()) {
- String region = entry.getKey();
- Map<String, String> fss = stateFeatures.get(region);
- for (Resource feature : entry.getValue()) {
- String fs = fss.get(getFeatureId(feature));
- propagateState(states, feature, RequestedState.valueOf(fs), resolver);
- }
- }
- states.keySet().retainAll(resolver.getBundles().keySet());
- //
- // Compute bundles to start, stop and resolve
- //
- for (Map.Entry<Resource, RequestedState> entry : states.entrySet()) {
- Bundle bundle = deployment.resToBnd.get(entry.getKey());
- if (bundle != null) {
- switch (entry.getValue()) {
- case Started:
- toResolve.add(bundle);
- toStart.add(bundle);
- break;
- case Resolved:
- toResolve.add(bundle);
- toStop.add(bundle);
- break;
- }
- }
- }
- //
- // Compute bundle all start levels and start levels to update
- //
- FrameworkStartLevel fsl = systemBundleContext.getBundle().adapt(FrameworkStartLevel.class);
- int initialBundleStartLevel = fsl.getInitialBundleStartLevel();
- int currentStartLevel = fsl.getStartLevel();
- Map<Resource, Integer> startLevels = new HashMap<>();
- Map<Bundle, Integer> toUpdateStartLevel = new HashMap<>();
- for (Map.Entry<String, Set<Resource>> entry : resolver.getBundlesPerRegions().entrySet()) {
- String region = entry.getKey();
- for (Resource resource : entry.getValue()) {
- BundleInfo bi = bundleInfos.get(region).get(getUri(resource));
- if (bi != null) {
- int sl = bi.getStartLevel() > 0 ? bi.getStartLevel() : initialBundleStartLevel;
- startLevels.put(resource, sl);
- Bundle bundle = deployment.resToBnd.get(resource);
- if (bundle != null) {
- int curSl = bundle.adapt(BundleStartLevel.class).getStartLevel();
- if (sl != curSl) {
- toUpdateStartLevel.put(bundle, sl);
- if (sl > currentStartLevel) {
- toStop.add(bundle);
- }
- }
- }
- }
- }
- }
-
-
- //
- // Handle updates on the FeaturesService bundle
- //
- RegionDeployment rootRegionDeployment = deployment.regions.get(ROOT_REGION);
- // We don't support uninstalling the bundle
- if (rootRegionDeployment != null && rootRegionDeployment.toDelete.contains(bundle)) {
- throw new UnsupportedOperationException("Uninstalling the FeaturesService bundle is not supported");
- }
- // If the bundle needs to be updated, do the following:
- // - create flag files to indicate the resolution must be continued after restart
- // - update the checksum and save the state
- // - compute bundles wired to the FeaturesService bundle that will be refreshed
- // - stop the bundle
- // - update the bundle
- // - refresh wired bundles
- // - start the bundle
- // - exit
- // When restarting, the resolution will be attempted again
- if (rootRegionDeployment != null && rootRegionDeployment.toUpdate.containsKey(bundle)) {
- writeResolve(requestedFeatures, options);
- // If the bundle is updated because of a different checksum,
- // save the new checksum persistently
- if (deployment.bundleChecksums.containsKey(bundle.getBundleId())) {
- synchronized (lock) {
- this.state.bundleChecksums.put(bundle.getBundleId(), deployment.bundleChecksums.get(bundle.getBundleId()));
- saveState();
- }
- }
- Resource resource = rootRegionDeployment.toUpdate.get(bundle);
- String uri = getUri(resource);
- print("The FeaturesService bundle needs is being updated with " + uri, verbose);
- toRefresh.clear();
- toRefresh.add(bundle);
- computeBundlesToRefresh(toRefresh,
- dstate.bundles.values(),
- Collections.<Resource, Bundle>emptyMap(),
- Collections.<Resource, List<Wire>>emptyMap());
- bundle.stop(STOP_TRANSIENT);
- try (
- InputStream is = getBundleInputStream(resource, providers)
- ) {
- bundle.update(is);
- }
- refreshPackages(toRefresh);
- bundle.start();
- return;
- }
-
- //
- // Perform bundle operations
- //
-
- //
- // Stop bundles by chunks
- //
- for (RegionDeployment regionDeployment : deployment.regions.values()) {
- toStop.addAll(regionDeployment.toUpdate.keySet());
- toStop.addAll(regionDeployment.toDelete);
- }
- removeFragmentsAndBundlesInState(toStop, UNINSTALLED | RESOLVED | STOPPING);
- if (!toStop.isEmpty()) {
- print("Stopping bundles:", verbose);
- while (!toStop.isEmpty()) {
- List<Bundle> bs = getBundlesToStop(toStop);
- for (Bundle bundle : bs) {
- print(" " + bundle.getSymbolicName() + " / " + bundle.getVersion(), verbose);
- // If the bundle start level will be changed, stop it persistently to
- // avoid a restart when the start level is actually changed
- bundle.stop(toUpdateStartLevel.containsKey(bundle) ? 0 : STOP_TRANSIENT);
- toStop.remove(bundle);
- }
- }
- }
-
- //
- // Delete bundles
- //
- boolean hasToDelete = false;
- for (RegionDeployment regionDeployment : deployment.regions.values()) {
- if (hasToDelete = !regionDeployment.toDelete.isEmpty()) {
- break;
- }
- }
- if (hasToDelete) {
- print("Uninstalling bundles:", verbose);
- for (Map.Entry<String, RegionDeployment> entry : deployment.regions.entrySet()) {
- String name = entry.getKey();
- RegionDeployment regionDeployment = entry.getValue();
- for (Bundle bundle : regionDeployment.toDelete) {
- print(" " + bundle.getSymbolicName() + " / " + bundle.getVersion(), verbose);
- bundle.uninstall();
- removeFromMapSet(managedBundles, name, bundle.getBundleId());
- }
- }
- }
+ Deployer.DeploymentState dstate = getDeploymentState(state);
+ Deployer.DeploymentRequest request = getDeploymentRequest(requestedFeatures, stateChanges, options);
+ new Deployer(this).deploy(dstate, request);
+ }
- //
- // Update regions
- //
- {
- RegionDigraph clone = digraph.copy();
- RegionDigraph computedDigraph = resolver.getFlatDigraph();
- Map<String, Map<String, Map<String, Set<String>>>> policies = copy(dstate.filtersPerRegion);
- // Iterate through previously managed regions and
- // delete those that do not contain any bundles anymore
- for (String name : state.managedBundles.keySet()) {
- if (!managedBundles.containsKey(name) && !unmanagedBundles.containsKey(name)) {
- dstate.filtersPerRegion.remove(name);
- }
- }
- // Fix broken filters
- for (String name : policies.keySet()) {
- policies.get(name).keySet().retainAll(policies.keySet());
- }
- // Update managed regions
- for (Region computedRegion : computedDigraph.getRegions()) {
- String name = computedRegion.getName();
- Map<String, Map<String, Set<String>>> policy = policies.get(name);
- if (policy == null) {
- policy = new HashMap<>();
- policies.put(name, policy);
- }
- for (RegionDigraph.FilteredRegion fr : computedDigraph.getEdges(computedRegion)) {
- String r2 = fr.getRegion().getName();
- Map<String, Set<String>> filters = new HashMap<>();
- Map<String, Collection<String>> current = fr.getFilter().getSharingPolicy();
- for (String ns : current.keySet()) {
- for (String f : current.get(ns)) {
- addToMapSet(filters, ns, f);
- }
- }
- policy.put(r2, filters);
- }
- }
- // Apply all changes
- for (Region region : clone.getRegions()) {
- clone.removeRegion(region);
- }
- for (String name : policies.keySet()) {
- clone.createRegion(name);
- }
- for (String r1Name : policies.keySet()) {
- Region r1 = clone.getRegion(r1Name);
- Map<String, Map<String, Set<String>>> policy = policies.get(r1Name);
- for (String r2Name : policy.keySet()) {
- Region r2 = clone.getRegion(r2Name);
- RegionFilterBuilder rfb = clone.createRegionFilterBuilder();
- for (String ns : policy.get(r2Name).keySet()) {
- for (String f : policy.get(r2Name).get(ns)) {
- rfb.allow(ns, f);
- }
- }
- clone.connect(r1, rfb.build(), r2);
- }
- // Dispatch bundles
- if (unmanagedBundles.containsKey(r1Name)) {
- for (Bundle bundle : unmanagedBundles.get(r1Name)) {
- r1.addBundle(bundle);
- }
- }
- if (managedBundles.containsKey(r1Name)) {
- for (long id : managedBundles.get(r1Name)) {
- r1.addBundle(id);
- }
- }
- }
- this.digraph.replace(clone);
+ public void print(String message, boolean verbose) {
+ LOGGER.info(message);
+ if (verbose) {
+ System.out.println(message);
}
+ }
-
- //
- // Update bundles
- //
- boolean hasToUpdate = false;
- for (RegionDeployment regionDeployment : deployment.regions.values()) {
- if (hasToUpdate = !regionDeployment.toUpdate.isEmpty()) {
- break;
- }
- }
- if (hasToUpdate) {
- print("Updating bundles:", verbose);
- for (Map.Entry<String, RegionDeployment> rde : deployment.regions.entrySet()) {
- for (Map.Entry<Bundle, Resource> entry : rde.getValue().toUpdate.entrySet()) {
- Bundle bundle = entry.getKey();
- Resource resource = entry.getValue();
- String uri = getUri(resource);
- print(" " + uri, verbose);
- try (
- InputStream is = getBundleInputStream(resource, providers)
- ) {
- bundle.update(is);
- }
- toStart.add(bundle);
- }
- }
- }
- //
- // Update start levels
- //
- for (Map.Entry<Bundle, Integer> entry : toUpdateStartLevel.entrySet()) {
- Bundle bundle = entry.getKey();
- int sl = entry.getValue();
- bundle.adapt(BundleStartLevel.class).setStartLevel(sl);
- }
- //
- // Install bundles
- //
- boolean hasToInstall = false;
- for (RegionDeployment regionDeployment : deployment.regions.values()) {
- if (hasToInstall = !regionDeployment.toInstall.isEmpty()) {
- break;
- }
- }
- if (hasToInstall) {
- print("Installing bundles:", verbose);
- for (Map.Entry<String, RegionDeployment> entry : deployment.regions.entrySet()) {
- String name = entry.getKey();
- Region region = digraph.getRegion(name);
- RegionDeployment regionDeployment = entry.getValue();
- for (Resource resource : regionDeployment.toInstall) {
- String uri = getUri(resource);
- print(" " + uri, verbose);
- Bundle bundle;
- long crc;
- try (
- ChecksumUtils.CRCInputStream is = new ChecksumUtils.CRCInputStream(getBundleInputStream(resource, providers))
- ) {
- if (ROOT_REGION.equals(name)) {
- bundle = region.installBundleAtLocation(uri, is);
- } else {
- bundle = region.installBundle(uri, is);
- }
- crc = is.getCRC();
- }
- addToMapSet(managedBundles, name, bundle.getBundleId());
- deployment.resToBnd.put(resource, bundle);
- // save a checksum of installed snapshot bundle
- if (UPDATE_SNAPSHOTS_CRC.equals(updateSnaphots)
- && isUpdateable(resource) && !deployment.bundleChecksums.containsKey(bundle.getBundleId())) {
- deployment.bundleChecksums.put(bundle.getBundleId(), crc);
- }
- int startLevel = startLevels.get(resource);
- bundle.adapt(BundleStartLevel.class).setStartLevel(startLevel);
- RequestedState reqState = states.get(resource);
- switch (reqState) {
- case Started:
- toResolve.add(bundle);
- toStart.add(bundle);
- break;
- case Resolved:
- toResolve.add(bundle);
- break;
- }
+ public void refreshPackages(Collection<Bundle> bundles) throws InterruptedException {
+ final CountDownLatch latch = new CountDownLatch(1);
+ FrameworkWiring fw = systemBundleContext.getBundle().adapt(FrameworkWiring.class);
+ fw.refreshBundles(bundles, new FrameworkListener() {
+ @Override
+ public void frameworkEvent(FrameworkEvent event) {
+ if (event.getType() == FrameworkEvent.ERROR) {
+ LOGGER.error("Framework error", event.getThrowable());
}
+ latch.countDown();
}
- }
+ });
+ latch.await();
+ }
- //
- // Update and save state
- //
+ @Override
+ public void saveState(State state) {
synchronized (lock) {
- this.state.bundleChecksums.clear();
- this.state.bundleChecksums.putAll(deployment.bundleChecksums);
- this.state.requestedFeatures.clear();
- this.state.requestedFeatures.putAll(requestedFeatures);
- this.state.installedFeatures.clear();
- this.state.installedFeatures.putAll(installedFeatures);
- this.state.stateFeatures.clear();
- this.state.stateFeatures.putAll(stateFeatures);
- this.state.managedBundles.clear();
- this.state.managedBundles.putAll(managedBundles);
+ this.state.replace(state);
saveState();
}
-
- //
- // Install configurations
- //
- if (configInstaller != null && !newFeatures.isEmpty()) {
- Set<Feature> set = apply(flatten(newFeatures), map(dstate.features));
- for (Feature feature : set) {
- configInstaller.installFeatureConfigs(feature);
- }
- }
-
- // TODO: remove this hack, but it avoids loading the class after the bundle is refreshed
- new CopyOnWriteArrayIdentityList().iterator();
- RequirementSort.sort(Collections.<Resource>emptyList());
-
- if (!noRefresh) {
- toStop = new HashSet<>();
- toStop.addAll(toRefresh);
- removeFragmentsAndBundlesInState(toStop, UNINSTALLED | RESOLVED | STOPPING);
- if (!toStop.isEmpty()) {
- print("Stopping bundles:", verbose);
- while (!toStop.isEmpty()) {
- List<Bundle> bs = getBundlesToStop(toStop);
- for (Bundle bundle : bs) {
- print(" " + bundle.getSymbolicName() + " / " + bundle.getVersion(), verbose);
- bundle.stop(STOP_TRANSIENT);
- toStop.remove(bundle);
- toStart.add(bundle);
- }
- }
- }
-
- if (!toRefresh.isEmpty()) {
- print("Refreshing bundles:", verbose);
- for (Bundle bundle : toRefresh) {
- print(" " + bundle.getSymbolicName() + " / " + bundle.getVersion(), verbose);
- }
- if (!toRefresh.isEmpty()) {
- refreshPackages(toRefresh);
- }
- }
- }
-
- // Resolve bundles
- toResolve.addAll(toStart);
- toResolve.addAll(toRefresh);
- removeFragmentsAndBundlesInState(toResolve, UNINSTALLED);
- systemBundleContext.getBundle().adapt(FrameworkWiring.class).resolveBundles(toResolve);
-
- // Compute bundles to start
- removeFragmentsAndBundlesInState(toStart, UNINSTALLED | ACTIVE | STARTING);
- if (!toStart.isEmpty()) {
- // Compute correct start order
- List<Exception> exceptions = new ArrayList<>();
- print("Starting bundles:", verbose);
- while (!toStart.isEmpty()) {
- List<Bundle> bs = getBundlesToStart(toStart);
- for (Bundle bundle : bs) {
- print(" " + bundle.getSymbolicName() + " / " + bundle.getVersion(), verbose);
- try {
- bundle.start();
- } catch (BundleException e) {
- exceptions.add(e);
- }
- toStart.remove(bundle);
- }
- }
- if (!exceptions.isEmpty()) {
- throw new MultiException("Error restarting bundles", exceptions);
- }
- }
-
- // Call listeners
- // TODO: add region information and avoid flattening
- for (Feature feature : apply(flatten(delFeatures), map(dstate.features))) {
- callListeners(new FeatureEvent(feature, FeatureEvent.EventType.FeatureUninstalled, false));
- }
- for (Feature feature : apply(flatten(newFeatures), map(dstate.features))) {
- callListeners(new FeatureEvent(feature, FeatureEvent.EventType.FeatureInstalled, false));
- }
-
- print("Done.", verbose);
- }
-
- private void propagateState(Map<Resource, RequestedState> states, Resource resource, RequestedState state, SubsystemResolver resolver) {
- if (!isSubsystem(resource)) {
- RequestedState reqState = mergeStates(state, states.get(resource));
- if (reqState != states.get(resource)) {
- states.put(resource, reqState);
- for (Wire wire : resolver.getWiring().get(resource)) {
- Resource provider = wire.getProvider();
- RequestedState stateToMerge;
- String region = resolver.getBundles().get(provider);
- BundleInfo bi = region != null ? resolver.getBundleInfos().get(region).get(getUri(provider)) : null;
- if (reqState == RequestedState.Started) {
- String effective = wire.getCapability().getDirectives().get(CAPABILITY_EFFECTIVE_DIRECTIVE);
- // If there is an active effective capability or a requirement from the feature
- // and if the bundle is flagged as to start, start it
- if ((EFFECTIVE_ACTIVE.equals(effective) || IDENTITY_NAMESPACE.equals(wire.getCapability().getNamespace()))
- && (bi == null || bi.isStart())) {
- stateToMerge = RequestedState.Started;
- } else {
- stateToMerge = RequestedState.Resolved;
- }
- } else {
- stateToMerge = reqState;
- }
- propagateState(states, provider, stateToMerge, resolver);
- }
- }
- }
}
- private boolean isSubsystem(Resource resource) {
- for (Capability cap : resource.getCapabilities(null)) {
- if (cap.getNamespace().equals(IDENTITY_NAMESPACE)) {
- String type = (String) cap.getAttributes().get(CAPABILITY_TYPE_ATTRIBUTE);
- return (type != null) && type.equals(TYPE_SUBSYSTEM);
- }
- }
- return false;
- }
-
- private RequestedState mergeStates(RequestedState s1, RequestedState s2) {
- if (s1 == RequestedState.Started || s2 == RequestedState.Started) {
- return RequestedState.Started;
- }
- if (s1 == RequestedState.Resolved || s2 == RequestedState.Resolved) {
- return RequestedState.Resolved;
- }
- return RequestedState.Installed;
- }
-
- private void computeBundlesToRefresh(Set<Bundle> toRefresh, Collection<Bundle> bundles, Map<Resource, Bundle> resources, Map<Resource, List<Wire>> resolution) {
- int size;
- do {
- size = toRefresh.size();
- for (Bundle bundle : bundles) {
- // Continue if we already know about this bundle
- if (toRefresh.contains(bundle)) {
- continue;
- }
- // Ignore non resolved bundle
- BundleWiring wiring = bundle.adapt(BundleWiring.class);
- if (wiring == null) {
- continue;
- }
- // Get through the old resolution and flag this bundle
- // if it was wired to a bundle to be refreshed
- for (BundleWire wire : wiring.getRequiredWires(null)) {
- if (toRefresh.contains(wire.getProvider().getBundle())) {
- toRefresh.add(bundle);
- break;
- }
- }
- // Get through the new resolution and flag this bundle
- // if it's wired to any new bundle
- List<Wire> newWires = resolution.get(wiring.getRevision());
- if (newWires != null) {
- for (Wire wire : newWires) {
- Bundle b;
- if (wire.getProvider() instanceof BundleRevision) {
- b = ((BundleRevision) wire.getProvider()).getBundle();
- } else {
- b = resources.get(wire.getProvider());
- }
- if (b == null || toRefresh.contains(b)) {
- toRefresh.add(bundle);
- break;
- }
- }
- }
- }
- } while (toRefresh.size() > size);
+ @Override
+ public void persistResolveRequest(Deployer.DeploymentRequest request) throws IOException {
+ writeResolve(request.requestedFeatures, request.options);
}
- private void print(String message, boolean verbose) {
- LOGGER.info(message);
- if (verbose) {
- System.out.println(message);
+ @Override
+ public void installFeatureConfigs(Feature feature) throws IOException, InvalidSyntaxException {
+ if (configInstaller != null) {
+ configInstaller.installFeatureConfigs(feature);
}
}
- private void removeFragmentsAndBundlesInState(Collection<Bundle> bundles, int state) {
- for (Iterator<Bundle> iterator = bundles.iterator(); iterator.hasNext();) {
- Bundle bundle = iterator.next();
- if ((bundle.getState() & state) != 0
- || bundle.getHeaders().get(Constants.FRAGMENT_HOST) != null) {
- iterator.remove();
- }
+ @Override
+ public Bundle installBundle(String region, String uri, InputStream is) throws BundleException {
+ if (ROOT_REGION.equals(region)) {
+ return digraph.getRegion(region).installBundleAtLocation(uri, is);
+ } else {
+ return digraph.getRegion(region).installBundle(uri, is);
}
}
- protected void logDeployment(Deployment overallDeployment, boolean verbose) {
- if (overallDeployment.regions.isEmpty()) {
- print("No deployment change.", verbose);
- return;
- }
- print("Changes to perform:", verbose);
- for (Map.Entry<String, RegionDeployment> region : overallDeployment.regions.entrySet()) {
- RegionDeployment deployment = region.getValue();
- print(" Region: " + region.getKey(), verbose);
- if (!deployment.toDelete.isEmpty()) {
- print(" Bundles to uninstall:", verbose);
- for (Bundle bundle : deployment.toDelete) {
- print(" " + bundle.getSymbolicName() + " / " + bundle.getVersion(), verbose);
- }
- }
- if (!deployment.toUpdate.isEmpty()) {
- print(" Bundles to update:", verbose);
- for (Map.Entry<Bundle, Resource> entry : deployment.toUpdate.entrySet()) {
- print(" " + entry.getKey().getSymbolicName() + " / " + entry.getKey().getVersion() + " with " + getUri(entry.getValue()), verbose);
- }
- }
- if (!deployment.toInstall.isEmpty()) {
- print(" Bundles to install:", verbose);
- for (Resource resource : deployment.toInstall) {
- print(" " + getUri(resource), verbose);
- }
- }
- }
+ @Override
+ public void updateBundle(Bundle bundle, InputStream is) throws BundleException {
+ bundle.update(is);
}
- protected Deployment computeDeployment(
- DeploymentState dstate,
- SubsystemResolver resolver,
- State state) throws IOException {
-
- Deployment result = new Deployment();
-
- Map<String, Set<Resource>> bundlesPerRegions = resolver.getBundlesPerRegions();
-
- // Gather all regions, including old ones and new ones
- Set<String> regions = new HashSet<>();
- regions.addAll(state.managedBundles.keySet());
- regions.addAll(bundlesPerRegions.keySet());
-
- for (String region : regions) {
-
- RegionDeployment deployment = new RegionDeployment();
-
- // Get the list of bundles currently assigned in the region
- Set<Long> managed = state.managedBundles.get(region);
- if (managed == null) {
- managed = Collections.emptySet();
- }
-
- // Compute the list of resources to deploy in the region
- Set<Resource> bundlesInRegion = bundlesPerRegions.get(region);
- List<Resource> toDeploy = bundlesInRegion != null
- ? new ArrayList<>(bundlesInRegion) : new ArrayList<Resource>();
-
- // First pass: go through all installed bundles and mark them
- // as either to ignore or delete
- for (long bundleId : managed) {
- // Look for the installed bundle
- Bundle bundle = dstate.bundles.get(bundleId);
- // Bundle has been manually uninstalled ?
- if (bundle != null) {
- // Look for a matching resource
- Resource resource = null;
- for (Resource res : toDeploy) {
- if (bundle.getSymbolicName().equals(getSymbolicName(res))
- && bundle.getVersion().equals(getVersion(res))) {
- resource = res;
- break;
- }
- }
- // We found a matching bundle
- if (resource != null) {
- // In case of snapshots, check if the snapshot is out of date
- // and flag it as to update
- if (isUpdateable(resource)) {
- // Always update snapshots
- if (UPDATE_SNAPSHOTS_ALWAYS.equalsIgnoreCase(updateSnaphots)) {
- LOGGER.debug("Update snapshot for " + bundle.getLocation());
- deployment.toUpdate.put(bundle, resource);
- } else if (UPDATE_SNAPSHOTS_CRC.equalsIgnoreCase(updateSnaphots)) {
- // if the checksum are different
- try (
- InputStream is = getBundleInputStream(resource, resolver.getProviders())
- ) {
- long newCrc = ChecksumUtils.checksum(is);
- long oldCrc = state.bundleChecksums.containsKey(bundle.getBundleId()) ? state.bundleChecksums.get(bundle.getBundleId()) : 0L;
- if (newCrc != oldCrc) {
- LOGGER.debug("New snapshot available for " + bundle.getLocation());
- deployment.toUpdate.put(bundle, resource);
- }
- result.bundleChecksums.put(bundle.getBundleId(), newCrc);
- }
- }
- }
- // We're done for this resource
- toDeploy.remove(resource);
- result.resToBnd.put(resource, bundle);
- // There's no matching resource
- // If the bundle is managed, we need to delete it
- } else if (managed.contains(bundle.getBundleId())) {
- deployment.toDelete.add(bundle);
- }
- }
- }
-
- // Second pass on remaining resources
- for (Resource resource : toDeploy) {
- TreeMap<Version, Bundle> matching = new TreeMap<>();
- VersionRange range = new VersionRange(Macro.transform(bundleUpdateRange, getVersion(resource).toString()));
- for (Bundle bundle : deployment.toDelete) {
- if (bundle.getSymbolicName().equals(getSymbolicName(resource)) && range.contains(bundle.getVersion())) {
- matching.put(bundle.getVersion(), bundle);
- }
- }
- if (!matching.isEmpty()) {
- Bundle bundle = matching.lastEntry().getValue();
- deployment.toUpdate.put(bundle, resource);
- deployment.toDelete.remove(bundle);
- result.resToBnd.put(resource, bundle);
- } else {
- deployment.toInstall.add(resource);
- }
- }
- Collections.sort(deployment.toInstall, new ResourceComparator());
-
- // Add this region if there is something to do
- if (!deployment.toDelete.isEmpty()
- || !deployment.toUpdate.isEmpty()
- || !deployment.toInstall.isEmpty()) {
- result.regions.put(region, deployment);
- }
- }
-
- return result;
+ @Override
+ public void uninstall(Bundle bundle) throws BundleException {
+ bundle.uninstall();
}
- protected <T> MapUtils.Function<Bundle, T> adapt(final Class<T> clazz) {
- return new MapUtils.Function<Bundle, T>() {
- @Override
- public T apply(Bundle bundle) {
- return bundle.adapt(clazz);
- }
- };
+ @Override
+ public void startBundle(Bundle bundle) throws BundleException {
+ bundle.start();
}
- protected MapUtils.Function<Resource, String> featureId() {
- return new MapUtils.Function<Resource, String>() {
- @Override
- public String apply(Resource resource) {
- return getFeatureId(resource);
- }
- };
+ @Override
+ public void stopBundle(Bundle bundle, int options) throws BundleException {
+ bundle.stop(options);
}
-
- protected boolean isUpdateable(Resource resource) {
- String uri = getUri(resource);
- return getVersion(resource).getQualifier().endsWith(SNAPSHOT)
- || uri.contains(SNAPSHOT)
- || !uri.contains(MAVEN);
+ @Override
+ public void setBundleStartLevel(Bundle bundle, int startLevel) {
+ bundle.adapt(BundleStartLevel.class).setStartLevel(startLevel);
}
- protected List<Bundle> getBundlesToStart(Collection<Bundle> bundles) {
- // Restart the features service last, regardless of any other consideration
- // so that we don't end up with the service trying to do stuff before we're done
- boolean restart = false;
-
- SortedMap<Integer, Set<Bundle>> bundlesPerStartLevel = new TreeMap<>();
- for (Bundle bundle : bundles) {
- if (bundle == this.bundle) {
- restart = true;
- } else {
- int sl = bundle.adapt(BundleStartLevel.class).getStartLevel();
- addToMapSet(bundlesPerStartLevel, sl, bundle);
- }
- }
- if (bundlesPerStartLevel.isEmpty()) {
- bundles = Collections.emptyList();
- } else {
- bundles = bundlesPerStartLevel.remove(bundlesPerStartLevel.firstKey());
- }
-
- // We hit FELIX-2949 if we don't use the correct order as Felix resolver isn't greedy.
- // In order to minimize that, we make sure we resolve the bundles in the order they
- // are given back by the resolution, meaning that all root bundles (i.e. those that were
- // not flagged as dependencies in features) are started before the others. This should
- // make sure those important bundles are started first and minimize the problem.
- List<BundleRevision> revs = new ArrayList<>();
- for (Bundle bundle : bundles) {
- revs.add(bundle.adapt(BundleRevision.class));
- }
- List<Bundle> sorted = new ArrayList<>();
- for (BundleRevision rev : RequirementSort.sort(revs)) {
- sorted.add(rev.getBundle());
- }
- if (sorted.isEmpty() && restart) {
- sorted.add(bundle);
- }
- return sorted;
+ @Override
+ public void resolveBundles(Set<Bundle> bundles) {
+ systemBundleContext.getBundle().adapt(FrameworkWiring.class).resolveBundles(bundles);
}
- protected List<Bundle> getBundlesToStop(Collection<Bundle> bundles) {
- SortedMap<Integer, Set<Bundle>> bundlesPerStartLevel = new TreeMap<>();
- for (Bundle bundle : bundles) {
- int sl = bundle.adapt(BundleStartLevel.class).getStartLevel();
- addToMapSet(bundlesPerStartLevel, sl, bundle);
- }
- bundles = bundlesPerStartLevel.get(bundlesPerStartLevel.lastKey());
-
- List<Bundle> bundlesToDestroy = new ArrayList<>();
- for (Bundle bundle : bundles) {
- ServiceReference[] references = bundle.getRegisteredServices();
- int usage = 0;
- if (references != null) {
- for (ServiceReference reference : references) {
- usage += getServiceUsage(reference, bundles);
- }
- }
- LOGGER.debug("Usage for bundle {} is {}", bundle, usage);
- if (usage == 0) {
- bundlesToDestroy.add(bundle);
- }
- }
- if (!bundlesToDestroy.isEmpty()) {
- Collections.sort(bundlesToDestroy, new Comparator<Bundle>() {
- public int compare(Bundle b1, Bundle b2) {
- return (int) (b2.getLastModified() - b1.getLastModified());
- }
- });
- LOGGER.debug("Selected bundles {} for destroy (no services in use)", bundlesToDestroy);
- } else {
- ServiceReference ref = null;
- for (Bundle bundle : bundles) {
- ServiceReference[] references = bundle.getRegisteredServices();
- for (ServiceReference reference : references) {
- if (getServiceUsage(reference, bundles) == 0) {
- continue;
- }
- if (ref == null || reference.compareTo(ref) < 0) {
- LOGGER.debug("Currently selecting bundle {} for destroy (with reference {})", bundle, reference);
- ref = reference;
+ @Override
+ public void replaceDigraph(Map<String, Map<String, Map<String, Set<String>>>> policies, Map<String, Set<Long>> bundles) throws BundleException, InvalidSyntaxException {
+ RegionDigraph temp = digraph.copy();
+ // Remove everything
+ for (Region region : temp.getRegions()) {
+ temp.removeRegion(region);
+ }
+ // Re-create regions
+ for (String name : policies.keySet()) {
+ temp.createRegion(name);
+ }
+ // Dispatch bundles
+ for (Map.Entry<String, Set<Long>> entry : bundles.entrySet()) {
+ Region region = temp.getRegion(entry.getKey());
+ for (long bundleId : entry.getValue()) {
+ region.addBundle(bundleId);
+ }
+ }
+ // Add policies
+ for (Map.Entry<String, Map<String, Map<String, Set<String>>>> entry1 : policies.entrySet()) {
+ Region region1 = temp.getRegion(entry1.getKey());
+ for (Map.Entry<String, Map<String, Set<String>>> entry2 : entry1.getValue().entrySet()) {
+ Region region2 = temp.getRegion(entry2.getKey());
+ RegionFilterBuilder rfb = temp.createRegionFilterBuilder();
+ for (Map.Entry<String, Set<String>> entry3 : entry2.getValue().entrySet()) {
+ for (String flt : entry3.getValue()) {
+ rfb.allow(entry3.getKey(), flt);
}
}
- }
- if (ref != null) {
- bundlesToDestroy.add(ref.getBundle());
- }
- LOGGER.debug("Selected bundle {} for destroy (lowest ranking service)", bundlesToDestroy);
- }
- return bundlesToDestroy;
- }
-
- private static int getServiceUsage(ServiceReference ref, Collection<Bundle> bundles) {
- Bundle[] usingBundles = ref.getUsingBundles();
- int nb = 0;
- if (usingBundles != null) {
- for (Bundle bundle : usingBundles) {
- if (bundles.contains(bundle)) {
- nb++;
- }
+ region1.connectRegion(region2, rfb.build());
}
}
- return nb;
+ digraph.replace(temp);
}
-
- protected InputStream getBundleInputStream(Resource resource, Map<String, StreamProvider> providers) throws IOException {
- String uri = getUri(resource);
- if (uri == null) {
- throw new IllegalStateException("Resource has no uri");
- }
- StreamProvider provider = providers.get(uri);
- if (provider == null) {
- throw new IllegalStateException("Resource " + uri + " has no StreamProvider");
- }
- return provider.open();
- }
-
- protected void refreshPackages(Collection<Bundle> bundles) throws InterruptedException {
- final CountDownLatch latch = new CountDownLatch(1);
- FrameworkWiring fw = systemBundleContext.getBundle().adapt(FrameworkWiring.class);
- fw.refreshBundles(bundles, new FrameworkListener() {
- @Override
- public void frameworkEvent(FrameworkEvent event) {
- if (event.getType() == FrameworkEvent.ERROR) {
- LOGGER.error("Framework error", event.getThrowable());
- }
- latch.countDown();
- }
- });
- latch.await();
- }
-
-
- static class Deployment {
- Map<Long, Long> bundleChecksums = new HashMap<>();
- Map<Resource, Bundle> resToBnd = new HashMap<>();
- Map<String, RegionDeployment> regions = new HashMap<>();
- }
-
- static class RegionDeployment {
- List<Resource> toInstall = new ArrayList<>();
- List<Bundle> toDelete = new ArrayList<>();
- Map<Bundle, Resource> toUpdate = new HashMap<>();
- }
-
}
http://git-wip-us.apache.org/repos/asf/karaf/blob/9e2ada3c/features/core/src/main/java/org/apache/karaf/features/internal/service/State.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/State.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/State.java
index fa1fd18..b1a3ccd 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/service/State.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/State.java
@@ -36,14 +36,30 @@ public class State {
public State copy() {
State state = new State();
- state.bootDone.set(bootDone.get());
- MapUtils.copy(repositories, state.repositories);
- MapUtils.copy(requestedFeatures, state.requestedFeatures);
- MapUtils.copy(installedFeatures, state.installedFeatures);
- MapUtils.copy(stateFeatures, state.stateFeatures);
- MapUtils.copy(managedBundles, state.managedBundles);
- MapUtils.copy(bundleChecksums, state.bundleChecksums);
+ copy(this, state, false);
return state;
}
+ public void replace(State state) {
+ copy(state, this, true);
+ }
+
+ private static void copy(State from, State to, boolean clear) {
+ if (clear) {
+ to.repositories.clear();
+ to.requestedFeatures.clear();
+ to.installedFeatures.clear();
+ to.stateFeatures.clear();
+ to.managedBundles.clear();
+ to.bundleChecksums.clear();
+ }
+ to.bootDone.set(from.bootDone.get());
+ MapUtils.copy(from.repositories, to.repositories);
+ MapUtils.copy(from.requestedFeatures, to.requestedFeatures);
+ MapUtils.copy(from.installedFeatures, to.installedFeatures);
+ MapUtils.copy(from.stateFeatures, to.stateFeatures);
+ MapUtils.copy(from.managedBundles, to.managedBundles);
+ MapUtils.copy(from.bundleChecksums, to.bundleChecksums);
+ }
+
}
http://git-wip-us.apache.org/repos/asf/karaf/blob/9e2ada3c/features/core/src/test/java/org/apache/karaf/features/FeaturesServiceTest.java
----------------------------------------------------------------------
diff --git a/features/core/src/test/java/org/apache/karaf/features/FeaturesServiceTest.java b/features/core/src/test/java/org/apache/karaf/features/FeaturesServiceTest.java
index 2609c8d..3a2f581 100644
--- a/features/core/src/test/java/org/apache/karaf/features/FeaturesServiceTest.java
+++ b/features/core/src/test/java/org/apache/karaf/features/FeaturesServiceTest.java
@@ -50,6 +50,7 @@ import org.junit.Before;
import org.junit.Test;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
+import org.osgi.framework.startlevel.FrameworkStartLevel;
public class FeaturesServiceTest extends TestBase {
private static final String FEATURE_WITH_INVALID_BUNDLE = "<features name='test' xmlns='http://karaf.apache.org/xmlns/features/v1.0.0'>"
@@ -363,8 +364,14 @@ public class FeaturesServiceTest extends TestBase {
URI uri = createTempRepo(FEATURE_WITH_INVALID_BUNDLE, bundle1Uri, bundle2Uri);
BundleContext bundleContext = EasyMock.createMock(BundleContext.class);
+ Bundle bundle = EasyMock.createMock(Bundle.class);
+ FrameworkStartLevel fsl = EasyMock.createMock(FrameworkStartLevel.class);
expect(bundleContext.getBundles()).andReturn(new Bundle[0]);
- replay(bundleContext);
+ expect(bundleContext.getBundle()).andReturn(bundle);
+ expect(bundle.adapt(FrameworkStartLevel.class)).andReturn(fsl);
+ expect(fsl.getInitialBundleStartLevel()).andReturn(50);
+ expect(fsl.getStartLevel()).andReturn(100);
+ replay(bundleContext, bundle, fsl);
FeaturesServiceImpl svc = new FeaturesServiceImpl(null, bundleContext, new Storage(), null, null, null, null, null, null, null, null, null);
svc.addRepository(uri);
http://git-wip-us.apache.org/repos/asf/karaf/blob/9e2ada3c/features/core/src/test/java/org/apache/karaf/features/internal/region/SubsystemTest.java
----------------------------------------------------------------------
diff --git a/features/core/src/test/java/org/apache/karaf/features/internal/region/SubsystemTest.java b/features/core/src/test/java/org/apache/karaf/features/internal/region/SubsystemTest.java
index 5aabcb7..dc371a1 100644
--- a/features/core/src/test/java/org/apache/karaf/features/internal/region/SubsystemTest.java
+++ b/features/core/src/test/java/org/apache/karaf/features/internal/region/SubsystemTest.java
@@ -32,9 +32,8 @@ import java.util.Set;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
-import org.apache.karaf.features.Repository;
+import org.apache.karaf.features.FeaturesService;
import org.apache.karaf.features.internal.download.StreamProvider;
-import org.apache.karaf.features.internal.service.FeaturesServiceImpl;
import org.apache.karaf.features.internal.service.RepositoryImpl;
import org.apache.karaf.features.internal.download.simple.SimpleDownloader;
import org.junit.Test;
@@ -67,7 +66,7 @@ public class SubsystemTest {
features,
Collections.<String, Set<BundleRevision>>emptyMap(),
Collections.<String>emptySet(),
- FeaturesServiceImpl.DEFAULT_FEATURE_RESOLUTION_RANGE,
+ FeaturesService.DEFAULT_FEATURE_RESOLUTION_RANGE,
null);
verify(resolver, expected);
@@ -98,7 +97,7 @@ public class SubsystemTest {
features,
Collections.<String, Set<BundleRevision>>emptyMap(),
Collections.<String>emptySet(),
- FeaturesServiceImpl.DEFAULT_FEATURE_RESOLUTION_RANGE,
+ FeaturesService.DEFAULT_FEATURE_RESOLUTION_RANGE,
null);
verify(resolver, expected);
@@ -119,7 +118,7 @@ public class SubsystemTest {
features,
Collections.<String, Set<BundleRevision>>emptyMap(),
Collections.singleton("b"),
- FeaturesServiceImpl.DEFAULT_FEATURE_RESOLUTION_RANGE,
+ FeaturesService.DEFAULT_FEATURE_RESOLUTION_RANGE,
null);
verify(resolver, expected);
@@ -139,7 +138,7 @@ public class SubsystemTest {
features,
Collections.<String, Set<BundleRevision>>emptyMap(),
Collections.<String>emptySet(),
- FeaturesServiceImpl.DEFAULT_FEATURE_RESOLUTION_RANGE,
+ FeaturesService.DEFAULT_FEATURE_RESOLUTION_RANGE,
null);
verify(resolver, expected);
@@ -161,7 +160,7 @@ public class SubsystemTest {
features,
Collections.<String, Set<BundleRevision>>emptyMap(),
Collections.<String>emptySet(),
- FeaturesServiceImpl.DEFAULT_FEATURE_RESOLUTION_RANGE,
+ FeaturesService.DEFAULT_FEATURE_RESOLUTION_RANGE,
null);
verify(resolver, expected);
[5/7] git commit: [KARAF-2888] Extract the deployment for easier unit
testing
Posted by gn...@apache.org.
[KARAF-2888] Extract the deployment for easier unit testing
Project: http://git-wip-us.apache.org/repos/asf/karaf/repo
Commit: http://git-wip-us.apache.org/repos/asf/karaf/commit/9e2ada3c
Tree: http://git-wip-us.apache.org/repos/asf/karaf/tree/9e2ada3c
Diff: http://git-wip-us.apache.org/repos/asf/karaf/diff/9e2ada3c
Branch: refs/heads/master
Commit: 9e2ada3cc655e9ed245e3bf0c97fc3723358c6f0
Parents: 3d20404
Author: Guillaume Nodet <gn...@gmail.com>
Authored: Tue Apr 29 22:32:54 2014 +0200
Committer: Guillaume Nodet <gn...@gmail.com>
Committed: Wed Apr 30 20:04:27 2014 +0200
----------------------------------------------------------------------
.../apache/karaf/features/FeaturesService.java | 12 +-
.../karaf/features/internal/osgi/Activator.java | 6 +-
.../features/internal/service/Deployer.java | 1112 +++++++++++++++++
.../internal/service/FeaturesServiceImpl.java | 1171 ++----------------
.../karaf/features/internal/service/State.java | 30 +-
.../karaf/features/FeaturesServiceTest.java | 9 +-
.../features/internal/region/SubsystemTest.java | 13 +-
7 files changed, 1285 insertions(+), 1068 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/karaf/blob/9e2ada3c/features/core/src/main/java/org/apache/karaf/features/FeaturesService.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/FeaturesService.java b/features/core/src/main/java/org/apache/karaf/features/FeaturesService.java
index 4366036..e266402 100644
--- a/features/core/src/main/java/org/apache/karaf/features/FeaturesService.java
+++ b/features/core/src/main/java/org/apache/karaf/features/FeaturesService.java
@@ -26,7 +26,17 @@ import java.util.Set;
*/
public interface FeaturesService {
- public static final String ROOT_REGION = "root";
+ String ROOT_REGION = "root";
+
+ String UPDATE_SNAPSHOTS_NONE = "none";
+ String UPDATE_SNAPSHOTS_CRC = "crc";
+ String DEFAULT_UPDATE_SNAPSHOTS = UPDATE_SNAPSHOTS_CRC;
+ String UPDATE_SNAPSHOTS_ALWAYS = "always";
+
+ String DEFAULT_FEATURE_RESOLUTION_RANGE = "${range;[====,====]}";
+ String DEFAULT_BUNDLE_UPDATE_RANGE = "${range;[==,=+)}";
+
+ String UPDATEABLE_URIS = "mvn:.*SNAPSHOT|(?!mvn:).*";
enum Option {
NoFailOnFeatureNotFound,
http://git-wip-us.apache.org/repos/asf/karaf/blob/9e2ada3c/features/core/src/main/java/org/apache/karaf/features/internal/osgi/Activator.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/osgi/Activator.java b/features/core/src/main/java/org/apache/karaf/features/internal/osgi/Activator.java
index 1be87fc..5db6204 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/osgi/Activator.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/osgi/Activator.java
@@ -159,9 +159,9 @@ public class Activator extends BaseActivator {
FeatureConfigInstaller configInstaller = new FeatureConfigInstaller(configurationAdmin);
String overrides = getString("overrides", new File(System.getProperty("karaf.etc"), "overrides.properties").toURI().toString());
- String featureResolutionRange = getString("featureResolutionRange", FeaturesServiceImpl.DEFAULT_FEATURE_RESOLUTION_RANGE);
- String bundleUpdateRange = getString("bundleUpdateRange", FeaturesServiceImpl.DEFAULT_BUNDLE_UPDATE_RANGE);
- String updateSnapshots = getString("updateSnapshots", FeaturesServiceImpl.DEFAULT_UPDATE_SNAPSHOTS);
+ String featureResolutionRange = getString("featureResolutionRange", FeaturesService.DEFAULT_FEATURE_RESOLUTION_RANGE);
+ String bundleUpdateRange = getString("bundleUpdateRange", FeaturesService.DEFAULT_BUNDLE_UPDATE_RANGE);
+ String updateSnapshots = getString("updateSnapshots", FeaturesService.DEFAULT_UPDATE_SNAPSHOTS);
StateStorage stateStorage = new StateStorage() {
@Override
protected InputStream getInputStream() throws IOException {
http://git-wip-us.apache.org/repos/asf/karaf/blob/9e2ada3c/features/core/src/main/java/org/apache/karaf/features/internal/service/Deployer.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/Deployer.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/Deployer.java
new file mode 100644
index 0000000..e26a07a
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/Deployer.java
@@ -0,0 +1,1112 @@
+/*
+ * 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.karaf.features.internal.service;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import org.apache.felix.utils.version.VersionRange;
+import org.apache.karaf.features.BundleInfo;
+import org.apache.karaf.features.Feature;
+import org.apache.karaf.features.FeatureEvent;
+import org.apache.karaf.features.FeaturesService;
+import org.apache.karaf.features.internal.download.StreamProvider;
+import org.apache.karaf.features.internal.region.SubsystemResolver;
+import org.apache.karaf.features.internal.util.ChecksumUtils;
+import org.apache.karaf.features.internal.util.Macro;
+import org.apache.karaf.features.internal.util.MapUtils;
+import org.apache.karaf.features.internal.util.MultiException;
+import org.apache.karaf.util.collections.CopyOnWriteArrayIdentityList;
+import org.eclipse.equinox.region.Region;
+import org.eclipse.equinox.region.RegionDigraph;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.Constants;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.Version;
+import org.osgi.framework.startlevel.BundleStartLevel;
+import org.osgi.framework.wiring.BundleRevision;
+import org.osgi.framework.wiring.BundleWire;
+import org.osgi.framework.wiring.BundleWiring;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Resource;
+import org.osgi.resource.Wire;
+import org.osgi.service.repository.Repository;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.apache.felix.resolver.Util.getSymbolicName;
+import static org.apache.felix.resolver.Util.getVersion;
+import static org.apache.karaf.features.FeaturesService.ROOT_REGION;
+import static org.apache.karaf.features.internal.resolver.ResourceUtils.TYPE_SUBSYSTEM;
+import static org.apache.karaf.features.internal.resolver.ResourceUtils.getFeatureId;
+import static org.apache.karaf.features.internal.resolver.ResourceUtils.getUri;
+import static org.apache.karaf.features.FeaturesService.UPDATEABLE_URIS;
+import static org.apache.karaf.features.FeaturesService.UPDATE_SNAPSHOTS_ALWAYS;
+import static org.apache.karaf.features.FeaturesService.UPDATE_SNAPSHOTS_CRC;
+import static org.apache.karaf.features.internal.util.MapUtils.add;
+import static org.apache.karaf.features.internal.util.MapUtils.addToMapSet;
+import static org.apache.karaf.features.internal.util.MapUtils.apply;
+import static org.apache.karaf.features.internal.util.MapUtils.copy;
+import static org.apache.karaf.features.internal.util.MapUtils.diff;
+import static org.apache.karaf.features.internal.util.MapUtils.flatten;
+import static org.apache.karaf.features.internal.util.MapUtils.map;
+import static org.apache.karaf.features.internal.util.MapUtils.removeFromMapSet;
+import static org.osgi.framework.Bundle.ACTIVE;
+import static org.osgi.framework.Bundle.RESOLVED;
+import static org.osgi.framework.Bundle.STARTING;
+import static org.osgi.framework.Bundle.STOPPING;
+import static org.osgi.framework.Bundle.STOP_TRANSIENT;
+import static org.osgi.framework.Bundle.UNINSTALLED;
+import static org.osgi.framework.namespace.IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE;
+import static org.osgi.framework.namespace.IdentityNamespace.IDENTITY_NAMESPACE;
+import static org.osgi.resource.Namespace.CAPABILITY_EFFECTIVE_DIRECTIVE;
+import static org.osgi.resource.Namespace.EFFECTIVE_ACTIVE;
+
+public class Deployer {
+
+ public interface DeployCallback {
+
+ void print(String message, boolean verbose);
+
+ void saveState(State state);
+ void persistResolveRequest(DeploymentRequest request) throws IOException;
+ void installFeatureConfigs(Feature feature) throws IOException, InvalidSyntaxException;
+ void callListeners(FeatureEvent featureEvent);
+
+ Bundle installBundle(String region, String uri, InputStream is) throws BundleException;
+ void updateBundle(Bundle bundle, InputStream is) throws BundleException;
+ void uninstall(Bundle bundle) throws BundleException;
+ void startBundle(Bundle bundle) throws BundleException;
+ void stopBundle(Bundle bundle, int options) throws BundleException;
+ void setBundleStartLevel(Bundle bundle, int startLevel);
+
+ void refreshPackages(Collection<Bundle> bundles) throws InterruptedException;
+ void resolveBundles(Set<Bundle> bundles);
+
+ void replaceDigraph(Map<String, Map<String,Map<String,Set<String>>>> policies, Map<String, Set<Long>> bundles) throws BundleException, InvalidSyntaxException;
+ }
+
+ static class DeploymentState {
+ State state;
+ Bundle serviceBundle;
+ int initialBundleStartLevel;
+ int currentStartLevel;
+ Map<Long, Bundle> bundles;
+ Map<String, Feature> features;
+ Map<String, Set<Long>> bundlesPerRegion;
+ Map<String, Map<String, Map<String, Set<String>>>> filtersPerRegion;
+ }
+
+ static class DeploymentRequest {
+ String overrides;
+ String featureResolutionRange;
+ String bundleUpdateRange;
+ String updateSnaphots;
+ Repository globalRepository;
+
+ Map<String, Set<String>> requestedFeatures;
+ Map<String, Map<String, FeaturesService.RequestedState>> stateChanges;
+ EnumSet<FeaturesService.Option> options;
+ }
+
+ static class Deployment {
+ Map<Long, Long> bundleChecksums = new HashMap<>();
+ Map<Resource, Bundle> resToBnd = new HashMap<>();
+ Map<String, RegionDeployment> regions = new HashMap<>();
+ }
+
+ static class RegionDeployment {
+ List<Resource> toInstall = new ArrayList<>();
+ List<Bundle> toDelete = new ArrayList<>();
+ Map<Bundle, Resource> toUpdate = new HashMap<>();
+ }
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(Deployer.class);
+
+ private final DeployCallback callback;
+
+ public Deployer(DeployCallback callback) {
+ this.callback = callback;
+ }
+
+ /**
+ *
+ * @param dstate deployment state
+ * @param request deployment request
+ * @throws Exception
+ */
+ public void deploy(DeploymentState dstate, DeploymentRequest request) throws Exception {
+
+ boolean noRefreshUnmanaged = request.options.contains(FeaturesService.Option.NoAutoRefreshUnmanagedBundles);
+ boolean noRefreshManaged = request.options.contains(FeaturesService.Option.NoAutoRefreshManagedBundles);
+ boolean noRefresh = request.options.contains(FeaturesService.Option.NoAutoRefreshBundles);
+ boolean noStart = request.options.contains(FeaturesService.Option.NoAutoStartBundles);
+ boolean verbose = request.options.contains(FeaturesService.Option.Verbose);
+ boolean simulate = request.options.contains(FeaturesService.Option.Simulate);
+ boolean noManageBundles = request.options.contains(FeaturesService.Option.NoAutoManageBundles);
+
+ // TODO: add an option to unmanage bundles instead of uninstalling those
+
+ Map<String, Set<Long>> managedBundles = copy(dstate.state.managedBundles);
+
+ Map<String, Set<Bundle>> unmanagedBundles = apply(diff(dstate.bundlesPerRegion, dstate.state.managedBundles),
+ map(dstate.bundles));
+
+ // Resolve
+ // TODO: requirements
+ // TODO: bundles
+
+ SubsystemResolver resolver = new SubsystemResolver();
+ resolver.resolve(
+ dstate.features.values(),
+ request.requestedFeatures,
+ apply(unmanagedBundles, adapt(BundleRevision.class)),
+ Overrides.loadOverrides(request.overrides),
+ request.featureResolutionRange,
+ request.globalRepository);
+
+ Map<String, StreamProvider> providers = resolver.getProviders();
+ Map<String, Set<Resource>> featuresPerRegion = resolver.getFeaturesPerRegions();
+ Map<String, Set<String>> installedFeatures = apply(featuresPerRegion, featureId());
+ Map<String, Set<String>> newFeatures = diff(installedFeatures, dstate.state.installedFeatures);
+ Map<String, Set<String>> delFeatures = diff(dstate.state.installedFeatures, installedFeatures);
+
+ //
+ // Compute requested features state
+ //
+ Map<String, Map<String, String>> stateFeatures = copy(dstate.state.stateFeatures);
+ for (Map.Entry<String, Set<String>> entry : delFeatures.entrySet()) {
+ Map<String, String> map = stateFeatures.get(entry.getKey());
+ if (map != null) {
+ map.entrySet().removeAll(entry.getValue());
+ if (map.isEmpty()) {
+ stateFeatures.remove(entry.getKey());
+ }
+ }
+ }
+ for (Map.Entry<String, Map<String, FeaturesService.RequestedState>> entry1 : request.stateChanges.entrySet()) {
+ String region = entry1.getKey();
+ Map<String, String> regionStates = stateFeatures.get(region);
+ if (regionStates != null) {
+ for (Map.Entry<String, FeaturesService.RequestedState> entry2 : entry1.getValue().entrySet()) {
+ String feature = entry2.getKey();
+ if (regionStates.containsKey(feature)) {
+ regionStates.put(feature, entry2.getValue().name());
+ }
+ }
+ }
+ }
+ for (Map.Entry<String, Set<String>> entry : newFeatures.entrySet()) {
+ for (String feature : entry.getValue()) {
+ Map<String, String> map = stateFeatures.get(entry.getKey());
+ if (map == null) {
+ map = new HashMap<>();
+ stateFeatures.put(entry.getKey(), map);
+ }
+ map.put(feature, noStart ? FeaturesService.RequestedState.Installed.name() : FeaturesService.RequestedState.Started.name());
+ }
+ }
+
+ // Compute information for each bundle
+ Map<String, Map<String, BundleInfo>> bundleInfos = resolver.getBundleInfos();
+
+ //
+ // Compute deployment
+ //
+ Deployer.Deployment deployment = computeDeployment(dstate, request, resolver);
+
+ //
+ // Compute the set of bundles to refresh
+ //
+ Set<Bundle> toRefresh = new TreeSet<>(new BundleComparator()); // sort is only used for display
+ for (Deployer.RegionDeployment regionDeployment : deployment.regions.values()) {
+ toRefresh.addAll(regionDeployment.toDelete);
+ toRefresh.addAll(regionDeployment.toUpdate.keySet());
+ }
+ if (!noRefreshManaged) {
+ computeBundlesToRefresh(toRefresh, dstate.bundles.values(), deployment.resToBnd, resolver.getWiring());
+ }
+ if (noRefreshUnmanaged) {
+ toRefresh.removeAll(flatten(unmanagedBundles));
+ }
+
+ // Automatically turn unmanaged bundles into managed bundles
+ // if they are required by a feature and no other unmanaged
+ // bundles have a requirement on it
+ Set<Bundle> toManage = new TreeSet<>(new BundleComparator()); // sort is only used for display
+ if (!noManageBundles) {
+ Set<Resource> features = resolver.getFeatures().keySet();
+ Set<? extends Resource> unmanaged = apply(flatten(unmanagedBundles), adapt(BundleRevision.class));
+ Set<Resource> requested = new HashSet<>();
+ // Gather bundles required by a feature
+ for (List<Wire> wires : resolver.getWiring().values()) {
+ for (Wire wire : wires) {
+ if (features.contains(wire.getRequirer()) && unmanaged.contains(wire.getProvider())) {
+ requested.add(wire.getProvider());
+ }
+ }
+ }
+ // Now, we know which bundles are completely unmanaged
+ unmanaged.removeAll(requested);
+ // Check if bundles have wires from really unmanaged bundles
+ for (List<Wire> wires : resolver.getWiring().values()) {
+ for (Wire wire : wires) {
+ if (requested.contains(wire.getProvider()) && unmanaged.contains(wire.getRequirer())) {
+ requested.remove(wire.getProvider());
+ }
+ }
+ }
+ if (!requested.isEmpty()) {
+ Map<Long, String> bundleToRegion = new HashMap<>();
+ for (Map.Entry<String, Set<Long>> entry : dstate.bundlesPerRegion.entrySet()) {
+ for (long id : entry.getValue()) {
+ bundleToRegion.put(id, entry.getKey());
+ }
+ }
+ for (Resource rev : requested) {
+ Bundle bundle = ((BundleRevision) rev).getBundle();
+ long id = bundle.getBundleId();
+ addToMapSet(managedBundles, bundleToRegion.get(id), id);
+ toManage.add(bundle);
+ }
+ }
+ }
+
+ //
+ // Log deployment
+ //
+ logDeployment(deployment, verbose);
+
+ if (simulate) {
+ if (!noRefresh && !toRefresh.isEmpty()) {
+ print(" Bundles to refresh:", verbose);
+ for (Bundle bundle : toRefresh) {
+ print(" " + bundle.getSymbolicName() + " / " + bundle.getVersion(), verbose);
+ }
+ }
+ if (!toManage.isEmpty()) {
+ print(" Managing bundle:", verbose);
+ for (Bundle bundle : toManage) {
+ print(" " + bundle.getSymbolicName() + " / " + bundle.getVersion(), verbose);
+ }
+ }
+ return;
+ }
+
+ Set<Bundle> toStart = new HashSet<>();
+ Set<Bundle> toResolve = new HashSet<>();
+ Set<Bundle> toStop = new HashSet<>();
+
+ //
+ // Execute deployment
+ //
+ // #1: stop bundles that needs to be updated or uninstalled in order
+ // #2: uninstall needed bundles
+ // #3: update regions
+ // #4: update bundles
+ // #5: install bundles
+ // #6: save state
+ // #7: install configuration
+ // #8: refresh bundles
+ // #9: start bundles in order
+ // #10: send events
+ //
+
+ //
+ // Compute bundle states
+ //
+ Map<Resource, FeaturesService.RequestedState> states = new HashMap<>();
+ for (Map.Entry<String, Set<Resource>> entry : resolver.getFeaturesPerRegions().entrySet()) {
+ String region = entry.getKey();
+ Map<String, String> fss = stateFeatures.get(region);
+ for (Resource feature : entry.getValue()) {
+ String fs = fss.get(getFeatureId(feature));
+ propagateState(states, feature, FeaturesService.RequestedState.valueOf(fs), resolver);
+ }
+ }
+ states.keySet().retainAll(resolver.getBundles().keySet());
+ //
+ // Compute bundles to start, stop and resolve
+ //
+ for (Map.Entry<Resource, FeaturesService.RequestedState> entry : states.entrySet()) {
+ Bundle bundle = deployment.resToBnd.get(entry.getKey());
+ if (bundle != null) {
+ switch (entry.getValue()) {
+ case Started:
+ toResolve.add(bundle);
+ toStart.add(bundle);
+ break;
+ case Resolved:
+ toResolve.add(bundle);
+ toStop.add(bundle);
+ break;
+ }
+ }
+ }
+ //
+ // Compute bundle all start levels and start levels to update
+ //
+ Map<Resource, Integer> startLevels = new HashMap<>();
+ Map<Bundle, Integer> toUpdateStartLevel = new HashMap<>();
+ for (Map.Entry<String, Set<Resource>> entry : resolver.getBundlesPerRegions().entrySet()) {
+ String region = entry.getKey();
+ for (Resource resource : entry.getValue()) {
+ BundleInfo bi = bundleInfos.get(region).get(getUri(resource));
+ if (bi != null) {
+ int sl = bi.getStartLevel() > 0 ? bi.getStartLevel() : dstate.initialBundleStartLevel;
+ startLevels.put(resource, sl);
+ Bundle bundle = deployment.resToBnd.get(resource);
+ if (bundle != null) {
+ int curSl = bundle.adapt(BundleStartLevel.class).getStartLevel();
+ if (sl != curSl) {
+ toUpdateStartLevel.put(bundle, sl);
+ if (sl > dstate.currentStartLevel) {
+ toStop.add(bundle);
+ }
+ }
+ }
+ }
+ }
+ }
+
+
+ //
+ // Handle updates on the FeaturesService bundle
+ //
+ Deployer.RegionDeployment rootRegionDeployment = deployment.regions.get(ROOT_REGION);
+ // We don't support uninstalling the bundle
+ if (rootRegionDeployment != null && rootRegionDeployment.toDelete.contains(dstate.serviceBundle)) {
+ throw new UnsupportedOperationException("Uninstalling the FeaturesService bundle is not supported");
+ }
+ // If the bundle needs to be updated, do the following:
+ // - persist the request to indicate the resolution must be continued after restart
+ // - update the checksum and save the state
+ // - compute bundles wired to the FeaturesService bundle that will be refreshed
+ // - stop the bundle
+ // - update the bundle
+ // - refresh wired bundles
+ // - start the bundle
+ // - exit
+ // When restarting, the resolution will be attempted again
+ if (rootRegionDeployment != null && rootRegionDeployment.toUpdate.containsKey(dstate.serviceBundle)) {
+ callback.persistResolveRequest(request);
+ // If the bundle is updated because of a different checksum,
+ // save the new checksum persistently
+ if (deployment.bundleChecksums.containsKey(dstate.serviceBundle.getBundleId())) {
+ State state = dstate.state.copy();
+ state.bundleChecksums.put(dstate.serviceBundle.getBundleId(),
+ deployment.bundleChecksums.get(dstate.serviceBundle.getBundleId()));
+ callback.saveState(state);
+ }
+ Resource resource = rootRegionDeployment.toUpdate.get(dstate.serviceBundle);
+ String uri = getUri(resource);
+ print("The FeaturesService bundle needs is being updated with " + uri, verbose);
+ toRefresh.clear();
+ toRefresh.add(dstate.serviceBundle);
+ computeBundlesToRefresh(toRefresh,
+ dstate.bundles.values(),
+ Collections.<Resource, Bundle>emptyMap(),
+ Collections.<Resource, List<Wire>>emptyMap());
+ callback.stopBundle(dstate.serviceBundle, STOP_TRANSIENT);
+ try (
+ InputStream is = getBundleInputStream(resource, providers)
+ ) {
+ callback.updateBundle(dstate.serviceBundle, is);
+ }
+ callback.refreshPackages(toRefresh);
+ callback.startBundle(dstate.serviceBundle);
+ return;
+ }
+
+ //
+ // Perform bundle operations
+ //
+
+ //
+ // Stop bundles by chunks
+ //
+ for (Deployer.RegionDeployment regionDeployment : deployment.regions.values()) {
+ toStop.addAll(regionDeployment.toUpdate.keySet());
+ toStop.addAll(regionDeployment.toDelete);
+ }
+ removeFragmentsAndBundlesInState(toStop, UNINSTALLED | RESOLVED | STOPPING);
+ if (!toStop.isEmpty()) {
+ print("Stopping bundles:", verbose);
+ while (!toStop.isEmpty()) {
+ List<Bundle> bs = getBundlesToStop(toStop);
+ for (Bundle bundle : bs) {
+ print(" " + bundle.getSymbolicName() + " / " + bundle.getVersion(), verbose);
+ // If the bundle start level will be changed, stop it persistently to
+ // avoid a restart when the start level is actually changed
+ callback.stopBundle(bundle, toUpdateStartLevel.containsKey(bundle) ? 0 : STOP_TRANSIENT);
+ toStop.remove(bundle);
+ }
+ }
+ }
+
+ //
+ // Delete bundles
+ //
+ boolean hasToDelete = false;
+ for (Deployer.RegionDeployment regionDeployment : deployment.regions.values()) {
+ if (hasToDelete = !regionDeployment.toDelete.isEmpty()) {
+ break;
+ }
+ }
+ if (hasToDelete) {
+ print("Uninstalling bundles:", verbose);
+ for (Map.Entry<String, Deployer.RegionDeployment> entry : deployment.regions.entrySet()) {
+ String name = entry.getKey();
+ Deployer.RegionDeployment regionDeployment = entry.getValue();
+ for (Bundle bundle : regionDeployment.toDelete) {
+ print(" " + bundle.getSymbolicName() + " / " + bundle.getVersion(), verbose);
+ callback.uninstall(bundle);
+ removeFromMapSet(managedBundles, name, bundle.getBundleId());
+ }
+ }
+ }
+
+ //
+ // Update regions
+ //
+ {
+ // Add bundles
+ Map<String, Set<Long>> bundles = new HashMap<>();
+ add(bundles, apply(unmanagedBundles, bundleId()));
+ add(bundles, managedBundles);
+ // Compute policies
+ RegionDigraph computedDigraph = resolver.getFlatDigraph();
+ Map<String, Map<String, Map<String, Set<String>>>> policies = copy(dstate.filtersPerRegion);
+ // Only keep regions which still have bundles
+ policies.keySet().retainAll(bundles.keySet());
+ // Fix broken filters
+ for (String name : policies.keySet()) {
+ policies.get(name).keySet().retainAll(policies.keySet());
+ }
+ // Update managed regions
+ for (Region computedRegion : computedDigraph.getRegions()) {
+ String name = computedRegion.getName();
+ Map<String, Map<String, Set<String>>> policy = policies.get(name);
+ if (policy == null) {
+ policy = new HashMap<>();
+ policies.put(name, policy);
+ }
+ for (RegionDigraph.FilteredRegion fr : computedRegion.getEdges()) {
+ String r2 = fr.getRegion().getName();
+ Map<String, Set<String>> filters = new HashMap<>();
+ Map<String, Collection<String>> current = fr.getFilter().getSharingPolicy();
+ for (String ns : current.keySet()) {
+ for (String f : current.get(ns)) {
+ addToMapSet(filters, ns, f);
+ }
+ }
+ policy.put(r2, filters);
+ }
+ }
+ // Apply all changes
+ callback.replaceDigraph(policies, bundles);
+ }
+
+
+ //
+ // Update bundles
+ //
+ boolean hasToUpdate = false;
+ for (Deployer.RegionDeployment regionDeployment : deployment.regions.values()) {
+ if (hasToUpdate = !regionDeployment.toUpdate.isEmpty()) {
+ break;
+ }
+ }
+ if (hasToUpdate) {
+ print("Updating bundles:", verbose);
+ for (Map.Entry<String, Deployer.RegionDeployment> rde : deployment.regions.entrySet()) {
+ for (Map.Entry<Bundle, Resource> entry : rde.getValue().toUpdate.entrySet()) {
+ Bundle bundle = entry.getKey();
+ Resource resource = entry.getValue();
+ String uri = getUri(resource);
+ print(" " + uri, verbose);
+ try (
+ InputStream is = getBundleInputStream(resource, providers)
+ ) {
+ callback.updateBundle(bundle, is);
+ }
+ toStart.add(bundle);
+ }
+ }
+ }
+ //
+ // Update start levels
+ //
+ for (Map.Entry<Bundle, Integer> entry : toUpdateStartLevel.entrySet()) {
+ Bundle bundle = entry.getKey();
+ int sl = entry.getValue();
+ callback.setBundleStartLevel(bundle, sl);
+ }
+ //
+ // Install bundles
+ //
+ boolean hasToInstall = false;
+ for (Deployer.RegionDeployment regionDeployment : deployment.regions.values()) {
+ if (hasToInstall = !regionDeployment.toInstall.isEmpty()) {
+ break;
+ }
+ }
+ if (hasToInstall) {
+ print("Installing bundles:", verbose);
+ for (Map.Entry<String, Deployer.RegionDeployment> entry : deployment.regions.entrySet()) {
+ String name = entry.getKey();
+ Deployer.RegionDeployment regionDeployment = entry.getValue();
+ for (Resource resource : regionDeployment.toInstall) {
+ String uri = getUri(resource);
+ print(" " + uri, verbose);
+ Bundle bundle;
+ long crc;
+ try (
+ ChecksumUtils.CRCInputStream is = new ChecksumUtils.CRCInputStream(getBundleInputStream(resource, providers))
+ ) {
+ bundle = callback.installBundle(name, uri, is);
+ crc = is.getCRC();
+ }
+ addToMapSet(managedBundles, name, bundle.getBundleId());
+ deployment.resToBnd.put(resource, bundle);
+ // save a checksum of installed snapshot bundle
+ if (UPDATE_SNAPSHOTS_CRC.equals(request.updateSnaphots)
+ && isUpdateable(resource) && !deployment.bundleChecksums.containsKey(bundle.getBundleId())) {
+ deployment.bundleChecksums.put(bundle.getBundleId(), crc);
+ }
+ int startLevel = startLevels.get(resource);
+ callback.setBundleStartLevel(bundle, startLevel);
+ FeaturesService.RequestedState reqState = states.get(resource);
+ switch (reqState) {
+ case Started:
+ toResolve.add(bundle);
+ toStart.add(bundle);
+ break;
+ case Resolved:
+ toResolve.add(bundle);
+ break;
+ }
+ }
+ }
+ }
+
+ //
+ // Update and save state
+ //
+ State newState = new State();
+ newState.bundleChecksums.putAll(deployment.bundleChecksums);
+ newState.requestedFeatures.putAll(request.requestedFeatures);
+ newState.installedFeatures.putAll(installedFeatures);
+ newState.stateFeatures.putAll(stateFeatures);
+ newState.managedBundles.putAll(managedBundles);
+ callback.saveState(newState);
+
+ //
+ // Install configurations
+ //
+ if (!newFeatures.isEmpty()) {
+ Set<Feature> set = apply(flatten(newFeatures), map(dstate.features));
+ for (Feature feature : set) {
+ callback.installFeatureConfigs(feature);
+ }
+ }
+
+ // TODO: remove this hack, but it avoids loading the class after the bundle is refreshed
+ new CopyOnWriteArrayIdentityList().iterator();
+ RequirementSort.sort(Collections.<Resource>emptyList());
+
+ if (!noRefresh) {
+ toStop = new HashSet<>();
+ toStop.addAll(toRefresh);
+ removeFragmentsAndBundlesInState(toStop, UNINSTALLED | RESOLVED | STOPPING);
+ if (!toStop.isEmpty()) {
+ print("Stopping bundles:", verbose);
+ while (!toStop.isEmpty()) {
+ List<Bundle> bs = getBundlesToStop(toStop);
+ for (Bundle bundle : bs) {
+ print(" " + bundle.getSymbolicName() + " / " + bundle.getVersion(), verbose);
+ callback.stopBundle(bundle, STOP_TRANSIENT);
+ toStop.remove(bundle);
+ toStart.add(bundle);
+ }
+ }
+ }
+
+ if (!toRefresh.isEmpty()) {
+ print("Refreshing bundles:", verbose);
+ for (Bundle bundle : toRefresh) {
+ print(" " + bundle.getSymbolicName() + " / " + bundle.getVersion(), verbose);
+ }
+ if (!toRefresh.isEmpty()) {
+ callback.refreshPackages(toRefresh);
+ }
+ }
+ }
+
+ // Resolve bundles
+ toResolve.addAll(toStart);
+ toResolve.addAll(toRefresh);
+ removeFragmentsAndBundlesInState(toResolve, UNINSTALLED);
+ callback.resolveBundles(toResolve);
+
+ // Compute bundles to start
+ removeFragmentsAndBundlesInState(toStart, UNINSTALLED | ACTIVE | STARTING);
+ if (!toStart.isEmpty()) {
+ // Compute correct start order
+ List<Exception> exceptions = new ArrayList<>();
+ print("Starting bundles:", verbose);
+ while (!toStart.isEmpty()) {
+ List<Bundle> bs = getBundlesToStart(toStart, dstate.serviceBundle);
+ for (Bundle bundle : bs) {
+ print(" " + bundle.getSymbolicName() + " / " + bundle.getVersion(), verbose);
+ try {
+ callback.startBundle(bundle);
+ } catch (BundleException e) {
+ exceptions.add(e);
+ }
+ toStart.remove(bundle);
+ }
+ }
+ if (!exceptions.isEmpty()) {
+ throw new MultiException("Error restarting bundles", exceptions);
+ }
+ }
+
+ // Call listeners
+ // TODO: add region information and avoid flattening
+ for (Feature feature : apply(flatten(delFeatures), map(dstate.features))) {
+ callback.callListeners(new FeatureEvent(feature, FeatureEvent.EventType.FeatureUninstalled, false));
+ }
+ for (Feature feature : apply(flatten(newFeatures), map(dstate.features))) {
+ callback.callListeners(new FeatureEvent(feature, FeatureEvent.EventType.FeatureInstalled, false));
+ }
+
+ print("Done.", verbose);
+ }
+
+ private void propagateState(Map<Resource, FeaturesService.RequestedState> states, Resource resource, FeaturesService.RequestedState state, SubsystemResolver resolver) {
+ if (!isSubsystem(resource)) {
+ FeaturesService.RequestedState reqState = mergeStates(state, states.get(resource));
+ if (reqState != states.get(resource)) {
+ states.put(resource, reqState);
+ for (Wire wire : resolver.getWiring().get(resource)) {
+ Resource provider = wire.getProvider();
+ FeaturesService.RequestedState stateToMerge;
+ String region = resolver.getBundles().get(provider);
+ BundleInfo bi = region != null ? resolver.getBundleInfos().get(region).get(getUri(provider)) : null;
+ if (reqState == FeaturesService.RequestedState.Started) {
+ String effective = wire.getCapability().getDirectives().get(CAPABILITY_EFFECTIVE_DIRECTIVE);
+ // If there is an active effective capability or a requirement from the feature
+ // and if the bundle is flagged as to start, start it
+ if ((EFFECTIVE_ACTIVE.equals(effective) || IDENTITY_NAMESPACE.equals(wire.getCapability().getNamespace()))
+ && (bi == null || bi.isStart())) {
+ stateToMerge = FeaturesService.RequestedState.Started;
+ } else {
+ stateToMerge = FeaturesService.RequestedState.Resolved;
+ }
+ } else {
+ stateToMerge = reqState;
+ }
+ propagateState(states, provider, stateToMerge, resolver);
+ }
+ }
+ }
+ }
+
+ private boolean isSubsystem(Resource resource) {
+ for (Capability cap : resource.getCapabilities(null)) {
+ if (cap.getNamespace().equals(IDENTITY_NAMESPACE)) {
+ String type = (String) cap.getAttributes().get(CAPABILITY_TYPE_ATTRIBUTE);
+ return (type != null) && type.equals(TYPE_SUBSYSTEM);
+ }
+ }
+ return false;
+ }
+
+ private FeaturesService.RequestedState mergeStates(FeaturesService.RequestedState s1, FeaturesService.RequestedState s2) {
+ if (s1 == FeaturesService.RequestedState.Started || s2 == FeaturesService.RequestedState.Started) {
+ return FeaturesService.RequestedState.Started;
+ }
+ if (s1 == FeaturesService.RequestedState.Resolved || s2 == FeaturesService.RequestedState.Resolved) {
+ return FeaturesService.RequestedState.Resolved;
+ }
+ return FeaturesService.RequestedState.Installed;
+ }
+
+ private void computeBundlesToRefresh(Set<Bundle> toRefresh, Collection<Bundle> bundles, Map<Resource, Bundle> resources, Map<Resource, List<Wire>> resolution) {
+ int size;
+ do {
+ size = toRefresh.size();
+ for (Bundle bundle : bundles) {
+ // Continue if we already know about this bundle
+ if (toRefresh.contains(bundle)) {
+ continue;
+ }
+ // Ignore non resolved bundle
+ BundleWiring wiring = bundle.adapt(BundleWiring.class);
+ if (wiring == null) {
+ continue;
+ }
+ // Get through the old resolution and flag this bundle
+ // if it was wired to a bundle to be refreshed
+ for (BundleWire wire : wiring.getRequiredWires(null)) {
+ if (toRefresh.contains(wire.getProvider().getBundle())) {
+ toRefresh.add(bundle);
+ break;
+ }
+ }
+ // Get through the new resolution and flag this bundle
+ // if it's wired to any new bundle
+ List<Wire> newWires = resolution.get(wiring.getRevision());
+ if (newWires != null) {
+ for (Wire wire : newWires) {
+ Bundle b;
+ if (wire.getProvider() instanceof BundleRevision) {
+ b = ((BundleRevision) wire.getProvider()).getBundle();
+ } else {
+ b = resources.get(wire.getProvider());
+ }
+ if (b == null || toRefresh.contains(b)) {
+ toRefresh.add(bundle);
+ break;
+ }
+ }
+ }
+ }
+ } while (toRefresh.size() > size);
+ }
+
+ private void print(String message, boolean verbose) {
+ callback.print(message, verbose);
+ }
+
+ private void removeFragmentsAndBundlesInState(Collection<Bundle> bundles, int state) {
+ for (Iterator<Bundle> iterator = bundles.iterator(); iterator.hasNext();) {
+ Bundle bundle = iterator.next();
+ if ((bundle.getState() & state) != 0
+ || bundle.getHeaders().get(Constants.FRAGMENT_HOST) != null) {
+ iterator.remove();
+ }
+ }
+ }
+
+ protected void logDeployment(Deployer.Deployment overallDeployment, boolean verbose) {
+ if (overallDeployment.regions.isEmpty()) {
+ print("No deployment change.", verbose);
+ return;
+ }
+ print("Changes to perform:", verbose);
+ for (Map.Entry<String, Deployer.RegionDeployment> region : overallDeployment.regions.entrySet()) {
+ Deployer.RegionDeployment deployment = region.getValue();
+ print(" Region: " + region.getKey(), verbose);
+ if (!deployment.toDelete.isEmpty()) {
+ print(" Bundles to uninstall:", verbose);
+ for (Bundle bundle : deployment.toDelete) {
+ print(" " + bundle.getSymbolicName() + " / " + bundle.getVersion(), verbose);
+ }
+ }
+ if (!deployment.toUpdate.isEmpty()) {
+ print(" Bundles to update:", verbose);
+ for (Map.Entry<Bundle, Resource> entry : deployment.toUpdate.entrySet()) {
+ print(" " + entry.getKey().getSymbolicName() + " / " + entry.getKey().getVersion() + " with " + getUri(entry.getValue()), verbose);
+ }
+ }
+ if (!deployment.toInstall.isEmpty()) {
+ print(" Bundles to install:", verbose);
+ for (Resource resource : deployment.toInstall) {
+ print(" " + getUri(resource), verbose);
+ }
+ }
+ }
+ }
+
+ protected Deployment computeDeployment(
+ DeploymentState dstate,
+ DeploymentRequest request,
+ SubsystemResolver resolver) throws IOException {
+
+ Deployment result = new Deployment();
+
+ Map<String, Set<Resource>> bundlesPerRegions = resolver.getBundlesPerRegions();
+
+ // Gather all regions, including old ones and new ones
+ Set<String> regions = new HashSet<>();
+ regions.addAll(dstate.state.managedBundles.keySet());
+ regions.addAll(bundlesPerRegions.keySet());
+
+ for (String region : regions) {
+
+ Deployer.RegionDeployment deployment = new Deployer.RegionDeployment();
+
+ // Get the list of bundles currently assigned in the region
+ Set<Long> managed = dstate.state.managedBundles.get(region);
+ if (managed == null) {
+ managed = Collections.emptySet();
+ }
+
+ // Compute the list of resources to deploy in the region
+ Set<Resource> bundlesInRegion = bundlesPerRegions.get(region);
+ List<Resource> toDeploy = bundlesInRegion != null
+ ? new ArrayList<>(bundlesInRegion) : new ArrayList<Resource>();
+
+ // First pass: go through all installed bundles and mark them
+ // as either to ignore or delete
+ for (long bundleId : managed) {
+ // Look for the installed bundle
+ Bundle bundle = dstate.bundles.get(bundleId);
+ // Bundle has been manually uninstalled ?
+ if (bundle != null) {
+ // Look for a matching resource
+ Resource resource = null;
+ for (Resource res : toDeploy) {
+ if (bundle.getSymbolicName().equals(getSymbolicName(res))
+ && bundle.getVersion().equals(getVersion(res))) {
+ resource = res;
+ break;
+ }
+ }
+ // We found a matching bundle
+ if (resource != null) {
+ // In case of snapshots, check if the snapshot is out of date
+ // and flag it as to update
+ if (isUpdateable(resource)) {
+ // Always update snapshots
+ if (UPDATE_SNAPSHOTS_ALWAYS.equalsIgnoreCase(request.updateSnaphots)) {
+ LOGGER.debug("Update snapshot for " + bundle.getLocation());
+ deployment.toUpdate.put(bundle, resource);
+ } else if (UPDATE_SNAPSHOTS_CRC.equalsIgnoreCase(request.updateSnaphots)) {
+ // if the checksum are different
+ try (
+ InputStream is = getBundleInputStream(resource, resolver.getProviders())
+ ) {
+ long newCrc = ChecksumUtils.checksum(is);
+ long oldCrc = dstate.state.bundleChecksums.containsKey(bundle.getBundleId())
+ ? dstate.state.bundleChecksums.get(bundle.getBundleId()) : 0L;
+ if (newCrc != oldCrc) {
+ LOGGER.debug("New snapshot available for " + bundle.getLocation());
+ deployment.toUpdate.put(bundle, resource);
+ }
+ result.bundleChecksums.put(bundle.getBundleId(), newCrc);
+ }
+ }
+ }
+ // We're done for this resource
+ toDeploy.remove(resource);
+ result.resToBnd.put(resource, bundle);
+ // There's no matching resource
+ // If the bundle is managed, we need to delete it
+ } else if (managed.contains(bundle.getBundleId())) {
+ deployment.toDelete.add(bundle);
+ }
+ }
+ }
+
+ // Second pass on remaining resources
+ for (Resource resource : toDeploy) {
+ TreeMap<Version, Bundle> matching = new TreeMap<>();
+ VersionRange range = new VersionRange(Macro.transform(request.bundleUpdateRange, getVersion(resource).toString()));
+ for (Bundle bundle : deployment.toDelete) {
+ if (bundle.getSymbolicName().equals(getSymbolicName(resource)) && range.contains(bundle.getVersion())) {
+ matching.put(bundle.getVersion(), bundle);
+ }
+ }
+ if (!matching.isEmpty()) {
+ Bundle bundle = matching.lastEntry().getValue();
+ deployment.toUpdate.put(bundle, resource);
+ deployment.toDelete.remove(bundle);
+ result.resToBnd.put(resource, bundle);
+ } else {
+ deployment.toInstall.add(resource);
+ }
+ }
+ Collections.sort(deployment.toInstall, new ResourceComparator());
+
+ // Add this region if there is something to do
+ if (!deployment.toDelete.isEmpty()
+ || !deployment.toUpdate.isEmpty()
+ || !deployment.toInstall.isEmpty()) {
+ result.regions.put(region, deployment);
+ }
+ }
+
+ return result;
+ }
+
+ protected <T> MapUtils.Function<Bundle, T> adapt(final Class<T> clazz) {
+ return new MapUtils.Function<Bundle, T>() {
+ @Override
+ public T apply(Bundle bundle) {
+ return bundle.adapt(clazz);
+ }
+ };
+ }
+
+ protected MapUtils.Function<Bundle, Long> bundleId() {
+ return new MapUtils.Function<Bundle, Long>() {
+ @Override
+ public Long apply(Bundle bundle) {
+ return bundle.getBundleId();
+ }
+ };
+ }
+
+ protected MapUtils.Function<Resource, String> featureId() {
+ return new MapUtils.Function<Resource, String>() {
+ @Override
+ public String apply(Resource resource) {
+ return getFeatureId(resource);
+ }
+ };
+ }
+
+ protected boolean isUpdateable(Resource resource) {
+ String uri = getUri(resource);
+ return uri.matches(UPDATEABLE_URIS);
+ }
+
+ protected List<Bundle> getBundlesToStart(Collection<Bundle> bundles, Bundle serviceBundle) {
+ // Restart the features service last, regardless of any other consideration
+ // so that we don't end up with the service trying to do stuff before we're done
+ boolean restart = false;
+
+ SortedMap<Integer, Set<Bundle>> bundlesPerStartLevel = new TreeMap<>();
+ for (Bundle bundle : bundles) {
+ if (bundle == serviceBundle) {
+ restart = true;
+ } else {
+ int sl = bundle.adapt(BundleStartLevel.class).getStartLevel();
+ addToMapSet(bundlesPerStartLevel, sl, bundle);
+ }
+ }
+ if (bundlesPerStartLevel.isEmpty()) {
+ bundles = Collections.emptyList();
+ } else {
+ bundles = bundlesPerStartLevel.remove(bundlesPerStartLevel.firstKey());
+ }
+
+ // We hit FELIX-2949 if we don't use the correct order as Felix resolver isn't greedy.
+ // In order to minimize that, we make sure we resolve the bundles in the order they
+ // are given back by the resolution, meaning that all root bundles (i.e. those that were
+ // not flagged as dependencies in features) are started before the others. This should
+ // make sure those important bundles are started first and minimize the problem.
+ List<BundleRevision> revs = new ArrayList<>();
+ for (Bundle bundle : bundles) {
+ revs.add(bundle.adapt(BundleRevision.class));
+ }
+ List<Bundle> sorted = new ArrayList<>();
+ for (BundleRevision rev : RequirementSort.sort(revs)) {
+ sorted.add(rev.getBundle());
+ }
+ if (sorted.isEmpty() && restart) {
+ sorted.add(serviceBundle);
+ }
+ return sorted;
+ }
+
+ protected List<Bundle> getBundlesToStop(Collection<Bundle> bundles) {
+ SortedMap<Integer, Set<Bundle>> bundlesPerStartLevel = new TreeMap<>();
+ for (Bundle bundle : bundles) {
+ int sl = bundle.adapt(BundleStartLevel.class).getStartLevel();
+ addToMapSet(bundlesPerStartLevel, sl, bundle);
+ }
+ bundles = bundlesPerStartLevel.get(bundlesPerStartLevel.lastKey());
+
+ List<Bundle> bundlesToDestroy = new ArrayList<>();
+ for (Bundle bundle : bundles) {
+ ServiceReference[] references = bundle.getRegisteredServices();
+ int usage = 0;
+ if (references != null) {
+ for (ServiceReference reference : references) {
+ usage += getServiceUsage(reference, bundles);
+ }
+ }
+ LOGGER.debug("Usage for bundle {} is {}", bundle, usage);
+ if (usage == 0) {
+ bundlesToDestroy.add(bundle);
+ }
+ }
+ if (!bundlesToDestroy.isEmpty()) {
+ Collections.sort(bundlesToDestroy, new Comparator<Bundle>() {
+ public int compare(Bundle b1, Bundle b2) {
+ return (int) (b2.getLastModified() - b1.getLastModified());
+ }
+ });
+ LOGGER.debug("Selected bundles {} for destroy (no services in use)", bundlesToDestroy);
+ } else {
+ ServiceReference ref = null;
+ for (Bundle bundle : bundles) {
+ ServiceReference[] references = bundle.getRegisteredServices();
+ for (ServiceReference reference : references) {
+ if (getServiceUsage(reference, bundles) == 0) {
+ continue;
+ }
+ if (ref == null || reference.compareTo(ref) < 0) {
+ LOGGER.debug("Currently selecting bundle {} for destroy (with reference {})", bundle, reference);
+ ref = reference;
+ }
+ }
+ }
+ if (ref != null) {
+ bundlesToDestroy.add(ref.getBundle());
+ }
+ LOGGER.debug("Selected bundle {} for destroy (lowest ranking service)", bundlesToDestroy);
+ }
+ return bundlesToDestroy;
+ }
+
+ private static int getServiceUsage(ServiceReference ref, Collection<Bundle> bundles) {
+ Bundle[] usingBundles = ref.getUsingBundles();
+ int nb = 0;
+ if (usingBundles != null) {
+ for (Bundle bundle : usingBundles) {
+ if (bundles.contains(bundle)) {
+ nb++;
+ }
+ }
+ }
+ return nb;
+ }
+
+ protected InputStream getBundleInputStream(Resource resource, Map<String, StreamProvider> providers) throws IOException {
+ String uri = getUri(resource);
+ if (uri == null) {
+ throw new IllegalStateException("Resource has no uri");
+ }
+ StreamProvider provider = providers.get(uri);
+ if (provider == null) {
+ throw new IllegalStateException("Resource " + uri + " has no StreamProvider");
+ }
+ return provider.open();
+ }
+
+}
[7/7] git commit: [KARAF-2888] Fix unit tests caused by the change in
Feature#getId()
Posted by gn...@apache.org.
[KARAF-2888] Fix unit tests caused by the change in Feature#getId()
Project: http://git-wip-us.apache.org/repos/asf/karaf/repo
Commit: http://git-wip-us.apache.org/repos/asf/karaf/commit/bd916772
Tree: http://git-wip-us.apache.org/repos/asf/karaf/tree/bd916772
Diff: http://git-wip-us.apache.org/repos/asf/karaf/diff/bd916772
Branch: refs/heads/master
Commit: bd9167729d3c824914f3e6fecaaa34bd48437fb3
Parents: e334fae
Author: Guillaume Nodet <gn...@gmail.com>
Authored: Wed Apr 30 20:04:17 2014 +0200
Committer: Guillaume Nodet <gn...@gmail.com>
Committed: Wed Apr 30 20:04:28 2014 +0200
----------------------------------------------------------------------
.../src/it/test-aggregate-features/control.xml | 4 ++--
.../src/it/test-check-dependencies-failure/control.xml | 4 ++--
.../src/it/test-check-dependencies/control.xml | 4 ++--
tooling/karaf-maven-plugin/src/it/test-input-file/control.xml | 2 +-
4 files changed, 7 insertions(+), 7 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/karaf/blob/bd916772/tooling/karaf-maven-plugin/src/it/test-aggregate-features/control.xml
----------------------------------------------------------------------
diff --git a/tooling/karaf-maven-plugin/src/it/test-aggregate-features/control.xml b/tooling/karaf-maven-plugin/src/it/test-aggregate-features/control.xml
index 01a6721..8369f0d 100644
--- a/tooling/karaf-maven-plugin/src/it/test-aggregate-features/control.xml
+++ b/tooling/karaf-maven-plugin/src/it/test-aggregate-features/control.xml
@@ -19,11 +19,11 @@
-->
<features xmlns="http://karaf.apache.org/xmlns/features/v1.3.0" name="aggregate-features">
- <feature description="aggregate-recursive-module-c" version="1.0-SNAPSHOT" name="aggregate-recursive-module-c">
+ <feature description="aggregate-recursive-module-c" version="1.0.0.SNAPSHOT" name="aggregate-recursive-module-c">
<details>Test Description</details>
<bundle>mvn:test/aggregate-recursive-module-b/1.0-SNAPSHOT</bundle>
</feature>
- <feature description="aggregate-recursive-module-d" version="1.0-SNAPSHOT" name="aggregate-recursive-module-d">
+ <feature description="aggregate-recursive-module-d" version="1.0.0.SNAPSHOT" name="aggregate-recursive-module-d">
<details>Test Description</details>
<bundle>mvn:test/aggregate-recursive-module-a/1.0-SNAPSHOT</bundle>
<bundle>mvn:test/aggregate-recursive-module-c/1.0-SNAPSHOT</bundle>
http://git-wip-us.apache.org/repos/asf/karaf/blob/bd916772/tooling/karaf-maven-plugin/src/it/test-check-dependencies-failure/control.xml
----------------------------------------------------------------------
diff --git a/tooling/karaf-maven-plugin/src/it/test-check-dependencies-failure/control.xml b/tooling/karaf-maven-plugin/src/it/test-check-dependencies-failure/control.xml
index 223d090..ef8eba4 100644
--- a/tooling/karaf-maven-plugin/src/it/test-check-dependencies-failure/control.xml
+++ b/tooling/karaf-maven-plugin/src/it/test-check-dependencies-failure/control.xml
@@ -19,11 +19,11 @@
-->
<features xmlns="http://karaf.apache.org/xmlns/features/v1.3.0" name="check-dependencies-features">
- <feature description="dependency-module-c" version="1.0-SNAPSHOT" name="dependency-module-c">
+ <feature description="dependency-module-c" version="1.0.0.SNAPSHOT" name="dependency-module-c">
<bundle>mvn:test/dependency-module-a/1.0-SNAPSHOT</bundle>
<bundle>mvn:test/dependency-module-b/1.0-SNAPSHOT</bundle>
</feature>
- <feature description="dependency-module-d" version="1.0-SNAPSHOT" name="dependency-module-d">
+ <feature description="dependency-module-d" version="1.0.0.SNAPSHOT" name="dependency-module-d">
<bundle>mvn:test/dependency-module-c/1.0-SNAPSHOT</bundle>
<bundle>mvn:test/dependency-module-a/1.0-SNAPSHOT</bundle>
<bundle>mvn:test/dependency-module-b/1.0-SNAPSHOT</bundle>
http://git-wip-us.apache.org/repos/asf/karaf/blob/bd916772/tooling/karaf-maven-plugin/src/it/test-check-dependencies/control.xml
----------------------------------------------------------------------
diff --git a/tooling/karaf-maven-plugin/src/it/test-check-dependencies/control.xml b/tooling/karaf-maven-plugin/src/it/test-check-dependencies/control.xml
index 223d090..ef8eba4 100644
--- a/tooling/karaf-maven-plugin/src/it/test-check-dependencies/control.xml
+++ b/tooling/karaf-maven-plugin/src/it/test-check-dependencies/control.xml
@@ -19,11 +19,11 @@
-->
<features xmlns="http://karaf.apache.org/xmlns/features/v1.3.0" name="check-dependencies-features">
- <feature description="dependency-module-c" version="1.0-SNAPSHOT" name="dependency-module-c">
+ <feature description="dependency-module-c" version="1.0.0.SNAPSHOT" name="dependency-module-c">
<bundle>mvn:test/dependency-module-a/1.0-SNAPSHOT</bundle>
<bundle>mvn:test/dependency-module-b/1.0-SNAPSHOT</bundle>
</feature>
- <feature description="dependency-module-d" version="1.0-SNAPSHOT" name="dependency-module-d">
+ <feature description="dependency-module-d" version="1.0.0.SNAPSHOT" name="dependency-module-d">
<bundle>mvn:test/dependency-module-c/1.0-SNAPSHOT</bundle>
<bundle>mvn:test/dependency-module-a/1.0-SNAPSHOT</bundle>
<bundle>mvn:test/dependency-module-b/1.0-SNAPSHOT</bundle>
http://git-wip-us.apache.org/repos/asf/karaf/blob/bd916772/tooling/karaf-maven-plugin/src/it/test-input-file/control.xml
----------------------------------------------------------------------
diff --git a/tooling/karaf-maven-plugin/src/it/test-input-file/control.xml b/tooling/karaf-maven-plugin/src/it/test-input-file/control.xml
index 08e8f0e..9310cf7 100644
--- a/tooling/karaf-maven-plugin/src/it/test-input-file/control.xml
+++ b/tooling/karaf-maven-plugin/src/it/test-input-file/control.xml
@@ -19,7 +19,7 @@
-->
<features xmlns="http://karaf.apache.org/xmlns/features/v1.3.0" name="test-input-file">
- <feature description="Test Description" version="1.0-SNAPSHOT" name="test-input-file">
+ <feature description="Test Description" version="1.0.0.SNAPSHOT" name="test-input-file">
<details>Test Description</details>
<bundle>mvn:test/test-input-file/1.0-SNAPSHOT</bundle>
</feature>
[3/7] git commit: [KARAF-2805] Upgrade command archetype to the new
api
Posted by gn...@apache.org.
[KARAF-2805] Upgrade command archetype to the new api
Project: http://git-wip-us.apache.org/repos/asf/karaf/repo
Commit: http://git-wip-us.apache.org/repos/asf/karaf/commit/3d204040
Tree: http://git-wip-us.apache.org/repos/asf/karaf/tree/3d204040
Diff: http://git-wip-us.apache.org/repos/asf/karaf/diff/3d204040
Branch: refs/heads/master
Commit: 3d204040f169025fece6eb6a116033407e70d534
Parents: 703c62f
Author: Guillaume Nodet <gn...@gmail.com>
Authored: Tue Apr 29 17:26:17 2014 +0200
Committer: Guillaume Nodet <gn...@gmail.com>
Committed: Wed Apr 30 20:04:27 2014 +0200
----------------------------------------------------------------------
.../main/resources/archetype-resources/pom.xml | 15 ++-------
.../src/main/java/__command__.java | 15 +++++----
.../resources/OSGI-INF/blueprint/shell-log.xml | 32 --------------------
3 files changed, 12 insertions(+), 50 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/karaf/blob/3d204040/archetypes/command/src/main/resources/archetype-resources/pom.xml
----------------------------------------------------------------------
diff --git a/archetypes/command/src/main/resources/archetype-resources/pom.xml b/archetypes/command/src/main/resources/archetype-resources/pom.xml
index e5b2ab0..208f292 100644
--- a/archetypes/command/src/main/resources/archetype-resources/pom.xml
+++ b/archetypes/command/src/main/resources/archetype-resources/pom.xml
@@ -31,7 +31,7 @@
<dependencies>
<dependency>
<groupId>org.apache.karaf.shell</groupId>
- <artifactId>org.apache.karaf.shell.console</artifactId>
+ <artifactId>org.apache.karaf.shell.core</artifactId>
<version>^project.version^</version>
</dependency>
@@ -76,17 +76,8 @@
<configuration>
<instructions>
<Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
- <Export-Package>${package}*;version=${project.version}</Export-Package>
- <Import-Package>
- !${package}*,
- org.apache.aries.blueprint,
- org.osgi.service.blueprint.container,
- org.osgi.service.blueprint.reflect,
- org.apache.felix.service.command,
- org.apache.karaf.shell.commands,
- org.apache.karaf.shell.console,
- *
- </Import-Package>
+ <Export-Package>${package}*;version=${project.version};-noimport:=true</Export-Package>
+ <Karaf-Commands>${package}*</Karaf-Commands>
</instructions>
</configuration>
</plugin>
http://git-wip-us.apache.org/repos/asf/karaf/blob/3d204040/archetypes/command/src/main/resources/archetype-resources/src/main/java/__command__.java
----------------------------------------------------------------------
diff --git a/archetypes/command/src/main/resources/archetype-resources/src/main/java/__command__.java b/archetypes/command/src/main/resources/archetype-resources/src/main/java/__command__.java
index 75ccb0a..1c6eb60 100644
--- a/archetypes/command/src/main/resources/archetype-resources/src/main/java/__command__.java
+++ b/archetypes/command/src/main/resources/archetype-resources/src/main/java/__command__.java
@@ -21,13 +21,15 @@
package ${package};
-import org.apache.karaf.shell.console.OsgiCommandSupport;
-import org.apache.karaf.shell.commands.Command;
-import org.apache.karaf.shell.commands.Option;
-import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
@Command(scope = "${scope}", name = "${cmd}", description = "${description}")
-public class ${command} extends OsgiCommandSupport {
+@Service
+public class ${command} implements Action {
@Option(name = "-o", aliases = { "--option" }, description = "An option to the command", required = false, multiValued = false)
private String option;
@@ -35,7 +37,8 @@ public class ${command} extends OsgiCommandSupport {
@Argument(name = "argument", description = "Argument to the command", required = false, multiValued = false)
private String argument;
- protected Object doExecute() throws Exception {
+ @Override
+ public Object execute() throws Exception {
System.out.println("Executing command ${cmd}");
System.out.println("Option: " + option);
System.out.println("Argument: " + argument);
http://git-wip-us.apache.org/repos/asf/karaf/blob/3d204040/archetypes/command/src/main/resources/archetype-resources/src/main/resources/OSGI-INF/blueprint/shell-log.xml
----------------------------------------------------------------------
diff --git a/archetypes/command/src/main/resources/archetype-resources/src/main/resources/OSGI-INF/blueprint/shell-log.xml b/archetypes/command/src/main/resources/archetype-resources/src/main/resources/OSGI-INF/blueprint/shell-log.xml
deleted file mode 100644
index 37c1b64..0000000
--- a/archetypes/command/src/main/resources/archetype-resources/src/main/resources/OSGI-INF/blueprint/shell-log.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-##---------------------------------------------------------------------------
-## 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.
-##---------------------------------------------------------------------------
-#set( $symbol_pound = '#' )
-#set( $symbol_dollar = '$' )
-#set( $symbol_escape = '\' )
-#set( $cmd = $command.toLowerCase())
-<?xml version="1.0" encoding="UTF-8"?>
-
-<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
- xmlns:cm="http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.0.0">
-
- <command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.1.0">
- <command>
- <action class="${package}.${command}">
- </action>
- </command>
- </command-bundle>
-</blueprint>
[2/7] git commit: [KARAF-2888] A few improvements to the deployer
Posted by gn...@apache.org.
[KARAF-2888] A few improvements to the deployer
Project: http://git-wip-us.apache.org/repos/asf/karaf/repo
Commit: http://git-wip-us.apache.org/repos/asf/karaf/commit/c082817f
Tree: http://git-wip-us.apache.org/repos/asf/karaf/tree/c082817f
Diff: http://git-wip-us.apache.org/repos/asf/karaf/diff/c082817f
Branch: refs/heads/master
Commit: c082817f21e1754b3e341815e3c00273efa966a3
Parents: 57b609d
Author: Guillaume Nodet <gn...@gmail.com>
Authored: Wed Apr 30 18:02:13 2014 +0200
Committer: Guillaume Nodet <gn...@gmail.com>
Committed: Wed Apr 30 20:04:27 2014 +0200
----------------------------------------------------------------------
.../features/internal/service/Deployer.java | 87 +++++++++++---------
.../internal/service/FeaturesServiceImpl.java | 8 +-
2 files changed, 52 insertions(+), 43 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/karaf/blob/c082817f/features/core/src/main/java/org/apache/karaf/features/internal/service/Deployer.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/Deployer.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/Deployer.java
index e26a07a..c636fbe 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/service/Deployer.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/Deployer.java
@@ -38,6 +38,7 @@ import org.apache.karaf.features.BundleInfo;
import org.apache.karaf.features.Feature;
import org.apache.karaf.features.FeatureEvent;
import org.apache.karaf.features.FeaturesService;
+import org.apache.karaf.features.internal.download.DownloadManager;
import org.apache.karaf.features.internal.download.StreamProvider;
import org.apache.karaf.features.internal.region.SubsystemResolver;
import org.apache.karaf.features.internal.util.ChecksumUtils;
@@ -128,7 +129,7 @@ public class Deployer {
}
static class DeploymentRequest {
- String overrides;
+ Set<String> overrides;
String featureResolutionRange;
String bundleUpdateRange;
String updateSnaphots;
@@ -153,9 +154,11 @@ public class Deployer {
private static final Logger LOGGER = LoggerFactory.getLogger(Deployer.class);
+ private final DownloadManager manager;
private final DeployCallback callback;
- public Deployer(DeployCallback callback) {
+ public Deployer(DownloadManager manager, DeployCallback callback) {
+ this.manager = manager;
this.callback = callback;
}
@@ -186,12 +189,12 @@ public class Deployer {
// TODO: requirements
// TODO: bundles
- SubsystemResolver resolver = new SubsystemResolver();
+ SubsystemResolver resolver = new SubsystemResolver(manager);
resolver.resolve(
dstate.features.values(),
request.requestedFeatures,
apply(unmanagedBundles, adapt(BundleRevision.class)),
- Overrides.loadOverrides(request.overrides),
+ request.overrides,
request.featureResolutionRange,
request.globalRepository);
@@ -302,47 +305,11 @@ public class Deployer {
}
}
- //
- // Log deployment
- //
- logDeployment(deployment, verbose);
-
- if (simulate) {
- if (!noRefresh && !toRefresh.isEmpty()) {
- print(" Bundles to refresh:", verbose);
- for (Bundle bundle : toRefresh) {
- print(" " + bundle.getSymbolicName() + " / " + bundle.getVersion(), verbose);
- }
- }
- if (!toManage.isEmpty()) {
- print(" Managing bundle:", verbose);
- for (Bundle bundle : toManage) {
- print(" " + bundle.getSymbolicName() + " / " + bundle.getVersion(), verbose);
- }
- }
- return;
- }
-
Set<Bundle> toStart = new HashSet<>();
Set<Bundle> toResolve = new HashSet<>();
Set<Bundle> toStop = new HashSet<>();
//
- // Execute deployment
- //
- // #1: stop bundles that needs to be updated or uninstalled in order
- // #2: uninstall needed bundles
- // #3: update regions
- // #4: update bundles
- // #5: install bundles
- // #6: save state
- // #7: install configuration
- // #8: refresh bundles
- // #9: start bundles in order
- // #10: send events
- //
-
- //
// Compute bundle states
//
Map<Resource, FeaturesService.RequestedState> states = new HashMap<>();
@@ -399,6 +366,42 @@ public class Deployer {
}
}
+ //
+ // Log deployment
+ //
+ logDeployment(deployment, verbose);
+
+ if (simulate) {
+ if (!noRefresh && !toRefresh.isEmpty()) {
+ print(" Bundles to refresh:", verbose);
+ for (Bundle bundle : toRefresh) {
+ print(" " + bundle.getSymbolicName() + " / " + bundle.getVersion(), verbose);
+ }
+ }
+ if (!toManage.isEmpty()) {
+ print(" Managing bundle:", verbose);
+ for (Bundle bundle : toManage) {
+ print(" " + bundle.getSymbolicName() + " / " + bundle.getVersion(), verbose);
+ }
+ }
+ return;
+ }
+
+ //
+ // Execute deployment
+ //
+ // #1: stop bundles that needs to be updated or uninstalled in order
+ // #2: uninstall needed bundles
+ // #3: update regions
+ // #4: update bundles
+ // #5: install bundles
+ // #6: save state
+ // #7: install configuration
+ // #8: refresh bundles
+ // #9: start bundles in order
+ // #10: send events
+ //
+
//
// Handle updates on the FeaturesService bundle
@@ -605,7 +608,9 @@ public class Deployer {
deployment.bundleChecksums.put(bundle.getBundleId(), crc);
}
int startLevel = startLevels.get(resource);
- callback.setBundleStartLevel(bundle, startLevel);
+ if (startLevel != dstate.initialBundleStartLevel) {
+ callback.setBundleStartLevel(bundle, startLevel);
+ }
FeaturesService.RequestedState reqState = states.get(resource);
switch (reqState) {
case Started:
http://git-wip-us.apache.org/repos/asf/karaf/blob/c082817f/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java
index 589b719..f0f0677 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java
@@ -48,6 +48,7 @@ import org.apache.karaf.features.FeaturesListener;
import org.apache.karaf.features.FeaturesService;
import org.apache.karaf.features.Repository;
import org.apache.karaf.features.RepositoryEvent;
+import org.apache.karaf.features.internal.download.simple.SimpleDownloader;
import org.apache.karaf.features.internal.util.JsonReader;
import org.apache.karaf.features.internal.util.JsonWriter;
import org.apache.karaf.util.collections.CopyOnWriteArrayIdentityList;
@@ -892,7 +893,7 @@ public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCall
request.bundleUpdateRange = bundleUpdateRange;
request.featureResolutionRange = featureResolutionRange;
request.globalRepository = globalRepository;
- request.overrides = overrides;
+ request.overrides = Overrides.loadOverrides(overrides);
request.requestedFeatures = requestedFeatures;
request.stateChanges = stateChanges;
request.options = options;
@@ -909,7 +910,7 @@ public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCall
Deployer.DeploymentState dstate = getDeploymentState(state);
Deployer.DeploymentRequest request = getDeploymentRequest(requestedFeatures, stateChanges, options);
- new Deployer(this).deploy(dstate, request);
+ new Deployer(new SimpleDownloader(), this).deploy(dstate, request);
}
public void print(String message, boolean verbose) {
@@ -937,6 +938,9 @@ public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCall
@Override
public void saveState(State state) {
synchronized (lock) {
+ state.repositories.clear();
+ state.repositories.addAll(this.state.repositories);
+ state.bootDone.set(this.state.bootDone.get());
this.state.replace(state);
saveState();
}
[6/7] git commit: [KARAF-2888] Make sure Feature#getId() is
normalized correctly
Posted by gn...@apache.org.
[KARAF-2888] Make sure Feature#getId() is normalized correctly
Project: http://git-wip-us.apache.org/repos/asf/karaf/repo
Commit: http://git-wip-us.apache.org/repos/asf/karaf/commit/57b609d8
Tree: http://git-wip-us.apache.org/repos/asf/karaf/tree/57b609d8
Diff: http://git-wip-us.apache.org/repos/asf/karaf/diff/57b609d8
Branch: refs/heads/master
Commit: 57b609d8e425eed55ec29082fdb09f788066f682
Parents: 9e2ada3
Author: Guillaume Nodet <gn...@gmail.com>
Authored: Wed Apr 30 16:46:38 2014 +0200
Committer: Guillaume Nodet <gn...@gmail.com>
Committed: Wed Apr 30 20:04:27 2014 +0200
----------------------------------------------------------------------
.../apache/karaf/features/internal/model/Feature.java | 11 ++++++++---
.../features/internal/service/FeaturesServiceImpl.java | 5 +++--
.../test/java/org/apache/karaf/features/FeatureTest.java | 4 ++--
.../internal/service/FeaturesServiceImplTest.java | 2 +-
.../internal/service/FeaturesValidationTest.java | 2 ++
.../org/apache/karaf/features/internal/service/f07.xml | 2 +-
6 files changed, 17 insertions(+), 9 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/karaf/blob/57b609d8/features/core/src/main/java/org/apache/karaf/features/internal/model/Feature.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/model/Feature.java b/features/core/src/main/java/org/apache/karaf/features/internal/model/Feature.java
index 154e89a..6cf3820 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/model/Feature.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/model/Feature.java
@@ -26,8 +26,12 @@ import java.util.regex.Pattern;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.XmlType;
+import org.apache.felix.utils.version.VersionCleaner;
+import org.apache.felix.utils.version.VersionTable;
+
/**
* Definition of the Feature.
@@ -81,7 +85,7 @@ public class Feature extends Content implements org.apache.karaf.features.Featur
protected String details;
@XmlAttribute(required = true)
protected String name;
- @XmlAttribute
+ @XmlTransient
protected String version;
@XmlAttribute
protected String description;
@@ -105,7 +109,7 @@ public class Feature extends Content implements org.apache.karaf.features.Featur
public Feature(String name, String version) {
this.name = name;
- this.version = version;
+ this.version = VersionCleaner.clean(version);
}
@@ -167,8 +171,9 @@ public class Feature extends Content implements org.apache.karaf.features.Featur
* @param value allowed object is
* {@link String }
*/
+ @XmlAttribute
public void setVersion(String value) {
- this.version = value;
+ this.version = VersionCleaner.clean(value);
}
/**
http://git-wip-us.apache.org/repos/asf/karaf/blob/57b609d8/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java
index 099d175..589b719 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java
@@ -39,6 +39,7 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import org.apache.felix.utils.version.VersionCleaner;
import org.apache.felix.utils.version.VersionRange;
import org.apache.felix.utils.version.VersionTable;
import org.apache.karaf.features.Feature;
@@ -799,7 +800,7 @@ public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCall
int idx = feature.indexOf("/");
String name = feature.substring(0, idx);
String version = feature.substring(idx + 1);
- return name + "/" + VersionTable.getVersion(version).toString();
+ return name + "/" + VersionCleaner.clean(version);
}
/**
@@ -856,7 +857,7 @@ public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCall
dstate.features = new HashMap<>();
for (Map<String, Feature> m : getFeatures().values()) {
for (Feature feature : m.values()) {
- String id = feature.getName() + "/" + VersionTable.getVersion(feature.getVersion());
+ String id = feature.getId();
dstate.features.put(id, feature);
}
}
http://git-wip-us.apache.org/repos/asf/karaf/blob/57b609d8/features/core/src/test/java/org/apache/karaf/features/FeatureTest.java
----------------------------------------------------------------------
diff --git a/features/core/src/test/java/org/apache/karaf/features/FeatureTest.java b/features/core/src/test/java/org/apache/karaf/features/FeatureTest.java
index b7d4c27..33debdf 100644
--- a/features/core/src/test/java/org/apache/karaf/features/FeatureTest.java
+++ b/features/core/src/test/java/org/apache/karaf/features/FeatureTest.java
@@ -21,9 +21,9 @@ import junit.framework.TestCase;
public class FeatureTest extends TestCase {
public void testValueOf() {
- Feature feature = org.apache.karaf.features.internal.model.Feature.valueOf("name" + org.apache.karaf.features.internal.model.Feature.SPLIT_FOR_NAME_AND_VERSION + "version");
+ Feature feature = org.apache.karaf.features.internal.model.Feature.valueOf("name/1.0.0");
assertEquals(feature.getName(), "name");
- assertEquals(feature.getVersion(), "version");
+ assertEquals(feature.getVersion(), "1.0.0");
feature = org.apache.karaf.features.internal.model.Feature.valueOf("name");
assertEquals(feature.getName(), "name");
assertEquals(feature.getVersion(), org.apache.karaf.features.internal.model.Feature.DEFAULT_VERSION);
http://git-wip-us.apache.org/repos/asf/karaf/blob/57b609d8/features/core/src/test/java/org/apache/karaf/features/internal/service/FeaturesServiceImplTest.java
----------------------------------------------------------------------
diff --git a/features/core/src/test/java/org/apache/karaf/features/internal/service/FeaturesServiceImplTest.java b/features/core/src/test/java/org/apache/karaf/features/internal/service/FeaturesServiceImplTest.java
index 7f1f358..405a7e9 100644
--- a/features/core/src/test/java/org/apache/karaf/features/internal/service/FeaturesServiceImplTest.java
+++ b/features/core/src/test/java/org/apache/karaf/features/internal/service/FeaturesServiceImplTest.java
@@ -92,7 +92,7 @@ public class FeaturesServiceImplTest extends TestBase {
}
};
assertNotNull(impl.getFeature("transaction", org.apache.karaf.features.internal.model.Feature.DEFAULT_VERSION));
- assertSame("2.0.0", impl.getFeature("transaction", org.apache.karaf.features.internal.model.Feature.DEFAULT_VERSION).getVersion());
+ assertEquals("2.0.0", impl.getFeature("transaction", org.apache.karaf.features.internal.model.Feature.DEFAULT_VERSION).getVersion());
}
/**
http://git-wip-us.apache.org/repos/asf/karaf/blob/57b609d8/features/core/src/test/java/org/apache/karaf/features/internal/service/FeaturesValidationTest.java
----------------------------------------------------------------------
diff --git a/features/core/src/test/java/org/apache/karaf/features/internal/service/FeaturesValidationTest.java b/features/core/src/test/java/org/apache/karaf/features/internal/service/FeaturesValidationTest.java
index 9e33ee3..8a0fbca 100644
--- a/features/core/src/test/java/org/apache/karaf/features/internal/service/FeaturesValidationTest.java
+++ b/features/core/src/test/java/org/apache/karaf/features/internal/service/FeaturesValidationTest.java
@@ -24,6 +24,7 @@ import org.apache.karaf.features.internal.model.Features;
import org.apache.karaf.features.internal.model.JaxbUtil;
import org.junit.Test;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
@@ -97,6 +98,7 @@ public class FeaturesValidationTest {
URL url = getClass().getResource("f07.xml");
Features features = JaxbUtil.unmarshal(url.toExternalForm(), true);
assertNotNull(features);
+ assertEquals("2.5.6.SEC02", features.getFeature().get(0).getVersion());
}
}
http://git-wip-us.apache.org/repos/asf/karaf/blob/57b609d8/features/core/src/test/resources/org/apache/karaf/features/internal/service/f07.xml
----------------------------------------------------------------------
diff --git a/features/core/src/test/resources/org/apache/karaf/features/internal/service/f07.xml b/features/core/src/test/resources/org/apache/karaf/features/internal/service/f07.xml
index 354ba86..d1a5745 100644
--- a/features/core/src/test/resources/org/apache/karaf/features/internal/service/f07.xml
+++ b/features/core/src/test/resources/org/apache/karaf/features/internal/service/f07.xml
@@ -16,7 +16,7 @@
limitations under the License.
-->
<features name="karaf" xmlns="http://karaf.apache.org/xmlns/features/v1.3.0">
- <feature name="spring" version="2.5.6.SEC02">
+ <feature name="spring" version="2.5.6-SEC02">
<bundle dependency='true'>mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.aopalliance/1.0_4</bundle>
<bundle>mvn:org.springframework/spring-core/2.5.6.SEC02</bundle>
<bundle>mvn:org.springframework/spring-beans/2.5.6.SEC02</bundle>