Repository: karaf Updated Branches: refs/heads/master b60231147 -> e164271c4
[KARAF-3026] Add a dependency="true" flag on feature dependencies Improve the heuristic for sorting candidates based on mandatory resources Project: http://git-wip-us.apache.org/repos/asf/karaf/repo Commit: http://git-wip-us.apache.org/repos/asf/karaf/commit/e164271c Tree: http://git-wip-us.apache.org/repos/asf/karaf/tree/e164271c Diff: http://git-wip-us.apache.org/repos/asf/karaf/diff/e164271c Branch: refs/heads/master Commit: e164271c4a894b8b0d83c6c44741e44ff47f1605 Parents: b602311 Author: Guillaume Nodet <[email protected]> Authored: Fri Jun 6 16:43:31 2014 +0200 Committer: Guillaume Nodet <[email protected]> Committed: Fri Jun 6 16:43:31 2014 +0200 ---------------------------------------------------------------------- .../enterprise/src/main/feature/feature.xml | 1 + .../org/apache/karaf/features/Dependency.java | 2 + .../features/internal/model/Dependency.java | 11 +++++ .../internal/region/CandidateComparator.java | 17 ++++++++ .../features/internal/region/Subsystem.java | 33 ++++++++------- .../region/SubsystemResolveContext.java | 25 +++++++++++- .../internal/resolver/FeatureResource.java | 4 +- .../internal/resolver/ResourceUtils.java | 17 +++++++- .../karaf/features/karaf-features-1.3.0.xsd | 1 + .../features/internal/region/SubsystemTest.java | 43 ++++++++++++++++++++ .../karaf/features/internal/region/data5/a.mf | 5 +++ .../karaf/features/internal/region/data5/b.mf | 5 +++ .../karaf/features/internal/region/data5/c.mf | 5 +++ .../features/internal/region/data5/features.xml | 37 +++++++++++++++++ .../karaf/features/internal/service/f07.xml | 1 + 15 files changed, 187 insertions(+), 20 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/karaf/blob/e164271c/assemblies/features/enterprise/src/main/feature/feature.xml ---------------------------------------------------------------------- diff --git a/assemblies/features/enterprise/src/main/feature/feature.xml b/assemblies/features/enterprise/src/main/feature/feature.xml index d4b70cc..6c21fe3 100644 --- a/assemblies/features/enterprise/src/main/feature/feature.xml +++ b/assemblies/features/enterprise/src/main/feature/feature.xml @@ -189,6 +189,7 @@ <feature name="jdbc" description="JDBC service and commands" version="${project.version}"> <details>JDBC support providing service, commands, and MBean.</details> + <feature dependency="true">aries-blueprint</feature> <feature>transaction</feature> <bundle>mvn:commons-pool/commons-pool/${commons-pool.version}</bundle> <bundle>mvn:commons-dbcp/commons-dbcp/${commons-dbcp.version}</bundle> http://git-wip-us.apache.org/repos/asf/karaf/blob/e164271c/features/core/src/main/java/org/apache/karaf/features/Dependency.java ---------------------------------------------------------------------- diff --git a/features/core/src/main/java/org/apache/karaf/features/Dependency.java b/features/core/src/main/java/org/apache/karaf/features/Dependency.java index 421df19..13a4381 100644 --- a/features/core/src/main/java/org/apache/karaf/features/Dependency.java +++ b/features/core/src/main/java/org/apache/karaf/features/Dependency.java @@ -25,4 +25,6 @@ public interface Dependency { boolean isPrerequisite(); + boolean isDependency(); + } http://git-wip-us.apache.org/repos/asf/karaf/blob/e164271c/features/core/src/main/java/org/apache/karaf/features/internal/model/Dependency.java ---------------------------------------------------------------------- diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/model/Dependency.java b/features/core/src/main/java/org/apache/karaf/features/internal/model/Dependency.java index 2a4d8dd..a0a3854 100644 --- a/features/core/src/main/java/org/apache/karaf/features/internal/model/Dependency.java +++ b/features/core/src/main/java/org/apache/karaf/features/internal/model/Dependency.java @@ -51,6 +51,8 @@ public class Dependency implements org.apache.karaf.features.Dependency { protected String version; @XmlAttribute protected boolean prerequisite; + @XmlAttribute + protected boolean dependency; /** * Feature name should be non empty string. @@ -105,6 +107,15 @@ public class Dependency implements org.apache.karaf.features.Dependency { this.prerequisite = prerequisite; } + @Override + public boolean isDependency() { + return dependency; + } + + public void setDependency(boolean dependency) { + this.dependency = dependency; + } + public String toString() { return getName() + Feature.VERSION_SEPARATOR + getVersion(); } http://git-wip-us.apache.org/repos/asf/karaf/blob/e164271c/features/core/src/main/java/org/apache/karaf/features/internal/region/CandidateComparator.java ---------------------------------------------------------------------- diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/region/CandidateComparator.java b/features/core/src/main/java/org/apache/karaf/features/internal/region/CandidateComparator.java index 35d6043..dfbfd05 100644 --- a/features/core/src/main/java/org/apache/karaf/features/internal/region/CandidateComparator.java +++ b/features/core/src/main/java/org/apache/karaf/features/internal/region/CandidateComparator.java @@ -17,6 +17,7 @@ package org.apache.karaf.features.internal.region; import java.util.Comparator; +import java.util.Set; import org.osgi.framework.Version; import org.osgi.framework.namespace.BundleNamespace; @@ -24,8 +25,16 @@ import org.osgi.framework.namespace.IdentityNamespace; import org.osgi.framework.namespace.PackageNamespace; import org.osgi.framework.wiring.BundleCapability; import org.osgi.resource.Capability; +import org.osgi.resource.Resource; public class CandidateComparator implements Comparator<Capability> { + + private final Set<Resource> mandatory; + + public CandidateComparator(Set<Resource> mandatory) { + this.mandatory = mandatory; + } + public int compare(Capability cap1, Capability cap2) { int c = 0; // Always prefer system bundle @@ -34,6 +43,14 @@ public class CandidateComparator implements Comparator<Capability> { } else if (!(cap1 instanceof BundleCapability) && cap2 instanceof BundleCapability) { c = 1; } + // Always prefer mandatory resources + if (c == 0) { + if (mandatory.contains(cap1.getResource()) && !mandatory.contains(cap2.getResource())) { + c = -1; + } else if (!mandatory.contains(cap1.getResource()) && mandatory.contains(cap2.getResource())) { + c = 1; + } + } // Compare revision capabilities. if ((c == 0) && cap1.getNamespace().equals(BundleNamespace.BUNDLE_NAMESPACE)) { c = ((Comparable<Object>) cap1.getAttributes().get(BundleNamespace.BUNDLE_NAMESPACE)) http://git-wip-us.apache.org/repos/asf/karaf/blob/e164271c/features/core/src/main/java/org/apache/karaf/features/internal/region/Subsystem.java ---------------------------------------------------------------------- diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/region/Subsystem.java b/features/core/src/main/java/org/apache/karaf/features/internal/region/Subsystem.java index 30a789a..3318885 100644 --- a/features/core/src/main/java/org/apache/karaf/features/internal/region/Subsystem.java +++ b/features/core/src/main/java/org/apache/karaf/features/internal/region/Subsystem.java @@ -93,6 +93,7 @@ public class Subsystem extends ResourceImpl { private final Map<String, Set<String>> exportPolicy; private final List<Resource> installable = new ArrayList<>(); private final Map<String, DependencyInfo> dependencies = new HashMap<>(); + private final List<Requirement> dependentFeatures = new ArrayList<>(); private final List<String> bundles = new ArrayList<>(); @@ -122,13 +123,10 @@ public class Subsystem extends ResourceImpl { this.exportPolicy = SHARE_ALL_POLICY; } - Map<String, String> dirs = new HashMap<>(); - Map<String, Object> attrs = new HashMap<>(); - attrs.put(IDENTITY_NAMESPACE, feature.getName()); - attrs.put(CAPABILITY_TYPE_ATTRIBUTE, TYPE_FEATURE); - attrs.put(CAPABILITY_VERSION_ATTRIBUTE, new VersionRange(VersionTable.getVersion(feature.getVersion()), true)); - Requirement requirement = new RequirementImpl(this, IDENTITY_NAMESPACE, dirs, attrs); - addRequirement(requirement); + addIdentityRequirement(this, + feature.getName(), + TYPE_FEATURE, + new VersionRange(VersionTable.getVersion(feature.getVersion()), true)); } public Subsystem(String name, Subsystem parent, boolean acceptDependencies) { @@ -191,13 +189,7 @@ public class Subsystem extends ResourceImpl { Subsystem as = new Subsystem(childName, this, acceptDependencies); children.add(as); // Add a requirement to force its resolution - Map<String, Object> attrs = new HashMap<>(); - attrs.put(IDENTITY_NAMESPACE, childName); - attrs.put(CAPABILITY_TYPE_ATTRIBUTE, TYPE_SUBSYSTEM); - Requirement requirement = new RequirementImpl(this, IDENTITY_NAMESPACE, - Collections.<String, String>emptyMap(), - attrs); - addRequirement(requirement); + ResourceUtils.addIdentityRequirement(this, childName, TYPE_SUBSYSTEM, (VersionRange) null); // Add it to repo installable.add(as); return as; @@ -207,8 +199,14 @@ public class Subsystem extends ResourceImpl { installable.add(resource); } - public void requireFeature(String name, String range) { - ResourceUtils.addIdentityRequirement(this, name, TYPE_FEATURE, range); + public void requireFeature(String name, String range, boolean mandatory) { + if (mandatory) { + ResourceUtils.addIdentityRequirement(this, name, TYPE_FEATURE, range); + } else { + ResourceImpl res = new ResourceImpl(); + ResourceUtils.addIdentityRequirement(res, name, TYPE_FEATURE, range); + dependentFeatures.addAll(res.getRequirements(null)); + } } public void require(String requirement) throws BundleException { @@ -263,12 +261,13 @@ public class Subsystem extends ResourceImpl { while (!ss.isAcceptDependencies()) { ss = ss.getParent(); } - ss.requireFeature(dep.getName(), dep.getVersion()); + ss.requireFeature(dep.getName(), dep.getVersion(), !dep.isDependency()); } } List<Requirement> processed = new ArrayList<>(); while (true) { List<Requirement> requirements = getRequirements(IDENTITY_NAMESPACE); + requirements.addAll(dependentFeatures); requirements.removeAll(processed); if (requirements.isEmpty()) { break; http://git-wip-us.apache.org/repos/asf/karaf/blob/e164271c/features/core/src/main/java/org/apache/karaf/features/internal/region/SubsystemResolveContext.java ---------------------------------------------------------------------- diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/region/SubsystemResolveContext.java b/features/core/src/main/java/org/apache/karaf/features/internal/region/SubsystemResolveContext.java index da435de..8b6fc6a 100644 --- a/features/core/src/main/java/org/apache/karaf/features/internal/region/SubsystemResolveContext.java +++ b/features/core/src/main/java/org/apache/karaf/features/internal/region/SubsystemResolveContext.java @@ -21,9 +21,11 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; 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 org.apache.felix.resolver.Util; import org.apache.karaf.features.internal.download.Downloader; @@ -53,12 +55,14 @@ import static org.osgi.framework.Constants.RESOLUTION_DIRECTIVE; import static org.osgi.framework.Constants.RESOLUTION_OPTIONAL; import static org.osgi.framework.namespace.IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE; import static org.osgi.framework.namespace.IdentityNamespace.IDENTITY_NAMESPACE; +import static org.osgi.resource.Namespace.REQUIREMENT_RESOLUTION_DIRECTIVE; public class SubsystemResolveContext extends ResolveContext { private final Subsystem root; private final RegionDigraph digraph; - private final CandidateComparator candidateComparator = new CandidateComparator(); + private final Set<Resource> mandatory = new HashSet<>(); + private final CandidateComparator candidateComparator = new CandidateComparator(mandatory); private final Map<Resource, Subsystem> resToSub = new HashMap<Resource, Subsystem>(); private final Repository repository; @@ -73,6 +77,25 @@ public class SubsystemResolveContext extends ResolveContext { prepare(root); repository = new BaseRepository(resToSub.keySet()); + + // Add a heuristic to sort capabilities : + // if a capability comes from a resource which needs to be installed, + // prefer that one over any capabilities from other resources + findMandatory(root); + } + + void findMandatory(Resource res) { + if (mandatory.add(res)) { + for (Requirement req : res.getRequirements(null)) { + String resolution = req.getDirectives().get(REQUIREMENT_RESOLUTION_DIRECTIVE); + if (!RESOLUTION_OPTIONAL.equals(resolution)) { + List<Capability> caps = findProviders(req); + if (caps.size() == 1) { + findMandatory(caps.get(0).getResource()); + } + } + } + } } void prepare(Subsystem subsystem) { http://git-wip-us.apache.org/repos/asf/karaf/blob/e164271c/features/core/src/main/java/org/apache/karaf/features/internal/resolver/FeatureResource.java ---------------------------------------------------------------------- diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/resolver/FeatureResource.java b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/FeatureResource.java index 15cfcc1..73cb7a1 100644 --- a/features/core/src/main/java/org/apache/karaf/features/internal/resolver/FeatureResource.java +++ b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/FeatureResource.java @@ -80,7 +80,9 @@ public final class FeatureResource extends ResourceImpl { } } for (Dependency dep : feature.getDependencies()) { - addDependency(resource, dep, featureRange); + if (!dep.isDependency()) { + addDependency(resource, dep, featureRange); + } } for (org.apache.karaf.features.Capability cap : feature.getCapabilities()) { resource.addCapabilities(ResourceBuilder.parseCapability(resource, cap.getValue())); http://git-wip-us.apache.org/repos/asf/karaf/blob/e164271c/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceUtils.java ---------------------------------------------------------------------- diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceUtils.java b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceUtils.java index 210c375..91ea5e1 100644 --- a/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceUtils.java +++ b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceUtils.java @@ -81,8 +81,23 @@ public final class ResourceUtils { } public static void addIdentityRequirement(ResourceImpl resource, String name, String type, String range) { + addIdentityRequirement(resource, name, type, range, true); + } + + public static void addIdentityRequirement(ResourceImpl resource, String name, String type, String range, boolean mandatory) { + addIdentityRequirement(resource, name, type, range != null ? new VersionRange(range) : null, mandatory); + } + + public static void addIdentityRequirement(ResourceImpl resource, String name, String type, VersionRange range) { + addIdentityRequirement(resource, name, type, range, true); + } + + public static void addIdentityRequirement(ResourceImpl resource, String name, String type, VersionRange range, boolean mandatory) { Map<String, String> dirs = new HashMap<>(); Map<String, Object> attrs = new HashMap<>(); + if (!mandatory) { + dirs.put(REQUIREMENT_RESOLUTION_DIRECTIVE, RESOLUTION_OPTIONAL); + } if (name != null) { attrs.put(IDENTITY_NAMESPACE, name); } @@ -90,7 +105,7 @@ public final class ResourceUtils { attrs.put(CAPABILITY_TYPE_ATTRIBUTE, type); } if (range != null) { - attrs.put(CAPABILITY_VERSION_ATTRIBUTE, new VersionRange(range)); + attrs.put(CAPABILITY_VERSION_ATTRIBUTE, range); } resource.addRequirement(new RequirementImpl(resource, IDENTITY_NAMESPACE, dirs, attrs)); } http://git-wip-us.apache.org/repos/asf/karaf/blob/e164271c/features/core/src/main/resources/org/apache/karaf/features/karaf-features-1.3.0.xsd ---------------------------------------------------------------------- diff --git a/features/core/src/main/resources/org/apache/karaf/features/karaf-features-1.3.0.xsd b/features/core/src/main/resources/org/apache/karaf/features/karaf-features-1.3.0.xsd index ef0f434..d145d7c 100644 --- a/features/core/src/main/resources/org/apache/karaf/features/karaf-features-1.3.0.xsd +++ b/features/core/src/main/resources/org/apache/karaf/features/karaf-features-1.3.0.xsd @@ -167,6 +167,7 @@ Dependency of feature. <xs:extension base="tns:featureName"> <xs:attribute name="version" type="xs:string" default="0.0.0" /> <xs:attribute name="prerequisite" type="xs:boolean" default="false"/> + <xs:attribute name="dependency" type="xs:boolean" default="false"/> </xs:extension> </xs:simpleContent> </xs:complexType> http://git-wip-us.apache.org/repos/asf/karaf/blob/e164271c/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 11f9b0a..89adfae 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 @@ -182,6 +182,49 @@ public class SubsystemTest { verify(resolver, expected); } + @Test + public void testFeatureOptional() throws Exception { + RepositoryImpl repo = new RepositoryImpl(getClass().getResource("data5/features.xml").toURI()); + + Map<String, Set<String>> features = new HashMap<String, Set<String>>(); + addToMapSet(features, "root", "f1"); + Map<String, Set<String>> expected = new HashMap<String, Set<String>>(); + addToMapSet(expected, "root", "a/1.0.0"); + addToMapSet(expected, "root", "b/1.0.0"); + + SubsystemResolver resolver = new SubsystemResolver(new TestDownloadManager(getClass(), "data5")); + resolver.prepare(Arrays.asList(repo.getFeatures()), + features, + Collections.<String, Set<BundleRevision>>emptyMap()); + resolver.resolve(Collections.<String>emptySet(), + FeaturesService.DEFAULT_FEATURE_RESOLUTION_RANGE, + null); + + verify(resolver, expected); + } + + @Test + public void testFeatureOptionalAlreadyProvided() throws Exception { + RepositoryImpl repo = new RepositoryImpl(getClass().getResource("data5/features.xml").toURI()); + + Map<String, Set<String>> features = new HashMap<String, Set<String>>(); + addToMapSet(features, "root", "f1"); + addToMapSet(features, "root", "f3"); + Map<String, Set<String>> expected = new HashMap<String, Set<String>>(); + addToMapSet(expected, "root", "a/1.0.0"); + addToMapSet(expected, "root", "c/1.0.0"); + + SubsystemResolver resolver = new SubsystemResolver(new TestDownloadManager(getClass(), "data5")); + resolver.prepare(Arrays.asList(repo.getFeatures()), + features, + Collections.<String, Set<BundleRevision>>emptyMap()); + resolver.resolve(Collections.<String>emptySet(), + FeaturesService.DEFAULT_FEATURE_RESOLUTION_RANGE, + null); + + verify(resolver, expected); + } + private void verify(SubsystemResolver resolver, Map<String, Set<String>> expected) { Map<String, Set<String>> mapping = getBundleNamesPerRegions(resolver); if (!expected.equals(mapping)) { http://git-wip-us.apache.org/repos/asf/karaf/blob/e164271c/features/core/src/test/resources/org/apache/karaf/features/internal/region/data5/a.mf ---------------------------------------------------------------------- diff --git a/features/core/src/test/resources/org/apache/karaf/features/internal/region/data5/a.mf b/features/core/src/test/resources/org/apache/karaf/features/internal/region/data5/a.mf new file mode 100644 index 0000000..20a7811 --- /dev/null +++ b/features/core/src/test/resources/org/apache/karaf/features/internal/region/data5/a.mf @@ -0,0 +1,5 @@ +Manifest-Version: 1 +Bundle-ManifestVersion: 2 +Bundle-SymbolicName: a +Bundle-Version: 1.0.0 + http://git-wip-us.apache.org/repos/asf/karaf/blob/e164271c/features/core/src/test/resources/org/apache/karaf/features/internal/region/data5/b.mf ---------------------------------------------------------------------- diff --git a/features/core/src/test/resources/org/apache/karaf/features/internal/region/data5/b.mf b/features/core/src/test/resources/org/apache/karaf/features/internal/region/data5/b.mf new file mode 100644 index 0000000..dc96158 --- /dev/null +++ b/features/core/src/test/resources/org/apache/karaf/features/internal/region/data5/b.mf @@ -0,0 +1,5 @@ +Manifest-Version: 1 +Bundle-ManifestVersion: 2 +Bundle-SymbolicName: b +Bundle-Version: 1.0.0 + http://git-wip-us.apache.org/repos/asf/karaf/blob/e164271c/features/core/src/test/resources/org/apache/karaf/features/internal/region/data5/c.mf ---------------------------------------------------------------------- diff --git a/features/core/src/test/resources/org/apache/karaf/features/internal/region/data5/c.mf b/features/core/src/test/resources/org/apache/karaf/features/internal/region/data5/c.mf new file mode 100644 index 0000000..8535efa --- /dev/null +++ b/features/core/src/test/resources/org/apache/karaf/features/internal/region/data5/c.mf @@ -0,0 +1,5 @@ +Manifest-Version: 1 +Bundle-ManifestVersion: 2 +Bundle-SymbolicName: c +Bundle-Version: 1.0.0 + http://git-wip-us.apache.org/repos/asf/karaf/blob/e164271c/features/core/src/test/resources/org/apache/karaf/features/internal/region/data5/features.xml ---------------------------------------------------------------------- diff --git a/features/core/src/test/resources/org/apache/karaf/features/internal/region/data5/features.xml b/features/core/src/test/resources/org/apache/karaf/features/internal/region/data5/features.xml new file mode 100644 index 0000000..1cfff56 --- /dev/null +++ b/features/core/src/test/resources/org/apache/karaf/features/internal/region/data5/features.xml @@ -0,0 +1,37 @@ +<?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="f1"> + <feature dependency="true">f2</feature> + <requirement>namespace;filter:="(namespace=foo)"</requirement> + <bundle>a</bundle> + </feature> + + <feature name="f2"> + <capability>namespace;namespace=foo</capability> + <bundle>b</bundle> + </feature> + + <feature name="f3"> + <capability>namespace;namespace=foo</capability> + <bundle>c</bundle> + </feature> + +</features> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/karaf/blob/e164271c/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 8c59514..f32f938 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 @@ -17,6 +17,7 @@ --> <features name="karaf" xmlns="http://karaf.apache.org/xmlns/features/v1.3.0"> <feature name="spring" version="2.5.6-SEC02"> + <feature dependency="true">hidden</feature> <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>
