You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@karaf.apache.org by gg...@apache.org on 2017/12/02 20:26:54 UTC
[karaf] 01/11: [KARAF-5376] Moving overrides and blacklist
mechanisms to "features processor" service
This is an automated email from the ASF dual-hosted git repository.
ggrzybek pushed a commit to branch KARAF-5376-overrides_v2
in repository https://gitbox.apache.org/repos/asf/karaf.git
commit 4533176b0eff9612d696e44fcf70c2e92ef690b0
Author: Grzegorz Grzybek <gr...@gmail.com>
AuthorDate: Mon Nov 6 10:26:26 2017 +0100
[KARAF-5376] Moving overrides and blacklist mechanisms to "features processor" service
---
features/core/pom.xml | 2 +-
.../org/apache/karaf/features/Blacklisting.java | 34 +++
.../java/org/apache/karaf/features/BundleInfo.java | 6 +-
.../java/org/apache/karaf/features/Feature.java | 2 +-
.../java/org/apache/karaf/features/Repository.java | 43 ++-
.../karaf/features/internal/model/Bundle.java | 36 ++-
.../karaf/features/internal/model/Feature.java | 12 +
.../model/processing/BundleReplacements.java | 101 +++++++
.../model/processing/FeatureReplacements.java | 81 ++++++
.../model/processing/FeaturesProcessing.java | 296 +++++++++++++++++++++
.../internal/model/processing/ObjectFactory.java | 30 +++
.../model/processing/OverrideBundleDependency.java | 111 ++++++++
.../internal/model/processing/package-info.java | 36 +++
.../karaf/features/internal/osgi/Activator.java | 9 +-
.../karaf/features/internal/region/Subsystem.java | 48 +++-
.../karaf/features/internal/service/Blacklist.java | 128 ++++++++-
.../karaf/features/internal/service/Deployer.java | 17 ++
.../internal/service/FeaturesProcessor.java | 44 +++
.../internal/service/FeaturesProcessorImpl.java | 197 ++++++++++++++
.../internal/service/FeaturesServiceConfig.java | 15 +-
.../internal/service/FeaturesServiceImpl.java | 3 +-
.../features/internal/service/LocationPattern.java | 198 ++++++++++++++
.../karaf/features/internal/service/Overrides.java | 5 +-
.../features/internal/service/RepositoryCache.java | 147 +++++-----
...positoryCache.java => RepositoryCacheImpl.java} | 41 ++-
.../features/internal/service/RepositoryImpl.java | 43 ++-
.../karaf/features/internal/service/State.java | 22 +-
.../features/karaf-features-processing-1.0.0.xsd | 236 ++++++++++++++++
.../features/internal/service/BlacklistTest.java | 49 ++--
.../internal/service/FeaturesProcessorTest.java | 200 ++++++++++++++
.../internal/service/FeaturesValidationTest.java | 2 +-
.../internal/service/LocationPatternTest.java | 162 +++++++++++
.../features/internal/service/OverridesTest.java | 4 +-
.../internal/service/RepositoryCacheTest.java | 70 +++++
.../features/internal/service/urn/Handler.java | 35 +++
features/core/src/test/resources/log4j.properties | 35 +++
...verrides.properties => blacklisted2.properties} | 9 +-
.../karaf/features/internal/service/fp01.xml | 26 ++
.../karaf/features/internal/service/fp02.xml | 28 ++
.../karaf/features/internal/service/fp03.xml | 34 +++
.../karaf/features/internal/service/fpi01.xml | 41 +++
.../karaf/features/internal/service/fpi02.xml | 29 ++
.../internal/service/org.apache.karaf.features.xml | 98 +++++++
.../features/internal/service/overrides.properties | 4 +-
.../internal/service/overrides2.properties | 37 +++
.../resources/org/apache/karaf/features/r1.xml | 20 ++
.../org/apache/karaf/profile/assembly/Builder.java | 2 +-
.../java/org/apache/karaf/util/maven/Parser.java | 66 +++++
.../java/org/apache/karaf/util/ParserTest.java | 37 ++-
.../karaf/webconsole/features/ExtendedFeature.java | 6 +
50 files changed, 2741 insertions(+), 196 deletions(-)
diff --git a/features/core/pom.xml b/features/core/pom.xml
index 9e5a272..028de45 100644
--- a/features/core/pom.xml
+++ b/features/core/pom.xml
@@ -89,7 +89,7 @@
<dependency>
<groupId>org.slf4j</groupId>
- <artifactId>slf4j-jdk14</artifactId>
+ <artifactId>slf4j-log4j12</artifactId>
<scope>test</scope>
</dependency>
diff --git a/features/core/src/main/java/org/apache/karaf/features/Blacklisting.java b/features/core/src/main/java/org/apache/karaf/features/Blacklisting.java
new file mode 100644
index 0000000..39a3ce0
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/Blacklisting.java
@@ -0,0 +1,34 @@
+/*
+ * 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;
+
+/**
+ * <p>Indication that given object may be <em>blacklisted</em>.</p>
+ * <p>Blacklisted item is available for query, but can't be used to alter system (e.g., blacklisted feature
+ * can't be installed)</p>
+ */
+public interface Blacklisting {
+
+ /**
+ * Returns <code>true</code> if this item is <em>blacklisted</em>.
+ * @return
+ */
+ boolean isBlacklisted();
+
+}
diff --git a/features/core/src/main/java/org/apache/karaf/features/BundleInfo.java b/features/core/src/main/java/org/apache/karaf/features/BundleInfo.java
index c1a4c05..306d8ea 100644
--- a/features/core/src/main/java/org/apache/karaf/features/BundleInfo.java
+++ b/features/core/src/main/java/org/apache/karaf/features/BundleInfo.java
@@ -19,14 +19,18 @@ package org.apache.karaf.features;
/**
* A bundle info holds info about a Bundle.
*/
-public interface BundleInfo {
+public interface BundleInfo extends Blacklisting {
String getLocation();
+ String getOriginalLocation();
+
int getStartLevel();
boolean isStart();
boolean isDependency();
+ boolean isOverriden();
+
}
diff --git a/features/core/src/main/java/org/apache/karaf/features/Feature.java b/features/core/src/main/java/org/apache/karaf/features/Feature.java
index 93450e8..ed4d077 100644
--- a/features/core/src/main/java/org/apache/karaf/features/Feature.java
+++ b/features/core/src/main/java/org/apache/karaf/features/Feature.java
@@ -21,7 +21,7 @@ import java.util.List;
/**
* A feature is a list of bundles associated identified by its name.
*/
-public interface Feature {
+public interface Feature extends Blacklisting {
String DEFAULT_INSTALL_MODE = "auto";
diff --git a/features/core/src/main/java/org/apache/karaf/features/Repository.java b/features/core/src/main/java/org/apache/karaf/features/Repository.java
index fd4ef85..adbd1a4 100644
--- a/features/core/src/main/java/org/apache/karaf/features/Repository.java
+++ b/features/core/src/main/java/org/apache/karaf/features/Repository.java
@@ -16,22 +16,43 @@
*/
package org.apache.karaf.features;
-import java.io.IOException;
import java.net.URI;
/**
- * A repository of features.
+ * <p>A repository of features. A runtime representation of JAXB model read from feature XML files.</p>
+ *
+ * <p>Original model may be subject to further processing (e.g., blacklisting)</p>
*/
-public interface Repository {
-
- String getName() throws IOException;
-
+public interface Repository extends Blacklisting {
+
+ /**
+ * Logical name of the {@link Repository}
+ * @return
+ */
+ String getName();
+
+ /**
+ * Original URI of the {@link Repository}, where feature declarations were loaded from
+ * @return
+ */
URI getURI();
- URI[] getRepositories() throws Exception;
-
- URI[] getResourceRepositories() throws Exception;
-
- Feature[] getFeatures() throws Exception;
+ /**
+ * An array of referenced repository URIs (<code>/features/repository</code>)
+ * @return
+ */
+ URI[] getRepositories();
+
+ /**
+ * An array of referenced resource repository URIs (<code>/features/resource-repository</code>)
+ * @return
+ */
+ URI[] getResourceRepositories();
+
+ /**
+ * An array of {@link Feature features} in this {@link Repository} after possible processing.
+ * @return
+ */
+ Feature[] getFeatures();
}
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/model/Bundle.java b/features/core/src/main/java/org/apache/karaf/features/internal/model/Bundle.java
index 8afa5ff..d0ba74f 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/model/Bundle.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/model/Bundle.java
@@ -20,6 +20,7 @@ import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlSchemaType;
+import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.XmlValue;
@@ -53,13 +54,19 @@ public class Bundle implements BundleInfo {
@XmlValue
@XmlSchemaType(name = "anyURI")
protected String value;
+ /** Original value may be queried if {@link #isOverriden()} is <code>true</code> */
+ @XmlTransient
+ protected String originalValue;
@XmlAttribute(name = "start-level")
protected Integer startLevel;
@XmlAttribute
protected Boolean start; // = true;
@XmlAttribute
protected Boolean dependency;
-
+ @XmlTransient
+ private boolean blacklisted = false;
+ @XmlTransient
+ private boolean overriden = false;
public Bundle() {
}
@@ -149,6 +156,33 @@ public class Bundle implements BundleInfo {
}
@Override
+ public boolean isBlacklisted() {
+ return blacklisted;
+ }
+
+ public void setBlacklisted(boolean blacklisted) {
+ this.blacklisted = blacklisted;
+ }
+
+ public boolean isOverriden() {
+ return overriden;
+ }
+
+ public void setOverriden(boolean overriden) {
+ this.overriden = overriden;
+
+ }
+
+ @Override
+ public String getOriginalLocation() {
+ return originalValue;
+ }
+
+ public void setOriginalLocation(String originalLocation) {
+ this.originalValue = originalLocation;
+ }
+
+ @Override
public boolean equals(Object o) {
if (this == o) {
return true;
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 54a739b..44216e4 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
@@ -113,6 +113,8 @@ public class Feature extends Content implements org.apache.karaf.features.Featur
protected List<String> resourceRepositories;
@XmlTransient
protected String repositoryUrl;
+ @XmlTransient
+ private boolean blacklisted;
public Feature() {
}
@@ -470,4 +472,14 @@ public class Feature extends Content implements org.apache.karaf.features.Featur
public void setRepositoryUrl(String repositoryUrl) {
this.repositoryUrl = repositoryUrl;
}
+
+ @Override
+ public boolean isBlacklisted() {
+ return blacklisted;
+ }
+
+ public void setBlacklisted(boolean blacklisted) {
+ this.blacklisted = blacklisted;
+ }
+
}
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/model/processing/BundleReplacements.java b/features/core/src/main/java/org/apache/karaf/features/internal/model/processing/BundleReplacements.java
new file mode 100644
index 0000000..f51a8b9
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/model/processing/BundleReplacements.java
@@ -0,0 +1,101 @@
+/*
+ * 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.model.processing;
+
+import java.net.MalformedURLException;
+import java.util.LinkedList;
+import java.util.List;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlEnum;
+import javax.xml.bind.annotation.XmlEnumValue;
+import javax.xml.bind.annotation.XmlTransient;
+import javax.xml.bind.annotation.XmlType;
+
+import org.apache.karaf.features.internal.service.LocationPattern;
+
+@XmlType(name = "bundleReplacements", propOrder = {
+ "overrideBundles"
+})
+public class BundleReplacements {
+
+ @XmlElement(name = "bundle")
+ private List<OverrideBundle> overrideBundles = new LinkedList<>();
+
+ public List<OverrideBundle> getOverrideBundles() {
+ return overrideBundles;
+ }
+
+ @XmlType(name = "bundleOverrideMode")
+ @XmlEnum
+ public enum BundleOverrideMode {
+ @XmlEnumValue("osgi")
+ OSGI,
+ @XmlEnumValue("maven")
+ MAVEN
+ }
+
+ @XmlType(name = "overrideBundle")
+ public static class OverrideBundle {
+ @XmlAttribute
+ private String originalUri;
+ @XmlTransient
+ private LocationPattern originalUriPattern;
+ @XmlAttribute
+ private String replacement;
+ @XmlAttribute
+ private BundleOverrideMode mode = BundleOverrideMode.OSGI;
+
+ public String getOriginalUri() {
+ return originalUri;
+ }
+
+ public void setOriginalUri(String originalUri) {
+ this.originalUri = originalUri;
+ }
+
+ public String getReplacement() {
+ return replacement;
+ }
+
+ public void setReplacement(String replacement) {
+ this.replacement = replacement;
+ }
+
+ public BundleOverrideMode getMode() {
+ return mode;
+ }
+
+ public void setMode(BundleOverrideMode mode) {
+ this.mode = mode;
+ }
+
+ public LocationPattern getOriginalUriPattern() {
+ return originalUriPattern;
+ }
+
+ /**
+ * Changes String for <code>originalUri</code> into {@link org.apache.karaf.features.internal.service.LocationPattern}
+ */
+ public void compile() throws MalformedURLException {
+ originalUriPattern = new LocationPattern(originalUri);
+ }
+ }
+
+}
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/model/processing/FeatureReplacements.java b/features/core/src/main/java/org/apache/karaf/features/internal/model/processing/FeatureReplacements.java
new file mode 100644
index 0000000..2f36dd2
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/model/processing/FeatureReplacements.java
@@ -0,0 +1,81 @@
+/*
+ * 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.model.processing;
+
+import java.util.LinkedList;
+import java.util.List;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlEnum;
+import javax.xml.bind.annotation.XmlEnumValue;
+import javax.xml.bind.annotation.XmlType;
+
+import org.apache.karaf.features.FeaturesNamespaces;
+import org.apache.karaf.features.internal.model.Feature;
+
+@XmlType(name = "featureReplacements", propOrder = {
+ "replacements"
+})
+public class FeatureReplacements {
+
+ @XmlElement(name = "replacement")
+ private List<OverrideFeature> replacements = new LinkedList<>();
+
+ public List<OverrideFeature> getReplacements() {
+ return replacements;
+ }
+
+ @XmlType(name = "featureOverrideMode")
+ @XmlEnum
+ public enum FeatureOverrideMode {
+ @XmlEnumValue("replace")
+ REPLACE,
+ @XmlEnumValue("merge")
+ MERGE,
+ @XmlEnumValue("remove")
+ REMOVE
+ }
+
+ @XmlType(name = "overrideFeature", propOrder = {
+ "feature"
+ })
+ public static class OverrideFeature {
+ @XmlAttribute
+ private FeatureOverrideMode mode = FeatureOverrideMode.REPLACE;
+ @XmlElement
+ private Feature feature;
+
+ public FeatureOverrideMode getMode() {
+ return mode;
+ }
+
+ public void setMode(FeatureOverrideMode mode) {
+ this.mode = mode;
+ }
+
+ public Feature getFeature() {
+ return feature;
+ }
+
+ public void setFeature(Feature feature) {
+ this.feature = feature;
+ }
+ }
+
+}
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/model/processing/FeaturesProcessing.java b/features/core/src/main/java/org/apache/karaf/features/internal/model/processing/FeaturesProcessing.java
new file mode 100644
index 0000000..142a16b
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/model/processing/FeaturesProcessing.java
@@ -0,0 +1,296 @@
+/*
+ * 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.model.processing;
+
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlTransient;
+import javax.xml.bind.annotation.XmlType;
+import javax.xml.bind.annotation.XmlValue;
+
+import org.apache.felix.utils.manifest.Clause;
+import org.apache.felix.utils.manifest.Parser;
+import org.apache.felix.utils.version.VersionCleaner;
+import org.apache.felix.utils.version.VersionRange;
+import org.apache.karaf.features.internal.service.Blacklist;
+import org.apache.karaf.features.internal.service.LocationPattern;
+import org.osgi.framework.Version;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.apache.karaf.features.internal.service.Overrides.OVERRIDE_RANGE;
+
+/**
+ * A set of instructions to process {@link org.apache.karaf.features.internal.model.Features} model. The actual
+ * use of these instructions is moved to {@link org.apache.karaf.features.internal.service.FeaturesProcessorImpl}
+ */
+@XmlRootElement(name = "featuresProcessing", namespace = FeaturesProcessing.FEATURES_PROCESSING_NS)
+@XmlType(name = "featuresProcessing", propOrder = {
+ "blacklistedRepositories",
+ "blacklistedFeatures",
+ "blacklistedBundles",
+ "overrideBundleDependency",
+ "bundleReplacements",
+ "featureReplacements"
+})
+public class FeaturesProcessing {
+
+ public static Logger LOG = LoggerFactory.getLogger(FeaturesProcessing.class);
+ public static final String FEATURES_PROCESSING_NS = "http://karaf.apache.org/xmlns/features-processing/v1.0.0";
+
+ @XmlElementWrapper(name = "blacklistedRepositories")
+ @XmlElement(name = "repository")
+ private List<String> blacklistedRepositories = new LinkedList<>();
+ @XmlTransient
+ private List<LocationPattern> blacklistedRepositoryLocationPatterns = new LinkedList<>();
+
+ @XmlElementWrapper(name = "blacklistedFeatures")
+ @XmlElement(name = "feature")
+ private List<BlacklistedFeature> blacklistedFeatures = new LinkedList<>();
+
+ @XmlElementWrapper(name = "blacklistedBundles")
+ @XmlElement(name = "bundle")
+ private List<String> blacklistedBundles = new LinkedList<>();
+
+ @XmlElement
+ private OverrideBundleDependency overrideBundleDependency;
+
+ @XmlElement
+ private BundleReplacements bundleReplacements;
+
+ @XmlElement
+ private FeatureReplacements featureReplacements;
+
+ @XmlTransient
+ private Blacklist blacklist;
+
+ public FeaturesProcessing() {
+ }
+
+ public List<String> getBlacklistedRepositories() {
+ return blacklistedRepositories;
+ }
+
+ public List<LocationPattern> getBlacklistedRepositoryLocationPatterns() {
+ return blacklistedRepositoryLocationPatterns;
+ }
+
+ public List<BlacklistedFeature> getBlacklistedFeatures() {
+ return blacklistedFeatures;
+ }
+
+ public List<String> getBlacklistedBundles() {
+ return blacklistedBundles;
+ }
+
+ public OverrideBundleDependency getOverrideBundleDependency() {
+ return overrideBundleDependency;
+ }
+
+ public void setOverrideBundleDependency(OverrideBundleDependency overrideBundleDependency) {
+ this.overrideBundleDependency = overrideBundleDependency;
+ }
+
+ public BundleReplacements getBundleReplacements() {
+ return bundleReplacements;
+ }
+
+ public void setBundleReplacements(BundleReplacements bundleReplacements) {
+ this.bundleReplacements = bundleReplacements;
+ }
+
+ public FeatureReplacements getFeatureReplacements() {
+ return featureReplacements;
+ }
+
+ public void setFeatureReplacements(FeatureReplacements featureReplacements) {
+ this.featureReplacements = featureReplacements;
+ }
+
+ public Blacklist getBlacklist() {
+ return blacklist;
+ }
+
+ /**
+ * Perform <em>compilation</em> of rules declared in feature processing XML file.
+ * @param blacklist additional {@link Blacklist} definition with lower priority
+ * @param overrides additional overrides definition with lower priority
+ */
+ public void postUnmarshall(Blacklist blacklist, Set<String> overrides) {
+ // compile blacklisted repository URIs
+ for (String repositoryURI : this.getBlacklistedRepositories()) {
+ try {
+ blacklistedRepositoryLocationPatterns.add(new LocationPattern(repositoryURI));
+ } catch (MalformedURLException e) {
+ LOG.warn("Can't parse blacklisted repository location pattern: " + repositoryURI + ". Ignoring.");
+ }
+ }
+
+ // verify bundle override definitions
+ for (Iterator<BundleReplacements.OverrideBundle> iterator = this.bundleReplacements.getOverrideBundles().iterator(); iterator.hasNext(); ) {
+ BundleReplacements.OverrideBundle overrideBundle = iterator.next();
+ if (overrideBundle.getOriginalUri() == null) {
+ // we have to derive it from replacement - as with etc/overrides.properties entry
+ if (overrideBundle.getMode() == BundleReplacements.BundleOverrideMode.MAVEN) {
+ LOG.warn("Can't override bundle in maven mode without explicit original URL. Switching to osgi mode.");
+ overrideBundle.setMode(BundleReplacements.BundleOverrideMode.OSGI);
+ }
+ String originalUri = calculateOverridenURI(overrideBundle.getReplacement(), null);
+ if (originalUri != null) {
+ overrideBundle.setOriginalUri(originalUri);
+ } else {
+ iterator.remove();
+ continue;
+ }
+ }
+ try {
+ overrideBundle.compile();
+ } catch (MalformedURLException e) {
+ LOG.warn("Can't parse override URL location pattern: " + overrideBundle.getOriginalUri() + ". Ignoring.");
+ iterator.remove();
+ }
+ }
+
+ // etc/blacklisted.properties
+ // blacklisted bundle from XML to instruction for Blacklist class
+ List<String> blacklisted = new LinkedList<>();
+ for (String bl : this.getBlacklistedBundles()) {
+ blacklisted.add(bl + ";type=bundle");
+ }
+ // blacklisted features - XML type to String instruction for Blacklist class
+ blacklisted.addAll(this.getBlacklistedFeatures().stream()
+ .map(bf -> bf.getName() + ";type=feature" + (bf.getVersion() == null ? "" : ";range=\"" + bf.getVersion() + "\""))
+ .collect(Collectors.toList()));
+
+ this.blacklist = new Blacklist(blacklisted);
+ this.blacklist.merge(blacklist);
+
+ // etc/overrides.properties (mvn: URIs)
+ for (Clause clause : Parser.parseClauses(overrides.toArray(new String[overrides.size()]))) {
+ // name of the clause will become a bundle replacement
+ String mvnURI = clause.getName();
+ URI uri = URI.create(mvnURI);
+ if (!"mvn".equals(uri.getScheme())) {
+ LOG.warn("Override URI \"" + mvnURI + "\" should use mvn: scheme. Ignoring.");
+ continue;
+ }
+ BundleReplacements.OverrideBundle override = new BundleReplacements.OverrideBundle();
+ override.setMode(BundleReplacements.BundleOverrideMode.OSGI);
+ override.setReplacement(mvnURI);
+ String originalUri = calculateOverridenURI(mvnURI, clause.getAttribute(OVERRIDE_RANGE));
+ if (originalUri != null) {
+ override.setOriginalUri(originalUri);
+ try {
+ override.compile();
+ bundleReplacements.getOverrideBundles().add(override);
+ } catch (MalformedURLException e) {
+ LOG.warn("Can't parse override URL location pattern: " + originalUri + ". Ignoring.");
+ }
+ }
+ }
+ }
+
+ /**
+ * For <code>etc/overrides.properties</code>, we know what is the target URI for bundles we should use. We need
+ * a pattern of original bundle URIs that are candidates for replacement
+ * @param replacement
+ * @param range
+ * @return
+ */
+ private String calculateOverridenURI(String replacement, String range) {
+ try {
+ org.apache.karaf.util.maven.Parser parser = new org.apache.karaf.util.maven.Parser(replacement);
+ if (parser.getVersion() != null
+ && (parser.getVersion().startsWith("[") || parser.getVersion().startsWith("("))) {
+ // replacement URI should not contain ranges
+ throw new MalformedURLException("Override URI should use single version.");
+ }
+ if (range != null) {
+ // explicit range determines originalUri
+ VersionRange vr = new VersionRange(range, true);
+ if (vr.isOpenCeiling() && vr.getCeiling() == VersionRange.INFINITE_VERSION) {
+ // toString() will give only floor version
+ parser.setVersion(String.format("%s%s,*)",
+ vr.isOpenFloor() ? "(" : "[",
+ vr.getFloor()));
+ } else {
+ parser.setVersion(vr.toString());
+ }
+ } else {
+ // no range: originalUri based on replacemenet URI with range deducted using default rules
+ // assume version in override URI is NOT a range
+ Version v;
+ try {
+ v = new Version(VersionCleaner.clean(parser.getVersion()));
+ } catch (IllegalArgumentException e) {
+ LOG.warn("Problem parsing override URI \"" + replacement + "\": " + e.getMessage() + ". Version ranges are not handled. Ignoring.");
+ return null;
+ }
+ Version vfloor = new Version(v.getMajor(), v.getMinor(), 0, null);
+ parser.setVersion(new VersionRange(false, vfloor, v, true).toString());
+ }
+ return parser.toMvnURI();
+ } catch (MalformedURLException e) {
+ LOG.warn("Problem parsing override URI \"" + replacement + "\": " + e.getMessage() + ". Ignoring.");
+ return null;
+ }
+ }
+
+ @XmlType(name = "blacklistedFeature")
+ public static class BlacklistedFeature {
+ @XmlValue
+ private String name;
+ @XmlAttribute
+ private String version;
+
+ public BlacklistedFeature() {
+ }
+
+ public BlacklistedFeature(String name, String version) {
+ this.name = name;
+ this.version = version;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getVersion() {
+ return version;
+ }
+
+ public void setVersion(String version) {
+ this.version = version;
+ }
+ }
+
+}
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/model/processing/ObjectFactory.java b/features/core/src/main/java/org/apache/karaf/features/internal/model/processing/ObjectFactory.java
new file mode 100644
index 0000000..e1bdede
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/model/processing/ObjectFactory.java
@@ -0,0 +1,30 @@
+/*
+ * 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.model.processing;
+
+import javax.xml.bind.annotation.XmlRegistry;
+
+@XmlRegistry
+public class ObjectFactory {
+
+ public FeaturesProcessing createFeaturesProcessing() {
+ return new FeaturesProcessing();
+ }
+
+}
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/model/processing/OverrideBundleDependency.java b/features/core/src/main/java/org/apache/karaf/features/internal/model/processing/OverrideBundleDependency.java
new file mode 100644
index 0000000..398e0c2
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/model/processing/OverrideBundleDependency.java
@@ -0,0 +1,111 @@
+/*
+ * 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.model.processing;
+
+import java.util.LinkedList;
+import java.util.List;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlType;
+
+@XmlType(name = "overrideBundleDependency", propOrder = {
+ "repositories",
+ "features",
+ "bundles"
+})
+public class OverrideBundleDependency {
+
+ @XmlElement(name = "repository")
+ private List<OverrideDependency> repositories = new LinkedList<>();
+ @XmlElement(name = "feature")
+ private List<OverrideFeatureDependency> features = new LinkedList<>();
+ @XmlElement(name = "bundle")
+ private List<OverrideDependency> bundles = new LinkedList<>();
+
+ public List<OverrideDependency> getRepositories() {
+ return repositories;
+ }
+
+ public List<OverrideFeatureDependency> getFeatures() {
+ return features;
+ }
+
+ public List<OverrideDependency> getBundles() {
+ return bundles;
+ }
+
+ @XmlType(name = "overrideDependency")
+ public static class OverrideDependency {
+ @XmlAttribute
+ private String uri;
+ @XmlAttribute
+ private boolean dependency = false;
+
+ public String getUri() {
+ return uri;
+ }
+
+ public void setUri(String uri) {
+ this.uri = uri;
+ }
+
+ public boolean isDependency() {
+ return dependency;
+ }
+
+ public void setDependency(boolean dependency) {
+ this.dependency = dependency;
+ }
+ }
+
+ @XmlType(name = "overrideFeatureDependency")
+ public static class OverrideFeatureDependency {
+ @XmlAttribute
+ private String name;
+ @XmlAttribute
+ private String version;
+ @XmlAttribute
+ private boolean dependency = false;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getVersion() {
+ return version;
+ }
+
+ public void setVersion(String version) {
+ this.version = version;
+ }
+
+ public boolean isDependency() {
+ return dependency;
+ }
+
+ public void setDependency(boolean dependency) {
+ this.dependency = dependency;
+ }
+ }
+
+}
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/model/processing/package-info.java b/features/core/src/main/java/org/apache/karaf/features/internal/model/processing/package-info.java
new file mode 100644
index 0000000..8086150
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/model/processing/package-info.java
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ */
+@XmlSchema(namespace = "http://karaf.apache.org/xmlns/features-processing/v1.0.0",
+ elementFormDefault = XmlNsForm.QUALIFIED, attributeFormDefault = XmlNsForm.UNQUALIFIED,
+ xmlns = {
+ @XmlNs(prefix = "", namespaceURI = FEATURES_PROCESSING_NS
+ ),
+ @XmlNs(prefix = "f", namespaceURI = FeaturesNamespaces.URI_CURRENT)
+ }
+)
+@XmlAccessorType(XmlAccessType.FIELD)
+package org.apache.karaf.features.internal.model.processing;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlNs;
+import javax.xml.bind.annotation.XmlNsForm;
+import javax.xml.bind.annotation.XmlSchema;
+
+import org.apache.karaf.features.FeaturesNamespaces;
+
+import static org.apache.karaf.features.internal.model.processing.FeaturesProcessing.FEATURES_PROCESSING_NS;
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 6881d47..ad6d940 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
@@ -28,9 +28,7 @@ import java.util.Dictionary;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
-import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@@ -93,6 +91,7 @@ import org.slf4j.LoggerFactory;
public class Activator extends BaseActivator {
public static final String FEATURES_SERVICE_CONFIG_FILE = "org.apache.karaf.features.cfg";
+ public static final String FEATURES_SERVICE_PROCESSING_FILE = "org.apache.karaf.features.xml";
private static final String STATE_FILE = "state.json";
@@ -228,15 +227,17 @@ public class Activator extends BaseActivator {
}
private FeaturesServiceConfig getConfig() {
+ String karafEtc = System.getProperty("karaf.etc");
return new FeaturesServiceConfig(
- getString("overrides", new File(System.getProperty("karaf.etc"), "overrides.properties").toURI().toString()),
+ getString("overrides", new File(karafEtc, "overrides.properties").toURI().toString()),
getString("featureResolutionRange", FeaturesService.DEFAULT_FEATURE_RESOLUTION_RANGE),
getString("bundleUpdateRange", FeaturesService.DEFAULT_BUNDLE_UPDATE_RANGE),
getString("updateSnapshots", FeaturesService.DEFAULT_UPDATE_SNAPSHOTS),
getInt("downloadThreads", FeaturesService.DEFAULT_DOWNLOAD_THREADS),
getLong("scheduleDelay", FeaturesService.DEFAULT_SCHEDULE_DELAY),
getInt("scheduleMaxRun", FeaturesService.DEFAULT_SCHEDULE_MAX_RUN),
- getString("blacklisted", new File(System.getProperty("karaf.etc"), "blacklisted.properties").toURI().toString()),
+ getString("blacklisted", new File(karafEtc, "blacklisted.properties").toURI().toString()),
+ getString("featureProcessing", new File(karafEtc, FEATURES_SERVICE_PROCESSING_FILE).toURI().toString()),
getString("serviceRequirements", FeaturesService.SERVICE_REQUIREMENTS_DEFAULT));
}
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 e55e60d..cfd295e 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
@@ -451,23 +451,23 @@ public class Subsystem extends ResourceImpl {
}
boolean mandatory = !bi.isDependency() && cond == null;
if (bi.isDependency()) {
- addDependency(res, mandatory, bi.isStart(), sl);
+ addDependency(res, mandatory, bi.isStart(), sl, bi.isBlacklisted());
} else {
- doAddDependency(res, mandatory, bi.isStart(), sl);
+ doAddDependency(res, mandatory, bi.isStart(), sl, bi.isBlacklisted());
}
}
for (Library library : feature.getLibraries()) {
if (library.isExport()) {
final String loc = library.getLocation();
ResourceImpl res = bundles.get(loc);
- addDependency(res, false, false, 0);
+ addDependency(res, false, false, 0, false);
}
}
for (String uri : feature.getResourceRepositories()) {
BaseRepository repo = repos.getRepository(feature.getRepositoryUrl(), uri);
for (Resource resource : repo.getResources()) {
ResourceImpl res = cloneResource(resource);
- addDependency(res, false, true, 0);
+ addDependency(res, false, true, 0, false);
}
}
}
@@ -475,6 +475,7 @@ public class Subsystem extends ResourceImpl {
final String loc = bundle.getName();
boolean dependency = Boolean.parseBoolean(bundle.getAttribute("dependency"));
boolean start = bundle.getAttribute("start") == null || Boolean.parseBoolean(bundle.getAttribute("start"));
+ boolean blacklisted = bundle.getAttribute("blacklisted") != null && Boolean.parseBoolean(bundle.getAttribute("blacklisted"));
int startLevel = 0;
try {
startLevel = Integer.parseInt(bundle.getAttribute("start-level"));
@@ -482,9 +483,9 @@ public class Subsystem extends ResourceImpl {
// Ignore
}
if (dependency) {
- addDependency(bundles.get(loc), false, start, startLevel);
+ addDependency(bundles.get(loc), false, start, startLevel, blacklisted);
} else {
- doAddDependency(bundles.get(loc), true, start, startLevel);
+ doAddDependency(bundles.get(loc), true, start, startLevel, blacklisted);
addIdentityRequirement(this, bundles.get(loc));
}
}
@@ -535,17 +536,17 @@ public class Subsystem extends ResourceImpl {
throw new IllegalArgumentException("Resource " + provider.getUrl() + " does not contain a manifest");
}
- void addDependency(ResourceImpl resource, boolean mandatory, boolean start, int startLevel) {
+ void addDependency(ResourceImpl resource, boolean mandatory, boolean start, int startLevel, boolean blacklisted) {
if (isAcceptDependencies()) {
- doAddDependency(resource, mandatory, start, startLevel);
+ doAddDependency(resource, mandatory, start, startLevel, blacklisted);
} else {
- parent.addDependency(resource, mandatory, start, startLevel);
+ parent.addDependency(resource, mandatory, start, startLevel, blacklisted);
}
}
- private void doAddDependency(ResourceImpl resource, boolean mandatory, boolean start, int startLevel) {
+ private void doAddDependency(ResourceImpl resource, boolean mandatory, boolean start, int startLevel, boolean blacklisted) {
String id = ResolverUtil.getSymbolicName(resource) + "|" + ResolverUtil.getVersion(resource);
- DependencyInfo info = new DependencyInfo(resource, mandatory, start, startLevel);
+ DependencyInfo info = new DependencyInfo(resource, mandatory, start, startLevel, blacklisted);
dependencies.merge(id, info, this::merge);
}
@@ -589,15 +590,18 @@ public class Subsystem extends ResourceImpl {
boolean mandatory;
boolean start;
int startLevel;
+ boolean blacklisted;
+ boolean overriden;
public DependencyInfo() {
}
- public DependencyInfo(ResourceImpl resource, boolean mandatory, boolean start, int startLevel) {
+ public DependencyInfo(ResourceImpl resource, boolean mandatory, boolean start, int startLevel, boolean blacklisted) {
this.resource = resource;
this.mandatory = mandatory;
this.start = start;
this.startLevel = startLevel;
+ this.blacklisted = blacklisted;
}
@Override
@@ -616,11 +620,31 @@ public class Subsystem extends ResourceImpl {
}
@Override
+ public String getOriginalLocation() {
+ // resource is already overriden
+ return getUri(resource);
+ }
+
+ @Override
public boolean isDependency() {
return !mandatory;
}
@Override
+ public boolean isBlacklisted() {
+ return blacklisted;
+ }
+
+ @Override
+ public boolean isOverriden() {
+ return overriden;
+ }
+
+ public void setOverriden(boolean overriden) {
+ this.overriden = overriden;
+ }
+
+ @Override
public String toString() {
return "DependencyInfo{" +
"resource=" + resource +
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/Blacklist.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/Blacklist.java
index ac971fc..8c6bfb9 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/service/Blacklist.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/Blacklist.java
@@ -16,18 +16,21 @@
*/
package org.apache.karaf.features.internal.service;
-import static java.util.stream.Collectors.toSet;
-
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.net.MalformedURLException;
import java.net.URL;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
+import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Map;
import java.util.Set;
+import java.util.regex.Pattern;
import org.apache.felix.utils.manifest.Clause;
import org.apache.felix.utils.manifest.Parser;
@@ -45,24 +48,28 @@ import org.slf4j.LoggerFactory;
*/
public class Blacklist {
+ public static Logger LOG = LoggerFactory.getLogger(Blacklist.class);
+
public static final String BLACKLIST_URL = "url";
public static final String BLACKLIST_RANGE = "range";
- public static final String BLACKLIST_TYPE = "type";
+ public static final String BLACKLIST_TYPE = "type"; // null -> "feature"
public static final String TYPE_FEATURE = "feature";
public static final String TYPE_BUNDLE = "bundle";
public static final String TYPE_REPOSITORY = "repository";
private static final Logger LOGGER = LoggerFactory.getLogger(Blacklist.class);
private Clause[] clauses;
-
+ private Map<String, LocationPattern> bundleBlacklist = new LinkedHashMap<>();
+
public Blacklist() {
this(Collections.emptyList());
}
public Blacklist(List<String> blacklist) {
- this.clauses = org.apache.felix.utils.manifest.Parser.parseClauses(blacklist.toArray(new String[blacklist.size()]));
+ this.clauses = Parser.parseClauses(blacklist.toArray(new String[blacklist.size()]));
+ compileClauses();
}
-
+
public Blacklist(String blacklistUrl) {
Set<String> blacklist = new HashSet<>();
if (blacklistUrl != null) {
@@ -79,8 +86,53 @@ public class Blacklist {
}
}
this.clauses = Parser.parseClauses(blacklist.toArray(new String[blacklist.size()]));
+ compileClauses();
}
+ /**
+ * Extracts blacklisting clauses related to bundles, features and repositories and changes them to more
+ * usable form.
+ */
+ private void compileClauses() {
+ for (Clause c : clauses) {
+ String type = c.getAttribute(BLACKLIST_TYPE);
+ if (type == null) {
+ String url = c.getAttribute(BLACKLIST_URL);
+ if (url != null || c.getName().startsWith("mvn:")) {
+ // some special rules from etc/blacklisted.properties
+ type = TYPE_BUNDLE;
+ } else {
+ type = TYPE_FEATURE;
+ }
+ }
+ switch (type) {
+ case TYPE_FEATURE:
+ break;
+ case TYPE_BUNDLE:
+ String location = c.getName();
+ if (c.getAttribute(BLACKLIST_URL) != null) {
+ location = c.getAttribute(BLACKLIST_URL);
+ }
+ if (location == null) {
+ // should not happen?
+ LOG.warn("Bundle blacklist URI is empty. Ignoring.");
+ } else {
+ try {
+ bundleBlacklist.put(location, location.startsWith("mvn:") ? new LocationPattern(location) : null);
+ } catch (MalformedURLException e) {
+ LOG.warn("Problem parsing blacklist URI \"" + location + "\": " + e.getMessage() + ". Ignoring.");
+ }
+ }
+ break;
+ case TYPE_REPOSITORY:
+ }
+ }
+ }
+
+ /**
+ * TODO: set {@link Feature#setBlacklisted(boolean)} instead of removing from collection
+ * @param features
+ */
public void blacklist(Features features) {
features.getFeature().removeIf(this::blacklist);
}
@@ -131,9 +183,39 @@ public class Blacklist {
}
}
+ public boolean isBundleBlacklisted(String uri) {
+ for (Map.Entry<String, LocationPattern> clause : bundleBlacklist.entrySet()) {
+ if (mavenMatches(clause.getKey(), clause.getValue(), uri)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Checks whether given <code>uri</code> matches Maven artifact pattern (group, artifact, optional type/classifier, version
+ * range, globs).
+ * @param blacklistedUri
+ * @param compiledUri
+ * @param uri
+ * @return
+ */
+ private boolean mavenMatches(String blacklistedUri, LocationPattern compiledUri, String uri) {
+ if (compiledUri == null) {
+ // non maven URI - we can't be smart
+ return blacklistedUri.equals(uri);
+ } else {
+ return compiledUri.matches(uri);
+ }
+ }
+
public boolean isFeatureBlacklisted(String name, String version) {
for (Clause clause : clauses) {
- if (clause.getName().equals(name)) {
+ String type = clause.getAttribute(BLACKLIST_TYPE);
+ if (type != null && !TYPE_FEATURE.equals(type)) {
+ continue;
+ }
+ if (Pattern.matches(clause.getName().replaceAll("\\*", ".*"), name)) {
// Check feature version
VersionRange range = VersionRange.ANY_VERSION;
String vr = clause.getAttribute(BLACKLIST_RANGE);
@@ -141,7 +223,6 @@ public class Blacklist {
range = new VersionRange(vr, true);
}
if (range.contains(VersionTable.getVersion(version))) {
- String type = clause.getAttribute(BLACKLIST_TYPE);
if (type == null || TYPE_FEATURE.equals(type)) {
return true;
}
@@ -151,11 +232,7 @@ public class Blacklist {
return false;
}
- public boolean isBundleBlacklisted(String uri) {
- return isBlacklisted(uri, TYPE_BUNDLE);
- }
-
- public boolean isBlacklisted(String uri, String btype) {
+ public boolean isRepositoryBlacklisted(String uri) {
for (Clause clause : clauses) {
String url = clause.getName();
if (clause.getAttribute(BLACKLIST_URL) != null) {
@@ -163,11 +240,34 @@ public class Blacklist {
}
if (uri.equals(url)) {
String type = clause.getAttribute(BLACKLIST_TYPE);
- if (type == null || btype.equals(type)) {
+ if (type == null || TYPE_REPOSITORY.equals(type)) {
return true;
}
}
}
return false;
}
+
+ /**
+ * Merge clauses from another {@link Blacklist} into this object
+ * @param others
+ */
+ public void merge(Blacklist others) {
+ Clause[] ours = this.clauses;
+ if (ours == null) {
+ this.clauses = Arrays.copyOf(others.clauses, others.clauses.length);
+ } else if (others != null && others.clauses.length > 0) {
+ this.clauses = new Clause[ours.length + others.clauses.length];
+ System.arraycopy(ours, 0, this.clauses, 0, ours.length);
+ System.arraycopy(others.clauses, ours.length, this.clauses, 0, others.clauses.length);
+ }
+ if (others != null) {
+ this.bundleBlacklist.putAll(others.bundleBlacklist);
+ }
+ }
+
+ public Clause[] getClauses() {
+ return clauses;
+ }
+
}
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 48eed92..61244b6 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
@@ -160,17 +160,34 @@ public class Deployer {
}
}
+ /**
+ * <p>Representation of the state of system from the point of view of <em>bundles</em> and <em>features</em></p>
+ */
public static class DeploymentState {
+ /** Current {@link State} of system */
public State state;
+ /** A {@link Bundle} providing {@link FeaturesService} */
public Bundle serviceBundle;
+ /** {@link org.osgi.framework.startlevel.FrameworkStartLevel#getInitialBundleStartLevel()} */
public int initialBundleStartLevel;
+ /** {@link org.osgi.framework.startlevel.FrameworkStartLevel#getStartLevel()} */
public int currentStartLevel;
+ /** bundle-id -> bundle for all currently installed bundles */
public Map<Long, Bundle> bundles;
+ /** feature-name/feature-id -> feature for all available features (not only installed) */
public Map<String, Feature> features;
+ /** region-name -> ids for bundles installed in region */
public Map<String, Set<Long>> bundlesPerRegion;
+ /** region-name -> connected, filtered, region-name -> filter-namespace -> filters */
public Map<String, Map<String, Map<String, Set<String>>>> filtersPerRegion;
}
+ /**
+ * <p>A request to change current {@link State state} of system</p>
+ * <p>{@link #requirements} specify target set of system requirements. If new features are installed,
+ * requirements should include currently installed features and new ones. If features are being uninstalled,
+ * requirements should include currently installed features minus the ones that are removed.</p>
+ */
public static class DeploymentRequest {
public Set<String> overrides;
public String featureResolutionRange;
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesProcessor.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesProcessor.java
new file mode 100644
index 0000000..28e805d
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesProcessor.java
@@ -0,0 +1,44 @@
+/*
+ * 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.net.URI;
+
+import org.apache.karaf.features.Repository;
+import org.apache.karaf.features.internal.model.Features;
+
+/**
+ * Service that can process (enhance, modify, trim, ...) a set of features read from {@link Repository}.
+ */
+public interface FeaturesProcessor {
+
+ /**
+ * Checks whether given repository URI is <em>blacklisted</em>
+ * @param uri
+ * @return
+ */
+ boolean isRepositoryBlacklisted(URI uri);
+
+ /**
+ * Processes original {@link Features JAXB model of features}
+ * @param features
+ */
+ void process(Features features);
+
+}
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesProcessorImpl.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesProcessorImpl.java
new file mode 100644
index 0000000..03dadea
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesProcessorImpl.java
@@ -0,0 +1,197 @@
+/*
+ * 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.FileNotFoundException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URL;
+import java.util.List;
+import java.util.Set;
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Unmarshaller;
+
+import org.apache.karaf.features.BundleInfo;
+import org.apache.karaf.features.internal.model.Bundle;
+import org.apache.karaf.features.internal.model.Conditional;
+import org.apache.karaf.features.internal.model.Feature;
+import org.apache.karaf.features.internal.model.Features;
+import org.apache.karaf.features.internal.model.processing.BundleReplacements;
+import org.apache.karaf.features.internal.model.processing.FeatureReplacements;
+import org.apache.karaf.features.internal.model.processing.FeaturesProcessing;
+import org.apache.karaf.features.internal.model.processing.ObjectFactory;
+import org.apache.karaf.features.internal.model.processing.OverrideBundleDependency;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * <p>Configurable {@link FeaturesProcessor}, controlled by several files from <code>etc/</code> directory:<ul>
+ * <li><code>etc/overrides.properties</code>: may alter bundle versions in features</li>
+ * <li><code>etc/blacklisted.properties</code>: may filter out some features/bundles</li>
+ * <li><code>etc/org.apache.karaf.features.xml</code> (<strong>new!</strong>): incorporates two above files
+ * and may define additional processing (changing G/A/V, adding bundles to features, changing <code>dependency</code>
+ * attributes, ...)</li>
+ * </ul></p>
+ */
+public class FeaturesProcessorImpl implements FeaturesProcessor {
+
+ public static Logger LOG = LoggerFactory.getLogger(FeaturesProcessorImpl.class);
+ private static final JAXBContext FEATURES_PROCESSING_CONTEXT;
+
+ private FeaturesProcessing processing;
+
+ static {
+ try {
+ FEATURES_PROCESSING_CONTEXT = JAXBContext.newInstance(ObjectFactory.class);
+ } catch (JAXBException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * <p>Creates instance of features processor using {@link FeaturesServiceConfig configuration object} where
+ * three files may be specified: overrides.properties, blacklisted.properties and org.apache.karaf.features.xml.</p>
+ * @param configuration
+ */
+ public FeaturesProcessorImpl(FeaturesServiceConfig configuration) {
+ // org.apache.karaf.features.xml - highest priority
+ String featureModificationsURI = configuration.featureModifications;
+ // blacklisted.properties - if available, adds to main configuration of feature processing
+ String blacklistedURI = configuration.blacklisted;
+ // overrides.properties - if available, adds to main configuration of feature processing
+ String overridesURI = configuration.overrides;
+
+ // these two are not changed - they still may be used, but if etc/org.apache.karaf.features.xml is available
+ // both of the below are merged into single processing configuration
+ Blacklist blacklist = new Blacklist(blacklistedURI);
+ Set<String> overrides = Overrides.loadOverrides(overridesURI);
+
+ if (featureModificationsURI != null) {
+ try {
+ try (InputStream stream = new URL(featureModificationsURI).openStream()) {
+ Unmarshaller unmarshaller = FEATURES_PROCESSING_CONTEXT.createUnmarshaller();
+ processing = (FeaturesProcessing) unmarshaller.unmarshal(stream);
+ }
+ } catch (FileNotFoundException e) {
+ LOG.warn("Can't find feature processing file (" + featureModificationsURI + ")");
+ } catch (Exception e) {
+ LOG.warn("Can't initialize feature processor: " + e.getMessage());
+ }
+ }
+
+ if (processing == null) {
+ processing = new FeaturesProcessing();
+ }
+ if (processing.getBundleReplacements() == null) {
+ processing.setBundleReplacements(new BundleReplacements());
+ }
+ if (processing.getFeatureReplacements() == null) {
+ processing.setFeatureReplacements(new FeatureReplacements());
+ }
+ if (processing.getOverrideBundleDependency() == null) {
+ processing.setOverrideBundleDependency(new OverrideBundleDependency());
+ }
+ processing.postUnmarshall(blacklist, overrides);
+ }
+
+ public FeaturesProcessing getInstructions() {
+ return processing;
+ }
+
+ @Override
+ public void process(Features features) {
+ // blacklisting features
+ for (Feature feature : features.getFeature()) {
+ feature.setBlacklisted(isFeatureBlacklisted(feature));
+ // blacklisting bundles
+ processBundles(feature.getBundle());
+ for (Conditional c : feature.getConditional()) {
+ processBundles(c.getBundle());
+ }
+ }
+
+ // TODO: changing "dependency" flag of features
+ // TODO: changing "dependency" flag of bundles
+ // TODO: overriding features
+ }
+
+ private void processBundles(List<Bundle> bundles) {
+ for (Bundle bundle : bundles) {
+ boolean bundleBlacklisted = isBundleBlacklisted(bundle.getLocation());
+ if (bundleBlacklisted) {
+ // blacklisting has higher priority
+ bundle.setBlacklisted(true);
+ } else {
+ // if not blacklisted, it may be overriden
+ staticOverrideBundle(bundle);
+ }
+ }
+ }
+
+ /**
+ * Processes {@link Bundle bundle definition} and (according to override instructions) maybe sets different target
+ * location and {@link BundleInfo#isOverriden()} flag
+ * @param bundle
+ */
+ private void staticOverrideBundle(Bundle bundle) {
+ for (BundleReplacements.OverrideBundle override : this.getInstructions().getBundleReplacements().getOverrideBundles()) {
+ String originalLocation = bundle.getLocation();
+ if (override.getOriginalUriPattern().matches(originalLocation)) {
+ LOG.debug("Overriding bundle location \"" + originalLocation + "\" with \"" + override.getReplacement() + "\"");
+ bundle.setOriginalLocation(originalLocation);
+ bundle.setOverriden(true);
+ bundle.setLocation(override.getReplacement());
+ // last rule wins - no break!!!
+ //break;
+ }
+ }
+
+ }
+
+ @Override
+ public boolean isRepositoryBlacklisted(URI uri) {
+ for (LocationPattern lp : processing.getBlacklistedRepositoryLocationPatterns()) {
+ if (lp.matches(uri.toString())) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Matching name and version of given feature, checks whether this feature is blacklisted
+ * @param feature
+ * @return
+ */
+ private boolean isFeatureBlacklisted(Feature feature) {
+ return getInstructions().getBlacklist().isFeatureBlacklisted(feature.getName(), feature.getVersion());
+ }
+
+ /**
+ * Matching location of the bundle, checks whether this bundle is blacklisted
+ * @param location
+ * @return
+ */
+ private boolean isBundleBlacklisted(String location) {
+ return getInstructions().getBlacklist().isBundleBlacklisted(location);
+ }
+
+}
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceConfig.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceConfig.java
index f6fe033..1f1fdfd 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceConfig.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceConfig.java
@@ -20,8 +20,6 @@ import org.apache.karaf.features.FeaturesService;
public class FeaturesServiceConfig {
- public final String overrides;
-
/**
* Range to use when a version is specified on a feature dependency.
* The default is {@link org.apache.karaf.features.FeaturesService#DEFAULT_FEATURE_RESOLUTION_RANGE}
@@ -54,14 +52,20 @@ public class FeaturesServiceConfig {
* Service requirements enforcement
*/
public final String serviceRequirements;
-
+
public final String blacklisted;
+ public final String featureModifications;
+ public final String overrides;
public FeaturesServiceConfig() {
- this(null, FeaturesService.DEFAULT_FEATURE_RESOLUTION_RANGE, FeaturesService.DEFAULT_BUNDLE_UPDATE_RANGE, null, 1, 0, 0, null, null);
+ this(null, null, null);
+ }
+
+ public FeaturesServiceConfig(String overrides, String blacklisted, String featureModifications) {
+ this(overrides, FeaturesService.DEFAULT_FEATURE_RESOLUTION_RANGE, FeaturesService.DEFAULT_BUNDLE_UPDATE_RANGE, null, 1, 0, 0, blacklisted, featureModifications, null);
}
- public FeaturesServiceConfig(String overrides, String featureResolutionRange, String bundleUpdateRange, String updateSnapshots, int downloadThreads, long scheduleDelay, int scheduleMaxRun, String blacklisted, String serviceRequirements) {
+ public FeaturesServiceConfig(String overrides, String featureResolutionRange, String bundleUpdateRange, String updateSnapshots, int downloadThreads, long scheduleDelay, int scheduleMaxRun, String blacklisted, String featureModifications, String serviceRequirements) {
this.overrides = overrides;
this.featureResolutionRange = featureResolutionRange;
this.bundleUpdateRange = bundleUpdateRange;
@@ -70,6 +74,7 @@ public class FeaturesServiceConfig {
this.scheduleDelay = scheduleDelay;
this.scheduleMaxRun = scheduleMaxRun;
this.blacklisted = blacklisted;
+ this.featureModifications = featureModifications;
this.serviceRequirements = serviceRequirements;
}
}
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 1478385..5a6d788 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
@@ -146,8 +146,7 @@ public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCall
this.resolver = resolver;
this.installSupport = installSupport;
this.globalRepository = globalRepository;
- Blacklist blacklist = new Blacklist(cfg.blacklisted);
- this.repositories = new RepositoryCache(blacklist);
+ this.repositories = new RepositoryCacheImpl(new FeaturesProcessorImpl(cfg));
this.cfg = cfg;
this.executor = Executors.newSingleThreadExecutor(ThreadUtils.namedThreadFactory("features"));
loadState();
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/LocationPattern.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/LocationPattern.java
new file mode 100644
index 0000000..7e55b9b
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/LocationPattern.java
@@ -0,0 +1,198 @@
+/*
+ * 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.net.MalformedURLException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.felix.utils.version.VersionCleaner;
+import org.apache.felix.utils.version.VersionRange;
+import org.apache.karaf.util.maven.Parser;
+import org.osgi.framework.Version;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * <p>Helper class to compare Maven URIs that may use globs and version ranges.</p>
+ * <p>Each Maven URI may contain these components: groupId, artifactId, optional version, optional type and optional
+ * classifier. Concrete URIs do not use globs and use precise versions (we not consider <code>LATEST</code>
+ * and <code>RELEASE</code> here).</p>
+ * <p>When comparing two Maven URIs, we split them to components and may use RegExps and
+ * {@link org.apache.felix.utils.version.VersionRange}s</p>
+ * <p>When pattern URI doesn't use <code>mvn:</code> scheme, plain {@link String#equals(Object)} is used or
+ * {@link Matcher#matches()} when pattern uses <code>*</code> glob.</p>
+ */
+public class LocationPattern {
+
+ public static Logger LOG = LoggerFactory.getLogger(LocationPattern.class);
+
+ private String originalUri;
+ private Pattern originalPattern;
+ private String groupId;
+ private Pattern groupIdPattern;
+ private String artifactId;
+ private Pattern artifactIdPattern;
+ private String versionString;
+ private Version version;
+ private VersionRange versionRange;
+ private String type;
+ private Pattern typePattern;
+ private String classifier;
+ private Pattern classifierPattern;
+
+ public LocationPattern(String uri) throws MalformedURLException {
+ if (uri == null) {
+ throw new IllegalArgumentException("URI to match should not be null");
+ }
+ originalUri = uri;
+ if (!originalUri.startsWith("mvn:")) {
+ originalPattern = toRegExp(originalUri);
+ } else {
+ uri = uri.substring(4);
+ Parser parser = new Parser(uri);
+ if (Parser.VERSION_LATEST.equals(parser.getVersion())) {
+ parser.setVersion(null);
+ }
+ groupId = parser.getGroup();
+ if (groupId.contains("*")) {
+ groupIdPattern = toRegExp(groupId);
+ }
+ artifactId = parser.getArtifact();
+ if (artifactId.contains("*")) {
+ artifactIdPattern = toRegExp(artifactId);
+ }
+ versionString = parser.getVersion();
+ if (versionString != null && versionString.length() >= 1) {
+ try {
+ char first = versionString.charAt(0);
+ if (first == '[' || first == '(') {
+ // range
+ versionRange = new VersionRange(versionString, true, false);
+ } else {
+ version = new Version(VersionCleaner.clean(versionString));
+ }
+ } catch (IllegalArgumentException e) {
+ MalformedURLException mue = new MalformedURLException("Can't parse version \"" + versionString + "\" as OSGi version object.");
+ mue.initCause(e);
+ throw mue;
+ }
+ }
+ type = parser.getType();
+ if (type != null && type.contains("*")) {
+ typePattern = toRegExp(type);
+ }
+ classifier = parser.getClassifier();
+ if (classifier != null && classifier.contains("*")) {
+ classifierPattern = toRegExp(classifier);
+ }
+ }
+ }
+
+ /**
+ * Converts a String with one special character (<code>*</code>) into working {@link Pattern}
+ * @param value
+ * @return
+ */
+ private Pattern toRegExp(String value) {
+ // TODO: escape all RegExp special chars that are valid path characters, only convert '*' into '.*'
+ return Pattern.compile(value
+ .replaceAll("\\.", "\\\\\\.")
+ .replaceAll("\\$", "\\\\\\$")
+ .replaceAll("\\^", "\\\\\\^")
+ .replaceAll("\\*", ".*")
+ );
+ }
+
+ /**
+ * Returns <code>true</code> if this location pattern matches other pattern.
+ * @param otherUri
+ * @return
+ */
+ public boolean matches(String otherUri) {
+ if (otherUri == null) {
+ return false;
+ }
+ if (originalPattern != null) {
+ // this pattern is not mvn:
+ return originalPattern.matcher(otherUri).matches();
+ }
+ if (!otherUri.startsWith("mvn:")) {
+ // other pattern is not mvn:
+ return originalUri.equals(otherUri);
+ }
+
+ LocationPattern other;
+ try {
+ other = new LocationPattern(otherUri);
+ } catch (MalformedURLException e) {
+ LOG.debug("Can't parse \"" + otherUri + "\" as Maven URI. Ignoring.");
+ return false;
+ }
+ if (other.versionRange != null) {
+ LOG.warn("Matched URI can't use version ranges: " + otherUri);
+ return false;
+ }
+
+ boolean match;
+
+ if (groupIdPattern == null) {
+ match = groupId.equals(other.groupId);
+ } else {
+ match = groupIdPattern.matcher(other.groupId).matches();
+ }
+ if (!match) {
+ return false;
+ }
+ if (artifactIdPattern == null) {
+ match = artifactId.equals(other.artifactId);
+ } else {
+ match = artifactIdPattern.matcher(other.artifactId).matches();
+ }
+ if (!match) {
+ return false;
+ }
+ if (versionRange != null && other.version != null) {
+ match = versionRange.contains(other.version);
+ } else {
+ match = version == null || version.equals(other.version);
+ }
+ if (!match) {
+ return false;
+ }
+ if (typePattern != null) {
+ match = typePattern.matcher(other.type == null ? "jar" : other.type).matches();
+ } else {
+ match = versionString == null || type.equals(other.type);
+ }
+ if (!match) {
+ return false;
+ }
+ if (classifierPattern != null) {
+ match = classifierPattern.matcher(other.classifier == null ? "" : other.classifier).matches();
+ } else if (classifier != null) {
+ match = classifier.equals(other.classifier);
+ } else {
+ match = other.classifierPattern == null;
+ }
+
+ return match;
+ }
+
+}
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/Overrides.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/Overrides.java
index 5e48bdf..7d6f3e2 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/service/Overrides.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/Overrides.java
@@ -24,6 +24,7 @@ import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
+import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
@@ -43,7 +44,7 @@ import static org.apache.karaf.features.internal.resolver.ResolverUtil.getVersio
*/
public final class Overrides {
- protected static final String OVERRIDE_RANGE = "range";
+ public static final String OVERRIDE_RANGE = "range";
private static final Logger LOGGER = LoggerFactory.getLogger(Overrides.class);
@@ -110,7 +111,7 @@ public final class Overrides {
}
public static Set<String> loadOverrides(String overridesUrl) {
- Set<String> overrides = new HashSet<>();
+ Set<String> overrides = new LinkedHashSet<>();
try {
if (overridesUrl != null) {
try (
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/RepositoryCache.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/RepositoryCache.java
index 801ffeb..4f684a5 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/service/RepositoryCache.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/RepositoryCache.java
@@ -1,103 +1,90 @@
/*
- * 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
+ * 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
+ * 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.
+ * 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.net.URI;
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Deque;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
import java.util.Set;
import org.apache.karaf.features.Repository;
-public class RepositoryCache {
-
- private final Map<String, Repository> repositoryCache = new HashMap<>();
- private final Blacklist blacklist;
-
- public RepositoryCache(Blacklist blacklist) {
- this.blacklist = blacklist;
- }
+/**
+ * <p>An interface for accessing repository/features information. Simple implementations
+ * may just map feature XMLs directly to JAXB model
+ * (see: {@link org.apache.karaf.features.internal.model.Features}).</p>
+ *
+ * <p>In more complex cases, additional processing (blacklisting, overrides, patching)
+ * may be performed.</p>
+ */
+public interface RepositoryCache {
- public Repository create(URI uri, boolean validate) throws Exception {
- return new RepositoryImpl(uri, blacklist, validate);
- }
+ /**
+ * Creates {@link Repository} without adding it to cache
+ * @param uri an URI (e.g., <code>mvn:groupId/artifactId/version/xml/features</code> of repository
+ * @param validate whether to perform XML Schema validation of loaded features XML
+ * @return a {@link Repository} that may be inspected or added to cache
+ */
+ Repository create(URI uri, boolean validate);
- public void addRepository(Repository repository) throws Exception {
- String repoUriSt = repository.getURI().toString();
- repositoryCache.put(repoUriSt, repository);
- }
+ /**
+ * Adds existing {@link Repository} to be tracked/managed by this cache and later be available e.g., via
+ * {@link #getRepository(String)}
+ * @param repository existing repository to add to cache
+ */
+ void addRepository(Repository repository);
- public void removeRepository(URI repositoryUri) throws Exception {
- List<String> toRemove = new ArrayList<>();
- toRemove.add(repositoryUri.toString());
- while (!toRemove.isEmpty()) {
- Repository rep = repositoryCache.remove(toRemove.remove(0));
- if (rep != null) {
- for (URI u : rep.getRepositories()) {
- toRemove.add(u.toString());
- }
- }
- }
- }
+ /**
+ * Removes existing {@link Repository} by its {@link URI}
+ * @param repositoryUri {@link URI} of the {@link Repository} to remove
+ */
+ void removeRepository(URI repositoryUri);
- public Repository[] listRepositories() {
- return repositoryCache.values().toArray(new Repository[repositoryCache.size()]);
- }
+ /**
+ * Gets {@link Repository} by its {@link URI}
+ * @param uri {@link URI} of the repository
+ * @return {@link Repository} as it's stored inside the cache
+ */
+ Repository getRepository(String uri);
- public Repository[] listMatchingRepositories(Set<String> uris) throws Exception {
- return repositoryCache.values().stream()
- .filter(r -> uris.contains(r.getURI().toString()))
- .toArray(Repository[]::new);
- }
+ /**
+ * Gets {@link Repository} by its name
+ * @param name Name of the repository
+ * @return {@link Repository} as it's stored inside the cache
+ */
+ Repository getRepositoryByName(String name);
- public Repository getRepositoryByName(String name) throws Exception {
- for (Repository repo : this.repositoryCache.values()) {
- if (name.equals(repo.getName())) {
- return repo;
- }
- }
- return null;
- }
+ /**
+ * Returns an array of all cached {@link Repository repositories}
+ * @return list of all {@link Repository repositories}
+ */
+ Repository[] listRepositories();
- public Repository getRepository(String uri) {
- return repositoryCache.get(uri);
- }
+ /**
+ * Returns an array of cached {@link Repository repositories} for a set of {@link URI repository URIs}
+ * @return list of matched {@link Repository repositories}
+ */
+ Repository[] listMatchingRepositories(Set<String> uris);
/**
- * Returns a set containing the given repository and all its dependencies recursively
+ * Returns a set of {@link Repository repositories} including passed repository and all referenced repositories.
+ * @param repo A {@link Repository}, that possibly references other feature repositories.
+ * @return A closure of {@link Repository repositories}
*/
- public Set<Repository> getRepositoryClosure(Repository repo) throws Exception {
- Set<Repository> closure = new HashSet<>();
- Deque<Repository> remaining = new ArrayDeque<>(Collections.singleton(repo));
- while (!remaining.isEmpty()) {
- Repository rep = remaining.removeFirst();
- if (closure.add(rep)) {
- for (URI uri : rep.getRepositories()) {
- remaining.add(getRepository(uri.toString()));
- }
- }
- }
- return closure;
- }
+ Set<Repository> getRepositoryClosure(Repository repo);
}
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/RepositoryCache.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/RepositoryCacheImpl.java
similarity index 74%
copy from features/core/src/main/java/org/apache/karaf/features/internal/service/RepositoryCache.java
copy to features/core/src/main/java/org/apache/karaf/features/internal/service/RepositoryCacheImpl.java
index 801ffeb..d33b39d 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/service/RepositoryCache.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/RepositoryCacheImpl.java
@@ -29,25 +29,37 @@ import java.util.Set;
import org.apache.karaf.features.Repository;
-public class RepositoryCache {
+/**
+ * Implementation of {@link RepositoryCache} that makes use of {@link FeaturesProcessor} to alter feature
+ * definitions after reading them from XML file.
+ */
+public class RepositoryCacheImpl implements RepositoryCache {
private final Map<String, Repository> repositoryCache = new HashMap<>();
- private final Blacklist blacklist;
-
- public RepositoryCache(Blacklist blacklist) {
- this.blacklist = blacklist;
+ private final FeaturesProcessor featuresProcessor;
+
+ public RepositoryCacheImpl(FeaturesProcessor featuresProcessor) {
+ this.featuresProcessor = featuresProcessor;
}
- public Repository create(URI uri, boolean validate) throws Exception {
- return new RepositoryImpl(uri, blacklist, validate);
+ @Override
+ public Repository create(URI uri, boolean validate) {
+ RepositoryImpl repository = new RepositoryImpl(uri, validate);
+ if (featuresProcessor != null) {
+ repository.setBlacklisted(featuresProcessor.isRepositoryBlacklisted(uri));
+ repository.processFeatures(featuresProcessor);
+ }
+ return repository;
}
- public void addRepository(Repository repository) throws Exception {
+ @Override
+ public void addRepository(Repository repository) {
String repoUriSt = repository.getURI().toString();
repositoryCache.put(repoUriSt, repository);
}
- public void removeRepository(URI repositoryUri) throws Exception {
+ @Override
+ public void removeRepository(URI repositoryUri) {
List<String> toRemove = new ArrayList<>();
toRemove.add(repositoryUri.toString());
while (!toRemove.isEmpty()) {
@@ -60,17 +72,20 @@ public class RepositoryCache {
}
}
+ @Override
public Repository[] listRepositories() {
return repositoryCache.values().toArray(new Repository[repositoryCache.size()]);
}
- public Repository[] listMatchingRepositories(Set<String> uris) throws Exception {
+ @Override
+ public Repository[] listMatchingRepositories(Set<String> uris) {
return repositoryCache.values().stream()
.filter(r -> uris.contains(r.getURI().toString()))
.toArray(Repository[]::new);
}
- public Repository getRepositoryByName(String name) throws Exception {
+ @Override
+ public Repository getRepositoryByName(String name) {
for (Repository repo : this.repositoryCache.values()) {
if (name.equals(repo.getName())) {
return repo;
@@ -79,6 +94,7 @@ public class RepositoryCache {
return null;
}
+ @Override
public Repository getRepository(String uri) {
return repositoryCache.get(uri);
}
@@ -86,7 +102,8 @@ public class RepositoryCache {
/**
* Returns a set containing the given repository and all its dependencies recursively
*/
- public Set<Repository> getRepositoryClosure(Repository repo) throws Exception {
+ @Override
+ public Set<Repository> getRepositoryClosure(Repository repo) {
Set<Repository> closure = new HashSet<>();
Deque<Repository> remaining = new ArrayDeque<>(Collections.singleton(repo));
while (!remaining.isEmpty()) {
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/RepositoryImpl.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/RepositoryImpl.java
index 0e9d703..31aadaf 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/service/RepositoryImpl.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/RepositoryImpl.java
@@ -33,28 +33,34 @@ import org.apache.karaf.features.internal.model.JaxbUtil;
*/
public class RepositoryImpl implements Repository {
+ /** {@link URI original URI} of the resource where feature declarations were loaded from */
private final URI uri;
- private final Blacklist blacklist;
+
+ /** Transformed {@link Features model} of the repository */
private Features features;
-
+
+ private boolean blacklisted;
+
public RepositoryImpl(URI uri) {
- this(uri, null, false);
+ this(uri, false);
}
- public RepositoryImpl(URI uri, Blacklist blacklist, boolean validate) {
+ public RepositoryImpl(URI uri, boolean validate) {
this.uri = uri;
- this.blacklist = blacklist;
load(validate);
}
+ @Override
public URI getURI() {
return uri;
}
+ @Override
public String getName() {
return features.getName();
}
+ @Override
public URI[] getRepositories() {
return features.getRepository().stream()
.map(String::trim)
@@ -62,6 +68,7 @@ public class RepositoryImpl implements Repository {
.toArray(URI[]::new);
}
+ @Override
public URI[] getResourceRepositories() {
return features.getResourceRepository().stream()
.map(String::trim)
@@ -69,27 +76,39 @@ public class RepositoryImpl implements Repository {
.toArray(URI[]::new);
}
+ @Override
public Feature[] getFeatures() {
return features.getFeature()
.toArray(new Feature[features.getFeature().size()]);
}
+ @Override
+ public boolean isBlacklisted() {
+ return blacklisted;
+ }
+
+ public void setBlacklisted(boolean blacklisted) {
+ this.blacklisted = blacklisted;
+ }
private void load(boolean validate) {
if (features == null) {
- try (
- InputStream inputStream = new InterruptibleInputStream(uri.toURL().openStream())
- ) {
+ try (InputStream inputStream = new InterruptibleInputStream(uri.toURL().openStream())) {
features = JaxbUtil.unmarshal(uri.toASCIIString(), inputStream, validate);
- if (blacklist != null) {
- blacklist.blacklist(features);
- }
} catch (Exception e) {
throw new RuntimeException(e.getMessage() + " : " + uri, e);
}
}
}
+ /**
+ * An extension point to alter {@link Features JAXB model of features}
+ * @param processor
+ */
+ public void processFeatures(FeaturesProcessor processor) {
+ processor.process(features);
+ }
+
static class InterruptibleInputStream extends FilterInputStream {
InterruptibleInputStream(InputStream in) {
super(in);
@@ -121,5 +140,5 @@ public class RepositoryImpl implements Repository {
public String toString() {
return getURI().toString();
}
-}
+}
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 f8f477c..b89947b 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
@@ -24,19 +24,35 @@ import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.karaf.features.internal.util.MapUtils;
+/**
+ * <p>Representation of the state of system from the point of view of <em>requirements</em>.
+ * It's a collection of:<ul>
+ * <li>used repositories</li>
+ * <li>region -> requirements</li>
+ * <li>region -> installed features</li>
+ * <li>region -> installed features -> state of feature installation</li>
+ * <li>region -> bundle ids</li>
+ * <li>bundle id -> checksum</li>
+ * </ul></p>
+ * <p>State is replaced (swapped) after uninstalling/updating/installing all the bundles as requested, but
+ * before resolving/refreshing them.</p>
+ */
public class State {
public final AtomicBoolean bootDone = new AtomicBoolean();
public final Set<String> repositories = new TreeSet<>();
- // Map from region name to Set of feature requirements (name/version range)
+ /** Map from region name to Set of feature requirements (name/version range) */
public final Map<String, Set<String>> requirements = new HashMap<>();
- // Map from region name to Set of feature id (name/version)
+ /** Map from region name to Set of feature id (name/version) */
public final Map<String, Set<String>> installedFeatures = new HashMap<>();
- // State of features by region and feature id (name/version)
+ /** State of features by region and feature id (name/version) */
public final Map<String, Map<String, String>> stateFeatures = new HashMap<>();
+
+ /** Map from region name to Set of installed bundle ids */
public final Map<String, Set<Long>> managedBundles = new HashMap<>();
+ /** Map from bundle id to bundle's java.util.zip.CRC32 */
public final Map<Long, Long> bundleChecksums = new HashMap<>();
public State copy() {
diff --git a/features/core/src/main/resources/org/apache/karaf/features/karaf-features-processing-1.0.0.xsd b/features/core/src/main/resources/org/apache/karaf/features/karaf-features-processing-1.0.0.xsd
new file mode 100644
index 0000000..d0e5d48
--- /dev/null
+++ b/features/core/src/main/resources/org/apache/karaf/features/karaf-features-processing-1.0.0.xsd
@@ -0,0 +1,236 @@
+<?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.
+-->
+<xs:schema elementFormDefault="qualified"
+ targetNamespace="http://karaf.apache.org/xmlns/features-processing/v1.0.0"
+ xmlns:tns="http://karaf.apache.org/xmlns/features-processing/v1.0.0"
+ xmlns:features="http://karaf.apache.org/xmlns/features/v1.5.0"
+ xmlns:xs="http://www.w3.org/2001/XMLSchema">
+
+ <xs:import namespace="http://karaf.apache.org/xmlns/features/v1.5.0" />
+
+ <xs:element name="featuresProcessing" type="tns:featuresProcessing" />
+
+ <xs:complexType name="featuresProcessing">
+ <xs:annotation>
+ <xs:documentation><![CDATA[Configuration of FeaturesProcessor that may modify feature definitions
+after reading them from features XML file and before using them by FeaturesService.
+]]></xs:documentation>
+ </xs:annotation>
+ <xs:choice minOccurs="0" maxOccurs="unbounded">
+ <xs:element name="blacklistedRepositories" type="tns:blacklistedRepositories" />
+ <xs:element name="blacklistedFeatures" type="tns:blacklistedFeatures" />
+ <xs:element name="blacklistedBundles" type="tns:blacklistedBundles" />
+ <xs:element name="overrideBundleDependency" type="tns:overrideBundleDependency" />
+ <xs:element name="bundleReplacements" type="tns:bundleReplacements" />
+ <xs:element name="featureReplacements" type="tns:featureReplacements" />
+ </xs:choice>
+ </xs:complexType>
+
+ <xs:complexType name="blacklistedRepositories">
+ <xs:annotation>
+ <xs:documentation><![CDATA[A list of feature repository URIs (e.g., mvn:group/artifact/version/xml/features)
+that should be blacklisted - they can't be added to FeaturesService and can't be searched for features to install.
+
+Repository URI should use 'mvn:' scheme, may use '*' glob (not RegExp) for all components except versions and
+version ranges as maven versions, e.g., "mvn:group/artifact/[3,5)/xml/*features*". At least groupId and artifactId
+should be specified. In most abstract case, "mvn:*/*" describes all maven URIs.
+]]></xs:documentation>
+ </xs:annotation>
+ <xs:sequence>
+ <xs:element name="repository" type="xs:anyURI" minOccurs="0" maxOccurs="unbounded" />
+ </xs:sequence>
+ </xs:complexType>
+
+ <xs:complexType name="blacklistedFeatures">
+ <xs:annotation>
+ <xs:documentation><![CDATA[A list of feature names (and related OSGi clause attributes) that should be
+blacklisted.
+
+Attempt to install such feature will be prohibited, but such feature is still available in `feature:list`
+output and marked as blacklisted. When custom Karaf distribution is assembled, blacklisted features' bundles are not
+taken into account (are not declared in "etc/startup.properties" and "etc/org.apache.karaf.features.cfg" and are not
+copied to "system/" directory).
+
+Feature names may use '*' glob (not RegExp) in names, "range" attribute is used in "etc/blacklisted.properties" file
+and may be used to specify version range, e.g., "*jclouds*;range=[1,3)". When using XML to configure blacklisted
+features, "range" manifest header attribute should be specified in "version" XML attribute.
+]]></xs:documentation>
+ </xs:annotation>
+ <xs:sequence>
+ <xs:element name="feature" type="tns:blacklistedFeature" minOccurs="0" maxOccurs="unbounded" />
+ </xs:sequence>
+ </xs:complexType>
+
+ <xs:complexType name="blacklistedFeature">
+ <xs:annotation>
+ <xs:documentation><![CDATA[Blacklisted feature name may use '*' character as glob. "version" attribute
+MAY specify a version (or range) of features to blacklist, e.g.,:
+ * version="[1,2)" - feature with versions 1, 1.1, 1.4.3, 1.9.99, ... will be blacklisted
+ * version="[2,*)" - features with all versions above 2.0.0 will be blacklisted
+ * version="3.0" - feature with version 3.0 only will be blacklisted
+]]></xs:documentation>
+ </xs:annotation>
+ <xs:simpleContent>
+ <xs:extension base="xs:string">
+ <xs:attribute name="version" type="xs:string" />
+ </xs:extension>
+ </xs:simpleContent>
+ </xs:complexType>
+
+ <xs:complexType name="blacklistedBundles">
+ <xs:annotation>
+ <xs:documentation><![CDATA[A list of bundle URIs that should be blacklisted.
+When feature is installed, all blacklisted bundles are skipped.
+
+When custom Karaf distribution is assembled, blacklisted bundles are not taken into account (are not declared in
+"etc/startup.properties" and are not copied to "system/" directory).
+
+Bundle URIs may use '*' globs (not RegExp), e.g., "*jclouds*;url=mvn:...". Both header-like format may be used
+(as in etc/blacklisted.properties, triggered, when ';' is used) or plain URI format for bundle locations.
+
+Bundle URIs should use 'mvn:' scheme, may use '*' glob (not RegExp) for all components except versions and
+version ranges may be specified as maven versions, e.g., "mvn:group/artifact/[3,5)/classifier". At least
+groupId and artifactId should be specified. In most abstract case, "mvn:*/*" describes all maven URIs.
+
+This element may be used instead of "etc/blacklisted.properties" file for bundle blacklisting.
+]]></xs:documentation>
+ </xs:annotation>
+ <xs:sequence>
+ <xs:element name="bundle" type="xs:anyURI" minOccurs="0" maxOccurs="unbounded" />
+ </xs:sequence>
+ </xs:complexType>
+
+ <xs:complexType name="overrideBundleDependency">
+ <xs:annotation>
+ <xs:documentation><![CDATA[FeaturesService configuration uses "dependency" flags for bundles and features
+declared in features XML file. This flag instructs the service to consider such "dependency" item only if current
+state of system doesn't provide required capabilities. When such flag is chosen wisely it greatly improves consistency
+of installed features and bundles. However many external features XML files (which are out of control, or simply are no
+longer maintained) do not use this flag. With "overrideBundleDependency" element it's possible to externally
+override this flag for any repository, feature or particular bundles.
+]]></xs:documentation>
+ </xs:annotation>
+ <xs:sequence>
+ <xs:element name="repository" type="tns:overrideDependency" minOccurs="0" maxOccurs="unbounded" />
+ <xs:element name="feature" type="tns:overrideFeatureDependency" minOccurs="0" maxOccurs="unbounded" />
+ <xs:element name="bundle" type="tns:overrideDependency" minOccurs="0" maxOccurs="unbounded" />
+ </xs:sequence>
+ </xs:complexType>
+
+ <xs:complexType name="overrideDependency">
+ <xs:annotation>
+ <xs:documentation><![CDATA[An URI of depedency (bundle or repository) should use 'mvn:' scheme.
+Maven schemes allow parsing version/version ranges.
+]]></xs:documentation>
+ </xs:annotation>
+ <xs:attribute name="uri" type="xs:anyURI" />
+ <xs:attribute name="dependency" type="xs:boolean" default="false" />
+ </xs:complexType>
+
+ <xs:complexType name="overrideFeatureDependency">
+ <xs:attribute name="name" type="xs:string" />
+ <xs:attribute name="version" type="xs:string" />
+ <xs:attribute name="dependency" type="xs:boolean" default="false" />
+ </xs:complexType>
+
+ <xs:complexType name="bundleReplacements">
+ <xs:annotation>
+ <xs:documentation><![CDATA[When feature is loaded from features XML file, "etc/overrides.properties" may
+be used to change the version of one/more of the bundles from given feature. Override is used only when symbolic
+names match. With bundleReplacements element, its possible to do the same and more. It's possible to replace a bundle
+with totally different bundle (in terms of OSGi's Symbolic-Name and Maven groupId/artifactId/version). That's useful
+especially with JavaEE API bundles, where single API may be provided by different bundles (ServiceMix, Geronimo,
+JBoss, javax.*, ...).
+
+Bundle replacement doesn't use globs - just version ranges to match replacement candidates
+]]></xs:documentation>
+ </xs:annotation>
+ <xs:sequence>
+ <xs:element name="bundle" type="tns:overrideBundle" minOccurs="0" maxOccurs="unbounded" />
+ </xs:sequence>
+ </xs:complexType>
+
+ <xs:complexType name="overrideBundle">
+ <xs:annotation>
+ <xs:documentation><![CDATA[For each bundle of any feature we can point bundle's URI to different location.
+
+URIs should use 'mvn:' scheme, to allow version/version ranges parsing. Replacement URI should use concrete version
+- no range. Original URI may use version ranges to indicate which version is eligible for replacement. E.g,:
+ * mvn:groupId/artifactId/[1,2) - bundle location will be overriden for versions 1, 1.1, 1.4.3, 1.9.99, ...
+ * mvn:groupId/artifactId/[2,*) - bundle location will be overriden for version 2.0, 3.1, 4.2, ...
+ * mvn:groupId/artifactId/3 - bundle location will be overriden for version 3 (3.0, 3.0.0) only (i.e., [3.0.0,3.0.0]
+URIs can't use globs for Maven components (groupId, artifactId, ...).
+
+mode="osgi" is used for etc/overrides.properties compatibility - symbolic name should be equal (requires
+resource's/bundle's manifest parsing) and target/replacement bundle's version shold be lower to perform replacement.
+When reading etc/overrides.properties, groupId and artifactId will match anyway, but if configured in XML, they do
+not have to match.
+
+with mode="maven", only location matching is done and target version is not compared - this is useful
+to replace bundle using different groupId/artifactId. The most common case is e.g.,
+"org.eclipse.jetty.orbit/javax.servlet/[3,4)" -> "org.apache.geronimo.specs/geronimo-servlet_3.0_spec/1.0" replacement.
+This is new mode (comparing to etc/overrides.properties).
+]]></xs:documentation>
+ </xs:annotation>
+ <xs:attribute name="originalUri" type="xs:anyURI" />
+ <xs:attribute name="replacement" type="xs:anyURI" />
+ <xs:attribute name="mode" type="tns:bundleOverrideMode" default="osgi" />
+ </xs:complexType>
+
+ <xs:simpleType name="bundleOverrideMode">
+ <xs:restriction base="xs:string">
+ <xs:enumeration value="osgi" />
+ <xs:enumeration value="maven" />
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:complexType name="featureReplacements">
+ <xs:annotation>
+ <xs:documentation><![CDATA[This element may be used to completely "rewrite" any feature (by name and
+version). Depending on mode of modification (replace, merge) it's possible to replace any feature definition, or just
+add/remove some items (usually bundles) to/from original feature.
+]]></xs:documentation>
+ </xs:annotation>
+ <xs:sequence>
+ <xs:element name="replacement" type="tns:overrideFeature" minOccurs="0" maxOccurs="unbounded" />
+ </xs:sequence>
+ </xs:complexType>
+
+ <xs:complexType name="overrideFeature">
+ <xs:annotation>
+ <xs:documentation><![CDATA[Any feature may be "overriden" simply by including its changed definition
+according to http://karaf.apache.org/xmlns/features/v1.5.0 XML schema.
+]]></xs:documentation>
+ </xs:annotation>
+ <xs:sequence>
+ <xs:element name="feature" type="features:feature" />
+ </xs:sequence>
+ <xs:attribute name="mode" type="tns:featureOverrideMode" default="replace" />
+ </xs:complexType>
+
+ <xs:simpleType name="featureOverrideMode">
+ <xs:restriction base="xs:string">
+ <xs:enumeration value="replace" />
+ <xs:enumeration value="merge" />
+ <xs:enumeration value="remove" />
+ </xs:restriction>
+ </xs:simpleType>
+
+</xs:schema>
diff --git a/features/core/src/test/java/org/apache/karaf/features/internal/service/BlacklistTest.java b/features/core/src/test/java/org/apache/karaf/features/internal/service/BlacklistTest.java
index 8e835ea..6c5d7e2 100644
--- a/features/core/src/test/java/org/apache/karaf/features/internal/service/BlacklistTest.java
+++ b/features/core/src/test/java/org/apache/karaf/features/internal/service/BlacklistTest.java
@@ -16,63 +16,76 @@
*/
package org.apache.karaf.features.internal.service;
-import static org.junit.Assert.assertTrue;
-
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
-import java.util.Collections;
+import java.util.HashSet;
import java.util.stream.Stream;
import org.apache.karaf.features.BundleInfo;
import org.apache.karaf.features.Feature;
+import org.apache.karaf.features.FeaturesService;
import org.junit.Test;
+import static org.junit.Assert.assertTrue;
+
public class BlacklistTest {
@Test
- public void testBlacklistFeatureWithRange() {
+ public void testBlacklistFeatureWithRange() throws IOException {
Stream<Feature> features = blacklistWith("spring;range=\"[2,3)\"");
- assertTrue(features.noneMatch(f -> f.getId().equals("spring/2.5.6.SEC02")));
+ assertTrue(features.noneMatch(f -> f.getId().equals("spring/2.5.6.SEC02") && !f.isBlacklisted()));
}
@Test
- public void testBlacklistFeatureWithVersion() {
+ public void testBlacklistFeatureWithVersion() throws IOException {
Stream<Feature> features = blacklistWith("spring;range=2.5.6.SEC02");
- assertTrue(features.noneMatch(f -> f.getId().equals("spring/2.5.6.SEC02")));
+ assertTrue(features.noneMatch(f -> f.getId().equals("spring/2.5.6.SEC02") && !f.isBlacklisted()));
}
@Test
- public void testBlacklistFeatureWithoutVersion() {
+ public void testBlacklistFeatureWithoutVersion() throws IOException {
Stream<Feature> features = blacklistWith("spring");
- assertTrue(features.noneMatch(f -> f.getId().startsWith("spring/")));
+ assertTrue(features.noneMatch(f -> f.getId().startsWith("spring/") && !f.isBlacklisted()));
}
@Test
- public void testBlacklistBundle() {
+ public void testBlacklistBundle() throws IOException {
String blacklisted = "mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.jasypt/1.7_1";
Stream<Feature> features = blacklistWith(blacklisted);
Stream<BundleInfo> bundles = features.flatMap(f -> f.getBundles().stream());
- assertTrue(bundles.noneMatch(b -> b.getLocation().equals(blacklisted)));
+ assertTrue(bundles.noneMatch(b -> b.getLocation().equals(blacklisted) && !b.isBlacklisted()));
}
@Test
public void testBlacklistLoad() throws URISyntaxException {
Blacklist blacklist = new Blacklist(getClass().getResource("blacklist.txt").toExternalForm());
- RepositoryImpl repo = new RepositoryImpl(getClass().getResource("f02.xml").toURI(), blacklist, true);
- Stream<Feature> features = Arrays.asList(repo.getFeatures()).stream();
- assertTrue(features.noneMatch(f -> f.getId().equals("spring/2.5.6.SEC02")));
+ RepositoryImpl repo = new RepositoryImpl(getClass().getResource("f02.xml").toURI(), true);
+ Stream<Feature> features = Arrays.stream(repo.getFeatures());
+ FeaturesProcessorImpl processor = new FeaturesProcessorImpl(new FeaturesServiceConfig());
+ processor.getInstructions().postUnmarshall(blacklist, new HashSet<>());
+ repo.processFeatures(processor);
+ assertTrue(features.noneMatch(f -> f.getId().equals("spring/2.5.6.SEC02") && !f.isBlacklisted()));
}
- private Stream<org.apache.karaf.features.Feature> blacklistWith(String blacklistClause) {
+ private Stream<org.apache.karaf.features.Feature> blacklistWith(String blacklistClause) throws IOException {
URI uri;
try {
uri = getClass().getResource("f02.xml").toURI();
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
- Blacklist blacklist = new Blacklist(Collections.singletonList(blacklistClause));
- RepositoryImpl features = new RepositoryImpl(uri, blacklist, true);
- return Arrays.asList(features.getFeatures()).stream();
+ File blacklistedProperties = File.createTempFile("blacklisted-", ".properties", new File("target"));
+ try (FileOutputStream fos = new FileOutputStream(blacklistedProperties)) {
+ fos.write(blacklistClause.getBytes("UTF-8"));
+ }
+ RepositoryImpl features = new RepositoryImpl(uri, true);
+ FeaturesServiceConfig config = new FeaturesServiceConfig(null, FeaturesService.DEFAULT_FEATURE_RESOLUTION_RANGE, FeaturesService.DEFAULT_BUNDLE_UPDATE_RANGE, null, 1, 0, 0, blacklistedProperties.toURI().toString(), null, null);
+ features.processFeatures(new FeaturesProcessorImpl(config));
+ return Arrays.stream(features.getFeatures());
}
+
}
diff --git a/features/core/src/test/java/org/apache/karaf/features/internal/service/FeaturesProcessorTest.java b/features/core/src/test/java/org/apache/karaf/features/internal/service/FeaturesProcessorTest.java
new file mode 100644
index 0000000..ce96722
--- /dev/null
+++ b/features/core/src/test/java/org/apache/karaf/features/internal/service/FeaturesProcessorTest.java
@@ -0,0 +1,200 @@
+/*
+ * 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.net.URI;
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.Marshaller;
+
+import org.apache.felix.utils.manifest.Clause;
+import org.apache.felix.utils.version.VersionRange;
+import org.apache.karaf.features.Feature;
+import org.apache.karaf.features.internal.model.processing.BundleReplacements;
+import org.apache.karaf.features.internal.model.processing.FeaturesProcessing;
+import org.apache.karaf.features.internal.model.processing.ObjectFactory;
+import org.apache.karaf.util.maven.Parser;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+public class FeaturesProcessorTest {
+
+ public static Logger LOG = LoggerFactory.getLogger(FeaturesProcessorTest.class);
+
+ @Test
+ public void jaxbModelForProcessor() throws Exception {
+ JAXBContext jaxb = JAXBContext.newInstance(ObjectFactory.class);
+ FeaturesProcessing fp = (FeaturesProcessing) jaxb.createUnmarshaller().unmarshal(getClass().getResourceAsStream("/org/apache/karaf/features/internal/service/org.apache.karaf.features.xml"));
+ assertThat(fp.getFeatureReplacements().getReplacements().get(0).getFeature().getName(), equalTo("pax-jsf-resources-support"));
+
+ Marshaller marshaller = jaxb.createMarshaller();
+ marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
+ marshaller.marshal(fp, System.out);
+ }
+
+ @Test
+ public void versionRanges() {
+ LOG.info(new VersionRange("1", false).toString());
+ LOG.info(new VersionRange("[2,3)", true).toString());
+ }
+
+ @Test
+ public void mavenURIs() throws Exception {
+ Parser p = new Parser("group/artifact/[1,2)/xml/features*");
+ assertThat(p.getVersion(), equalTo("[1,2)"));
+ assertThat(p.getClassifier(), equalTo("features*"));
+
+ p = new Parser("org.springframework*/*cloud*/*");
+ assertThat(p.getVersion(), equalTo("*"));
+ assertThat(p.getArtifact(), equalTo("*cloud*"));
+ assertThat(p.getGroup(), equalTo("org.springframework*"));
+ assertThat(p.getType(), equalTo("jar"));
+ assertThat(p.getClassifier(), nullValue());
+
+ p = new Parser("org.ops4j/org.ops4j*/*//uber");
+ assertThat(p.getVersion(), equalTo("*"));
+ assertThat(p.getArtifact(), equalTo("org.ops4j*"));
+ assertThat(p.getGroup(), equalTo("org.ops4j"));
+ assertThat(p.getType(), equalTo("jar"));
+ assertThat(p.getClassifier(), equalTo("uber"));
+ }
+
+ @Test
+ public void readingLegacyOverrides() {
+ FeaturesProcessorImpl processor = new FeaturesProcessorImpl(new FeaturesServiceConfig(
+ "file:src/test/resources/org/apache/karaf/features/internal/service/overrides2.properties",
+ null, null));
+
+ FeaturesProcessing instructions = processor.getInstructions();
+ BundleReplacements bundleReplacements = instructions.getBundleReplacements();
+ assertThat(bundleReplacements.getOverrideBundles().size(), equalTo(5));
+ BundleReplacements.OverrideBundle o1 = bundleReplacements.getOverrideBundles().get(0);
+ assertThat(o1.getOriginalUri(), equalTo("mvn:org.apache.karaf.admin/org.apache.karaf.admin.command/[2.3.0,2.3.0.61033X)"));
+ assertThat(o1.getReplacement(), equalTo("mvn:org.apache.karaf.admin/org.apache.karaf.admin.command/2.3.0.61033X"));
+ BundleReplacements.OverrideBundle o2 = bundleReplacements.getOverrideBundles().get(1);
+ assertThat(o2.getOriginalUri(), equalTo("mvn:org.apache.karaf.admin/org.apache.karaf.admin.core/[2.2.0,2.4.0)"));
+ assertThat(o2.getReplacement(), equalTo("mvn:org.apache.karaf.admin/org.apache.karaf.admin.core/2.3.0.61033X"));
+ BundleReplacements.OverrideBundle o3 = bundleReplacements.getOverrideBundles().get(2);
+ assertThat(o3.getOriginalUri(), equalTo("mvn:org.apache.karaf.admin/org.apache.karaf.admin.resources/[2.3.0,2.3.14)"));
+ assertThat(o3.getReplacement(), equalTo("mvn:org.apache.karaf.admin/org.apache.karaf.admin.resources/2.3.14"));
+ BundleReplacements.OverrideBundle o4 = bundleReplacements.getOverrideBundles().get(3);
+ assertThat(o4.getOriginalUri(), equalTo("mvn:org.apache.karaf.admin/org.apache.karaf.admin.kernel/[2.0.0,2.0.0]"));
+ assertThat(o4.getReplacement(), equalTo("mvn:org.apache.karaf.admin/org.apache.karaf.admin.kernel/2.3.14"));
+ BundleReplacements.OverrideBundle o5 = bundleReplacements.getOverrideBundles().get(4);
+ assertThat(o5.getOriginalUri(), equalTo("mvn:org.apache.karaf.admin/org.apache.karaf.admin.infinity/[1.0.0,*)"));
+ assertThat(o5.getReplacement(), equalTo("mvn:org.apache.karaf.admin/org.apache.karaf.admin.infinity/2.3.14"));
+ }
+
+ @Test
+ public void readingLegacyBlacklist() {
+ FeaturesProcessorImpl processor = new FeaturesProcessorImpl(new FeaturesServiceConfig(
+ null,
+ "file:src/test/resources/org/apache/karaf/features/internal/service/blacklisted2.properties",
+ null));
+
+ FeaturesProcessing instructions = processor.getInstructions();
+ Blacklist blacklist = instructions.getBlacklist();
+ Clause[] clauses = blacklist.getClauses();
+ assertThat(clauses.length, equalTo(4));
+ assertTrue(blacklist.isFeatureBlacklisted("spring", "2.5.6.SEC02"));
+ assertFalse(blacklist.isFeatureBlacklisted("spring", "2.5.7.SEC02"));
+ assertTrue(blacklist.isFeatureBlacklisted("jclouds", "42"));
+
+ assertTrue(blacklist.isBundleBlacklisted("mvn:org.spring/spring-infinity/42"));
+ assertFalse(blacklist.isBundleBlacklisted("mvn:org.spring/spring-infinity/41"));
+ assertTrue(blacklist.isBundleBlacklisted("mvn:org.spring/spring-eternity/42"));
+ assertTrue(blacklist.isBundleBlacklisted("mvn:jclouds/jclouds/1"));
+ }
+
+ @Test
+ public void blacklistingRepositories() {
+ FeaturesProcessorImpl processor = new FeaturesProcessorImpl(new FeaturesServiceConfig(
+ null, null,
+ "file:src/test/resources/org/apache/karaf/features/internal/service/fpi01.xml"));
+ URI uri = URI.create("file:src/test/resources/org/apache/karaf/features/internal/service/fp01.xml");
+ RepositoryImpl repo = (RepositoryImpl) new RepositoryCacheImpl(processor).create(uri, true);
+ assertThat(repo.getRepositories().length, equalTo(3));
+ assertFalse(repo.isBlacklisted());
+ assertFalse(processor.isRepositoryBlacklisted(repo.getRepositories()[0]));
+ assertTrue(processor.isRepositoryBlacklisted(repo.getRepositories()[1]));
+ assertFalse(processor.isRepositoryBlacklisted(repo.getRepositories()[2]));
+ }
+
+ @Test
+ public void blacklistingFeatures() {
+ FeaturesProcessorImpl processor = new FeaturesProcessorImpl(new FeaturesServiceConfig(
+ null, null,
+ "file:src/test/resources/org/apache/karaf/features/internal/service/fpi01.xml"));
+ URI uri = URI.create("file:src/test/resources/org/apache/karaf/features/internal/service/fp02.xml");
+ RepositoryImpl repo = (RepositoryImpl) new RepositoryCacheImpl(processor).create(uri, true);
+
+ Feature[] features = repo.getFeatures();
+ assertTrue(features[0].isBlacklisted());
+ assertFalse(features[1].isBlacklisted());
+ assertTrue(features[2].isBlacklisted());
+ assertTrue(features[3].isBlacklisted());
+ assertFalse(features[4].isBlacklisted());
+ }
+
+ @Test
+ public void blacklistingBundles() {
+ FeaturesProcessorImpl processor = new FeaturesProcessorImpl(new FeaturesServiceConfig(
+ null, null,
+ "file:src/test/resources/org/apache/karaf/features/internal/service/fpi01.xml"));
+ URI uri = URI.create("file:src/test/resources/org/apache/karaf/features/internal/service/fp03.xml");
+ RepositoryImpl repo = (RepositoryImpl) new RepositoryCacheImpl(processor).create(uri, true);
+
+ Feature f1 = repo.getFeatures()[0];
+ assertFalse(f1.getBundles().get(0).isBlacklisted());
+ assertFalse(f1.getBundles().get(1).isBlacklisted());
+ assertTrue(f1.getBundles().get(2).isBlacklisted());
+ assertTrue(f1.getBundles().get(3).isBlacklisted());
+ assertTrue(f1.getConditional().get(0).getBundles().get(0).isBlacklisted());
+ }
+
+ @Test
+ public void overridingBundles() {
+ FeaturesProcessorImpl processor = new FeaturesProcessorImpl(new FeaturesServiceConfig(
+ null, null,
+ "file:src/test/resources/org/apache/karaf/features/internal/service/fpi02.xml"));
+ URI uri = URI.create("file:src/test/resources/org/apache/karaf/features/internal/service/fp03.xml");
+ RepositoryImpl repo = (RepositoryImpl) new RepositoryCacheImpl(processor).create(uri, true);
+
+ Feature f1 = repo.getFeatures()[0];
+ assertFalse(f1.getBundles().get(0).isOverriden());
+ assertTrue(f1.getBundles().get(1).isOverriden());
+ assertThat(f1.getBundles().get(1).getLocation(), equalTo("mvn:commons-io/commons-io/1.3.5"));
+ assertThat(f1.getBundles().get(1).getOriginalLocation(), equalTo("mvn:commons-io/commons-io/1.3"));
+ assertTrue(f1.getBundles().get(2).isOverriden());
+ assertThat(f1.getBundles().get(2).getLocation(), equalTo("mvn:commons-codec/commons-codec/1.4.2"));
+ assertThat(f1.getBundles().get(2).getOriginalLocation(), equalTo("mvn:commons-codec/commons-codec/0.4"));
+ assertFalse(f1.getBundles().get(3).isOverriden());
+ assertTrue(f1.getConditional().get(0).getBundles().get(0).isOverriden());
+ assertThat(f1.getConditional().get(0).getBundles().get(0).getLocation(), equalTo("mvn:org.glassfish/something-strangest/4.3.1"));
+ assertThat(f1.getConditional().get(0).getBundles().get(0).getOriginalLocation(), equalTo("mvn:org.glassfish/something-strangest/4.3.0"));
+ assertFalse(f1.getConditional().get(0).getBundles().get(1).isOverriden());
+ }
+
+}
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 e21c854..feee89d 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
@@ -84,7 +84,7 @@ public class FeaturesValidationTest {
private Repository unmarshalAndValidate(String path) throws Exception {
URI uri = getClass().getResource(path).toURI();
- return new RepositoryImpl(uri, null, true);
+ return new RepositoryImpl(uri, true);
}
}
diff --git a/features/core/src/test/java/org/apache/karaf/features/internal/service/LocationPatternTest.java b/features/core/src/test/java/org/apache/karaf/features/internal/service/LocationPatternTest.java
new file mode 100644
index 0000000..c0a7b31
--- /dev/null
+++ b/features/core/src/test/java/org/apache/karaf/features/internal/service/LocationPatternTest.java
@@ -0,0 +1,162 @@
+/*
+ * 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.net.MalformedURLException;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class LocationPatternTest {
+
+ @Test
+ public void matchingNonMavenUris() throws MalformedURLException {
+ assertTrue(new LocationPattern("file:1").matches("file:1"));
+ assertFalse(new LocationPattern("file:1").matches("file:2"));
+ assertFalse(new LocationPattern("file:*").matches(null));
+ assertTrue(new LocationPattern("http://*").matches("http://a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q.txt"));
+ assertTrue(new LocationPattern("file:/tmp/x*.txt").matches("file:/tmp/x1.txt"));
+ assertTrue(new LocationPattern("file:/tmp/x$2*.txt").matches("file:/tmp/x$24.txt"));
+ assertTrue(new LocationPattern("file:/tmp/x^2*.txt").matches("file:/tmp/x^24.txt"));
+ }
+
+ @Test
+ public void correctMvnLocationPatterns() {
+ boolean exception = false;
+ for (String p : new String[] {
+ "mvn:groupId/artifactId",
+ "mvn:groupId/artifactId/1",
+ "mvn:groupId/artifactId/1/t",
+ "mvn:groupId/artifactId/1/t/c",
+ "mvn:groupId/*",
+ "mvn:*/*",
+ "mvn:*/*/[0,*)/*/*",
+ "mvn:g/a/[1,2)",
+ "mvn:g/a/[1,*)",
+ "mvn:groupId/artifactId/[1.0.0,1.0.0.0)",
+ "mvn:groupId/artifactId/[1.0,1.0.0.0)",
+ "mvn:groupId/artifactId/[1,1.0.0.0)",
+ }) {
+ try {
+ new LocationPattern(p);
+ } catch (MalformedURLException ignored) {
+ exception |= true;
+ }
+ }
+ assertFalse("We should not fail for correct mvn: URIs", exception);
+ }
+
+ @Test
+ public void incorrectMvnLocationPatterns() {
+ boolean exception = true;
+ for (String p : new String[] {
+ "mvn:onlyGroupId",
+// "mvn:groupId/artifactId/wrongVersion",
+ "mvn:groupId/artifactId/[1.2,2",
+ "mvn:groupId/artifactId/[1.2,",
+ "mvn:groupId/artifactId/[1.2",
+ "mvn:groupId/artifactId/[",
+// "mvn:groupId/artifactId/*",
+ "mvn:groupId/artifactId/[wrongRange,wrongRange]",
+ "mvn:groupId/artifactId/(wrongRange,wrongRange]",
+ "mvn:groupId/artifactId/(wrongRange,3]",
+ "mvn:groupId/artifactId/[1,wrongRange)",
+ "mvn:groupId/artifactId/[1,1.2.3.4.5)",
+ "mvn:groupId/artifactId/[1,1.2.a)",
+ "mvn:groupId/artifactId/[1,1.a)",
+ "mvn:groupId/artifactId/[1,1)",
+ "mvn:groupId/artifactId/[1.0,1)",
+ "mvn:groupId/artifactId/[1.0.0,1)",
+ "mvn:groupId/artifactId/[1.0.0.0,1)",
+ "mvn:groupId/artifactId/[1.0.0.0,1.0)",
+ "mvn:groupId/artifactId/[1.0.0.0,1.0.0)",
+ "mvn:groupId/artifactId/[1.0.0.0,1.0.0.0)"
+ }) {
+ try {
+ new LocationPattern(p);
+ exception &= false;
+ } catch (MalformedURLException ignored) {
+ }
+ }
+ assertTrue("We should fail for all broken mvn: URIs", exception);
+ }
+
+ @Test
+ public void matchingMavenUrisWithoutPatterns() throws MalformedURLException {
+ assertTrue(new LocationPattern("mvn:g/a/1/t/c").matches("mvn:g/a/1/t/c"));
+ assertTrue(new LocationPattern("mvn:g/a/1//c").matches("mvn:g/a/1/jar/c"));
+ assertTrue(new LocationPattern("mvn:g/a/1").matches("mvn:g/a/1"));
+ assertTrue(new LocationPattern("mvn:g/a").matches("mvn:g/a/1"));
+ assertTrue(new LocationPattern("mvn:g/a").matches("mvn:g/a/1/jar"));
+ assertTrue("Special case - when there's no version, we don't match to \"jar\" type, but to all types",
+ new LocationPattern("mvn:g/a").matches("mvn:g/a/1/j"));
+ assertTrue(new LocationPattern("mvn:g/a").matches("mvn:g/a/1/t/c"));
+ assertTrue(new LocationPattern("mvn:g/a/1").matches("mvn:g/a/1/jar"));
+ assertFalse(new LocationPattern("mvn:g/a/1").matches("mvn:g/a/1/war"));
+ assertTrue(new LocationPattern("mvn:g/a/1").matches("mvn:g/a/1/jar/c"));
+ assertTrue(new LocationPattern("mvn:g/a/1").matches("mvn:g/a/1//c"));
+ }
+
+ @Test
+ public void matchingMavenUrisWithVersionRangesInPattern() throws MalformedURLException {
+ assertTrue(new LocationPattern("mvn:g/a/[1,1]").matches("mvn:g/a"));
+ assertTrue(new LocationPattern("mvn:g/a/[1,1]").matches("mvn:g/a/1"));
+ assertFalse(new LocationPattern("mvn:g/a/[1,1.1)").matches("mvn:g/a/1.1"));
+ assertTrue(new LocationPattern("mvn:g/a/[1,2)").matches("mvn:g/a/1"));
+ assertTrue(new LocationPattern("mvn:g/a/[1,2)").matches("mvn:g/a/1.1"));
+ assertTrue(new LocationPattern("mvn:g/a/[1,2)").matches("mvn:g/a/1.9"));
+ assertTrue(new LocationPattern("mvn:g/a/[1,2)").matches("mvn:g/a/1.9.9.BUILD-SNAPSHOT"));
+ assertFalse(new LocationPattern("mvn:g/a/[1,2)").matches("mvn:g/a/2.0"));
+ assertTrue(new LocationPattern("mvn:g/a/[1,*)").matches("mvn:g/a/2.0"));
+ assertTrue(new LocationPattern("mvn:g/a/[1,*)").matches("mvn:g/a/42.0"));
+ assertTrue(new LocationPattern("mvn:g/a/[1,*)").matches("mvn:g/a/9999.9999.9999.9999"));
+ }
+
+ @Test
+ public void matchingMavenUrisWithPatterns() throws MalformedURLException {
+ assertTrue(new LocationPattern("mvn:*/a/1").matches("mvn:g/a/1"));
+ assertTrue(new LocationPattern("mvn:g/*/1").matches("mvn:g/a/1"));
+ assertTrue(new LocationPattern("mvn:*/*/1").matches("mvn:g/a/1"));
+ assertFalse(new LocationPattern("mvn:*/*/[1,*)/jar").matches("mvn:g/a/1/war/c"));
+ assertFalse(new LocationPattern("mvn:*/*/[1,*)/jar").matches("mvn:g/a/1/war"));
+ assertTrue(new LocationPattern("mvn:*/*/[1,*)/jar").matches("mvn:g/a/1"));
+ assertTrue(new LocationPattern("mvn:*/*/[1,*)/jar").matches("mvn:g/a/1//c"));
+ assertFalse(new LocationPattern("mvn:*/*/[1,*)/jar/c*").matches("mvn:g/a/1//d"));
+ assertTrue(new LocationPattern("mvn:*/*/[1,*)/jar/d*").matches("mvn:g/a/1//d"));
+ assertTrue(new LocationPattern("mvn:*/*").matches("mvn:g/a/1//c"));
+ assertTrue(new LocationPattern("mvn:*/*").matches("mvn:g/a/1/t/c"));
+ assertTrue(new LocationPattern("mvn:*/*").matches("mvn:g/a/1/t"));
+ assertTrue(new LocationPattern("mvn:*/*").matches("mvn:g/a/1"));
+ assertTrue(new LocationPattern("mvn:*/*").matches("mvn:g/a"));
+ assertFalse(new LocationPattern("mvn:*/*/2/*/*").matches("mvn:g/a/1"));
+ assertTrue(new LocationPattern("mvn:*/*/[1,2)/*/*").matches("mvn:g/a/1"));
+ assertTrue(new LocationPattern("mvn:*/*/1/*/*").matches("mvn:g/a/1"));
+ assertTrue(new LocationPattern("mvn:*/*/1/*/*").matches("mvn:g/a/1//c"));
+ assertTrue(new LocationPattern("mvn:*/*/1/*/*").matches("mvn:g/a/1/jar/c"));
+ assertTrue(new LocationPattern("mvn:*/*/1/*/*").matches("mvn:g/a/1/t/c"));
+ }
+
+ @Test
+ public void matchingMavenUrisWithVersionRangesInUri() throws MalformedURLException {
+ assertFalse(new LocationPattern("mvn:g/a/[1,1]").matches("mvn:g/a/[1,1]"));
+ }
+
+}
diff --git a/features/core/src/test/java/org/apache/karaf/features/internal/service/OverridesTest.java b/features/core/src/test/java/org/apache/karaf/features/internal/service/OverridesTest.java
index 033ac7c..d64685d 100644
--- a/features/core/src/test/java/org/apache/karaf/features/internal/service/OverridesTest.java
+++ b/features/core/src/test/java/org/apache/karaf/features/internal/service/OverridesTest.java
@@ -134,10 +134,10 @@ public class OverridesTest {
Clause karafAdminCommand = null;
Clause karafAdminCore = null;
for (Clause clause : Parser.parseClauses(overrides.toArray(new String[overrides.size()]))) {
- if (clause.getName().equals("mvn:org.apache.karaf.admin/org.apache.karaf.admin.command/2.3.0.redhat-61033X")) {
+ if (clause.getName().equals("mvn:org.apache.karaf.admin/org.apache.karaf.admin.command/2.3.0.61033X")) {
karafAdminCommand = clause;
}
- if (clause.getName().equals("mvn:org.apache.karaf.admin/org.apache.karaf.admin.core/2.3.0.redhat-61033X")) {
+ if (clause.getName().equals("mvn:org.apache.karaf.admin/org.apache.karaf.admin.core/2.3.0.61033X")) {
karafAdminCore = clause;
}
}
diff --git a/features/core/src/test/java/org/apache/karaf/features/internal/service/RepositoryCacheTest.java b/features/core/src/test/java/org/apache/karaf/features/internal/service/RepositoryCacheTest.java
new file mode 100644
index 0000000..e8e9fd0
--- /dev/null
+++ b/features/core/src/test/java/org/apache/karaf/features/internal/service/RepositoryCacheTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.net.URI;
+
+import org.apache.karaf.features.Repository;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import static org.junit.Assert.assertNotNull;
+
+public class RepositoryCacheTest {
+
+ private String pkgs;
+
+ @Before
+ public void init() {
+ String _pkgs = pkgs = System.getProperty("java.protocol.handler.pkgs");
+ if (_pkgs == null || "".equals(_pkgs.trim())) {
+ _pkgs = "";
+ } else {
+ _pkgs += "|";
+ }
+ _pkgs += this.getClass().getPackage().getName();
+ System.setProperty("java.protocol.handler.pkgs", _pkgs);
+ }
+
+ @After
+ public void cleanup() {
+ if (pkgs != null) {
+ System.setProperty("java.protocol.handler.pkgs", pkgs);
+ }
+ }
+
+ @Test
+ @Ignore("Ignoring to check if it's real problem")
+ public void refCountForIncludedRepositories() throws Exception {
+ RepositoryCacheImpl cache = new RepositoryCacheImpl(null);
+ Repository repo1 = cache.create(getClass().getResource("/org/apache/karaf/features/repo1.xml").toURI(), false);
+ Repository repo2 = cache.create(getClass().getResource("/org/apache/karaf/features/repo2.xml").toURI(), false);
+ cache.addRepository(repo1);
+ cache.addRepository(repo2);
+ cache.addRepository(new RepositoryImpl(URI.create("urn:r1"), false));
+ assertNotNull(cache.getRepository("urn:r1"));
+
+ cache.removeRepository(repo2.getURI());
+ assertNotNull("Repository referenced from two different repositories should not be cascade-removed",
+ cache.getRepository("urn:r1"));
+ }
+
+}
diff --git a/features/core/src/test/java/org/apache/karaf/features/internal/service/urn/Handler.java b/features/core/src/test/java/org/apache/karaf/features/internal/service/urn/Handler.java
new file mode 100644
index 0000000..7d366de
--- /dev/null
+++ b/features/core/src/test/java/org/apache/karaf/features/internal/service/urn/Handler.java
@@ -0,0 +1,35 @@
+/*
+ * 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.urn;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLStreamHandler;
+
+public class Handler extends URLStreamHandler {
+
+ @Override
+ protected URLConnection openConnection(URL u) throws IOException {
+ String name = new File(u.getPath()).getName();
+ return getClass().getResource("/org/apache/karaf/features/" + name + ".xml").openConnection();
+ }
+
+}
diff --git a/features/core/src/test/resources/log4j.properties b/features/core/src/test/resources/log4j.properties
new file mode 100644
index 0000000..696136e
--- /dev/null
+++ b/features/core/src/test/resources/log4j.properties
@@ -0,0 +1,35 @@
+## ---------------------------------------------------------------------------
+## 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.
+## ---------------------------------------------------------------------------
+
+#
+# The logging properties used during tests..
+#
+log4j.rootLogger=DEBUG, console, file
+log4j.logger.org.apache.karaf.features.internal.service=TRACE
+
+# Console will only display warnnings
+log4j.appender.console=org.apache.log4j.ConsoleAppender
+log4j.appender.console.layout=org.apache.log4j.PatternLayout
+log4j.appender.console.layout.ConversionPattern=%d{ISO8601} [%-5.5p] {%t} %c{1} (%F:%L) : %m%n
+#log4j.appender.console.threshold=WARN
+
+# File appender will contain all info messages
+log4j.appender.file=org.apache.log4j.FileAppender
+log4j.appender.file.layout=org.apache.log4j.PatternLayout
+log4j.appender.file.layout.ConversionPattern=%d | %-5p | %m | %c | %t%n
+log4j.appender.file.file=target/test.log
+log4j.appender.file.append=true
diff --git a/features/core/src/test/resources/org/apache/karaf/features/internal/service/overrides.properties b/features/core/src/test/resources/org/apache/karaf/features/internal/service/blacklisted2.properties
similarity index 80%
copy from features/core/src/test/resources/org/apache/karaf/features/internal/service/overrides.properties
copy to features/core/src/test/resources/org/apache/karaf/features/internal/service/blacklisted2.properties
index d34fa7e..e4247ac 100644
--- a/features/core/src/test/resources/org/apache/karaf/features/internal/service/overrides.properties
+++ b/features/core/src/test/resources/org/apache/karaf/features/internal/service/blacklisted2.properties
@@ -18,6 +18,9 @@
#
################################################################################
-# Sample etc/overrides.properties file for testing purposes
-mvn:org.apache.karaf.admin/org.apache.karaf.admin.command/2.3.0.redhat-61033X
-mvn:org.apache.karaf.admin/org.apache.karaf.admin.core/2.3.0.redhat-61033X;range=[2.3.0,2.5)
+spring;range=2.5.6.SEC02
+
+jclouds;url=mvn:jclouds/jclouds/1
+
+spring;type=bundle;url=mvn:org.spring/spring-infinity/42
+mvn:org.spring/spring-eternity/42
diff --git a/features/core/src/test/resources/org/apache/karaf/features/internal/service/fp01.xml b/features/core/src/test/resources/org/apache/karaf/features/internal/service/fp01.xml
new file mode 100644
index 0000000..99407ac
--- /dev/null
+++ b/features/core/src/test/resources/org/apache/karaf/features/internal/service/fp01.xml
@@ -0,0 +1,26 @@
+<?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="fp01" xmlns="http://karaf.apache.org/xmlns/features/v1.5.0">
+
+ <repository>mvn:org.hibernate/hibernate-validator-osgi-karaf-features/4.1/xml/features</repository>
+ <repository>mvn:org.hibernate/hibernate-validator-osgi-karaf-features/5.2/xml/features</repository>
+ <repository>mvn:org.hibernate/hibernate-validator-osgi-karaf-features/5.3/xml/features</repository>
+
+</features>
diff --git a/features/core/src/test/resources/org/apache/karaf/features/internal/service/fp02.xml b/features/core/src/test/resources/org/apache/karaf/features/internal/service/fp02.xml
new file mode 100644
index 0000000..94d01cf
--- /dev/null
+++ b/features/core/src/test/resources/org/apache/karaf/features/internal/service/fp02.xml
@@ -0,0 +1,28 @@
+<?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="fp02" xmlns="http://karaf.apache.org/xmlns/features/v1.5.0">
+
+ <feature name="f1" />
+ <feature name="f2a" version="42" />
+ <feature name="f3a" version="1.3" />
+ <feature name="f4" version="4" />
+ <feature name="f4" version="5" />
+
+</features>
diff --git a/features/core/src/test/resources/org/apache/karaf/features/internal/service/fp03.xml b/features/core/src/test/resources/org/apache/karaf/features/internal/service/fp03.xml
new file mode 100644
index 0000000..ad621e3
--- /dev/null
+++ b/features/core/src/test/resources/org/apache/karaf/features/internal/service/fp03.xml
@@ -0,0 +1,34 @@
+<?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="fp02" xmlns="http://karaf.apache.org/xmlns/features/v1.5.0">
+
+ <feature name="f5" version="4.2">
+ <bundle>mvn:commons-io/commons-io/1.2</bundle>
+ <bundle>mvn:commons-io/commons-io/1.3</bundle>
+ <bundle>mvn:commons-codec/commons-codec/0.4</bundle>
+ <bundle>mvn:commons-codec/commons-codec/1.2</bundle>
+ <conditional>
+ <condition>x=y</condition>
+ <bundle>mvn:org.glassfish/something-strangest/4.3.0</bundle>
+ <bundle>mvn:org.glassfish/something-strangest/4.3.2</bundle>
+ </conditional>
+ </feature>
+
+</features>
diff --git a/features/core/src/test/resources/org/apache/karaf/features/internal/service/fpi01.xml b/features/core/src/test/resources/org/apache/karaf/features/internal/service/fpi01.xml
new file mode 100644
index 0000000..6f299e0
--- /dev/null
+++ b/features/core/src/test/resources/org/apache/karaf/features/internal/service/fpi01.xml
@@ -0,0 +1,41 @@
+<?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.
+-->
+<featuresProcessing xmlns="http://karaf.apache.org/xmlns/features-processing/v1.0.0">
+
+ <blacklistedRepositories>
+ <repository>mvn:org.hibernate/hibernate-validator-osgi-karaf-features/[5,5.3)/xml/features</repository>
+ </blacklistedRepositories>
+
+ <blacklistedFeatures>
+ <feature>f1</feature>
+ <feature version="[1,2)">f*a</feature>
+ <feature version="[4,5)">f4</feature>
+ </blacklistedFeatures>
+
+ <blacklistedBundles>
+ <bundle>mvn:commons-io/commons-io/[0,1.1)</bundle>
+ <bundle>mvn:commons-codec/commons-codec/[0,1.5)</bundle>
+ <bundle>mvn:org.glassfish/*</bundle>
+ <bundle>mvn:org.apache.karaf/exception/[0,*)</bundle>
+ <!-- same as: -->
+ <!--<bundle>mvn:org.apache.karaf/exception</bundle>-->
+ </blacklistedBundles>
+
+</featuresProcessing>
diff --git a/features/core/src/test/resources/org/apache/karaf/features/internal/service/fpi02.xml b/features/core/src/test/resources/org/apache/karaf/features/internal/service/fpi02.xml
new file mode 100644
index 0000000..e67a0be
--- /dev/null
+++ b/features/core/src/test/resources/org/apache/karaf/features/internal/service/fpi02.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.
+-->
+<featuresProcessing xmlns="http://karaf.apache.org/xmlns/features-processing/v1.0.0">
+
+ <bundleReplacements>
+ <bundle replacement="mvn:commons-io/commons-io/1.3.5" />
+ <bundle originalUri="mvn:commons-codec/commons-codec/[0,1.2)"
+ replacement="mvn:commons-codec/commons-codec/1.4.2" mode="maven" />
+ <bundle replacement="mvn:org.glassfish/something-strangest/4.3.1" />
+ </bundleReplacements>
+
+</featuresProcessing>
diff --git a/features/core/src/test/resources/org/apache/karaf/features/internal/service/org.apache.karaf.features.xml b/features/core/src/test/resources/org/apache/karaf/features/internal/service/org.apache.karaf.features.xml
new file mode 100644
index 0000000..3691319
--- /dev/null
+++ b/features/core/src/test/resources/org/apache/karaf/features/internal/service/org.apache.karaf.features.xml
@@ -0,0 +1,98 @@
+<?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.
+-->
+<featuresProcessing xmlns="http://karaf.apache.org/xmlns/features-processing/v1.0.0"
+ xmlns:f="http://karaf.apache.org/xmlns/features/v1.5.0">
+
+ <!--
+ org.apache.karaf.features.internal.service.RepositoryCache will refuse to add/track a repository URI if it's blacklisted,
+ either if added explicitly or as referenced features repository URI
+ -->
+ <blacklistedRepositories>
+ <repository>mvn:org.hibernate/hibernate-validator-osgi-karaf-features/[5,*)/xml/features</repository>
+ <!-- ... -->
+ </blacklistedRepositories>
+
+ <!--
+ org.apache.karaf.features.internal.service.RepositoryCache will transform added/tracked repository by removing blacklisted features
+ -->
+ <blacklistedFeatures>
+ <feature>*jetty*</feature>
+ <feature version="[2,3)">*jclouds*</feature>
+ <!-- ... -->
+ </blacklistedFeatures>
+
+ <!--
+ org.apache.karaf.features.internal.service.RepositoryCache will transform added/tracked repository and remove all blacklisted
+ bundles from all the features in the repository
+ -->
+ <blacklistedBundles>
+ <bundle>mvn:commons-logging/*</bundle>
+ <!-- ... -->
+ </blacklistedBundles>
+
+ <!--
+ We can configure RepositoryCache to change 'dependency="false|true"' flag on given bundles, features,
+ repositories or globally
+ -->
+ <overrideBundleDependency>
+ <!-- Override "dependency" flag for all bundles of all features for given repository URI(s) -->
+ <repository uri="mvn:org.ops4j.pax.cdi/pax-cdi-features/*/xml/features" dependency="true" />
+ <repository uri="mvn:*/xml/features" dependency="true" />
+ <!-- Override "dependency" flag for all bundles of given feature(s) -->
+ <feature name="jclouds*" version="[1,3)" dependency="true" />
+ <!-- Override "dependency" flag for given bundle(s) -->
+ <bundle uri="mvn:javax.annotation/javax.annotation-api/*" dependency="true" />
+ </overrideBundleDependency>
+
+ <!--
+ Knowing there are multiple bundles containing the same classes (usually APIs), we can "translate"
+ bundle location to completely different bundles
+ -->
+ <bundleReplacements>
+ <bundle originalUri="mvn:commons-beanutils/commons-beanutils/[1.9,2)"
+ replacement="mvn:commons-beanutils/commons-beanutils/1.9.3" />
+ <!--
+ An example of direct etc/overrides.properties equivalent - originalUri will be derived from replacement
+ - candidate must have version lower than replacement
+ - candidate must be in eligible range for update: [3.2.0, 3.2.2)
+ -->
+ <bundle replacement="mvn:commons-collections/commons-collections/3.2.2" />
+ <bundle originalUri="mvn:org.eclipse.jetty.orbit/javax.servlet/[3,4)"
+ replacement="mvn:org.apache.geronimo.specs/geronimo-servlet_3.0_spec/1.0" mode="maven" />
+ <!-- ... -->
+ </bundleReplacements>
+
+ <!--
+ We can completely rewrite any feature deifnition, which may be useful for features beyond our control or
+ which are no longer maintained. This is expert setting and has to be configured with care.
+ We can add, remove and change all the items inside feature definition
+ -->
+ <featureReplacements>
+ <replacement mode="replace">
+ <feature name="pax-jsf-resources-support" description="Provide sharing of resources according to Servlet 3.0 for OSGi bundles and JSF" version="6.0.7">
+ <f:feature version="[6.0,6.1)">pax-jsf-support</f:feature>
+ <f:bundle dependency="true">mvn:org.ops4j.pax.web/pax-web-resources-extender/6.0.7</f:bundle>
+ <f:bundle>mvn:org.ops4j.pax.web/pax-web-resources-jsf/6.0.7</f:bundle>
+ </feature>
+ </replacement>
+ <!-- ... -->
+ </featureReplacements>
+
+</featuresProcessing>
diff --git a/features/core/src/test/resources/org/apache/karaf/features/internal/service/overrides.properties b/features/core/src/test/resources/org/apache/karaf/features/internal/service/overrides.properties
index d34fa7e..84f070e 100644
--- a/features/core/src/test/resources/org/apache/karaf/features/internal/service/overrides.properties
+++ b/features/core/src/test/resources/org/apache/karaf/features/internal/service/overrides.properties
@@ -19,5 +19,5 @@
################################################################################
# Sample etc/overrides.properties file for testing purposes
-mvn:org.apache.karaf.admin/org.apache.karaf.admin.command/2.3.0.redhat-61033X
-mvn:org.apache.karaf.admin/org.apache.karaf.admin.core/2.3.0.redhat-61033X;range=[2.3.0,2.5)
+mvn:org.apache.karaf.admin/org.apache.karaf.admin.command/2.3.0.61033X
+mvn:org.apache.karaf.admin/org.apache.karaf.admin.core/2.3.0.61033X;range=[2.3.0,2.5)
diff --git a/features/core/src/test/resources/org/apache/karaf/features/internal/service/overrides2.properties b/features/core/src/test/resources/org/apache/karaf/features/internal/service/overrides2.properties
new file mode 100644
index 0000000..d112d43
--- /dev/null
+++ b/features/core/src/test/resources/org/apache/karaf/features/internal/service/overrides2.properties
@@ -0,0 +1,37 @@
+
+################################################################################
+#
+# 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.
+#
+################################################################################
+
+# Each line is a location of a bundle that may override any feature bundle with matching symbolic name and versions
+# (according to implicit or explicit rules)
+
+# 2.3.0.61033X will be used instead of any org.apache.karaf.admin.command with version in range [2.3.0, 2.4.0) and
+# lower than 2.3.0.61033X, i.e., for version in range [2.3.0, 2.3.0.61033X) (conjunction).
+# version 2.2.x can be upgraded only up to and exluding 2.3.0, so it won't be overriden
+mvn:org.apache.karaf.admin/org.apache.karaf.admin.command/2.3.0.61033X
+# explicit range. can be used to declare "bigger" override, which is not normally allowed (e.g., 2.2.0 -> 2.3.0)
+# normally, we can upgrade to version different at micro (3rd) digit (e.g., 2.3.1 -> 2.3.4)
+mvn:org.apache.karaf.admin/org.apache.karaf.admin.core/2.3.0.61033X;range=[2.2.0,2.4)
+# invalid override - exact version should be specified
+mvn:org.apache.karaf.admin/org.apache.karaf.admin.invalid/[2.3,3)
+mvn:org.apache.karaf.admin/org.apache.karaf.admin.resources/2.3.14
+# range specified as single version is a short-hand of [2.0.0,2.0.0]
+mvn:org.apache.karaf.admin/org.apache.karaf.admin.kernel/2.3.14;range=2.0
+# range with '*' as open ceiling (it makes no sense to use "[v1,*]") is just wider range of bundle versions to consider
+mvn:org.apache.karaf.admin/org.apache.karaf.admin.infinity/2.3.14;range=[1.0,*)
diff --git a/features/core/src/test/resources/org/apache/karaf/features/r1.xml b/features/core/src/test/resources/org/apache/karaf/features/r1.xml
new file mode 100644
index 0000000..5c136b6
--- /dev/null
+++ b/features/core/src/test/resources/org/apache/karaf/features/r1.xml
@@ -0,0 +1,20 @@
+<?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="r1" xmlns="http://karaf.apache.org/xmlns/features/v1.3.0">
+ <feature name="f1" />
+</features>
diff --git a/profile/src/main/java/org/apache/karaf/profile/assembly/Builder.java b/profile/src/main/java/org/apache/karaf/profile/assembly/Builder.java
index e69656b..f14c6ae 100644
--- a/profile/src/main/java/org/apache/karaf/profile/assembly/Builder.java
+++ b/profile/src/main/java/org/apache/karaf/profile/assembly/Builder.java
@@ -1141,7 +1141,7 @@ public class Builder {
@Override
public void downloaded(final StreamProvider provider) throws Exception {
String url = provider.getUrl();
- if (repoBlacklist.isBlacklisted(url, TYPE_REPOSITORY)) {
+ if (repoBlacklist.isRepositoryBlacklisted(url)) {
LOGGER.info(" feature repository " + url + " is blacklisted");
return;
}
diff --git a/util/src/main/java/org/apache/karaf/util/maven/Parser.java b/util/src/main/java/org/apache/karaf/util/maven/Parser.java
index 96b9f88..b39d856 100644
--- a/util/src/main/java/org/apache/karaf/util/maven/Parser.java
+++ b/util/src/main/java/org/apache/karaf/util/maven/Parser.java
@@ -285,6 +285,27 @@ public class Parser
}
/**
+ * Prints parsed mvn: URI (after possible change of any component)
+ * @return
+ */
+ public String toMvnURI()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.append(m_group).append(ARTIFACT_SEPARATOR).append(m_artifact).append(ARTIFACT_SEPARATOR).append(m_version);
+ if (!TYPE_JAR.equals(m_type)) {
+ sb.append(ARTIFACT_SEPARATOR).append(m_type);
+ }
+ if (m_classifier != null && !"".equals(m_classifier)) {
+ if (TYPE_JAR.equals(m_type)) {
+ sb.append(ARTIFACT_SEPARATOR).append(m_type);
+ }
+ sb.append(ARTIFACT_SEPARATOR).append(m_classifier);
+ }
+
+ return sb.toString();
+ }
+
+ /**
* Return the group id of the artifact.
*
* @return group ID.
@@ -335,6 +356,51 @@ public class Parser
}
/**
+ * Changes parsed group - to allow printing mvn: URI with changed groupId
+ * @param m_group
+ */
+ public void setGroup(String m_group)
+ {
+ this.m_group = m_group;
+ }
+
+ /**
+ * Changes parsed artifact - to allow printing mvn: URI with changed artifactId
+ * @param m_artifact
+ */
+ public void setArtifact(String m_artifact)
+ {
+ this.m_artifact = m_artifact;
+ }
+
+ /**
+ * Changes parsed version - to allow printing mvn: URI with changed version
+ * @param m_version
+ */
+ public void setVersion(String m_version)
+ {
+ this.m_version = m_version;
+ }
+
+ /**
+ * Changes parsed type - to allow printing mvn: URI with changed type
+ * @param m_type
+ */
+ public void setType(String m_type)
+ {
+ this.m_type = m_type;
+ }
+
+ /**
+ * Changes parsed classifier - to allow printing mvn: URI with changed classifier
+ * @param m_classifier
+ */
+ public void setClassifier(String m_classifier)
+ {
+ this.m_classifier = m_classifier;
+ }
+
+ /**
* Return the complete path to artifact as stated by Maven 2 repository layout.
*
* @return artifact path.
diff --git a/util/src/test/java/org/apache/karaf/util/ParserTest.java b/util/src/test/java/org/apache/karaf/util/ParserTest.java
index d15487a..0f3c246 100644
--- a/util/src/test/java/org/apache/karaf/util/ParserTest.java
+++ b/util/src/test/java/org/apache/karaf/util/ParserTest.java
@@ -16,12 +16,17 @@
*/
package org.apache.karaf.util;
-import junit.framework.Assert;
+import java.net.MalformedURLException;
+import java.util.HashMap;
+import java.util.Map;
+
import org.apache.karaf.util.maven.Parser;
import org.junit.Test;
-import java.util.HashMap;
-import java.util.Map;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
public class ParserTest {
@@ -29,14 +34,30 @@ public class ParserTest {
private final static String PATH_WITHOUT_CLASSIFIER = "org/apache/karaf/test/1.0-SNAPSHOT/test-1.0-SNAPSHOT.xml";
@Test
- public void parserTest() throws Exception {
+ public void parserTest() {
Map parts = new HashMap();
String uri = Parser.pathToMaven(PATH_WITH_CLASSIFIER, parts);
- Assert.assertEquals("mvn:org.apache.karaf/test/1.0-SNAPSHOT/xml/feature", uri);
- Assert.assertEquals("feature", parts.get("classifier"));
+ assertEquals("mvn:org.apache.karaf/test/1.0-SNAPSHOT/xml/feature", uri);
+ assertEquals("feature", parts.get("classifier"));
uri = Parser.pathToMaven(PATH_WITHOUT_CLASSIFIER, parts);
- Assert.assertEquals("mvn:org.apache.karaf/test/1.0-SNAPSHOT/xml", uri);
- Assert.assertNull(parts.get("classifier"));
+ assertEquals("mvn:org.apache.karaf/test/1.0-SNAPSHOT/xml", uri);
+ assertNull(parts.get("classifier"));
+ }
+
+ @Test
+ public void unparserTest() throws MalformedURLException {
+ Parser p1 = new Parser("org.apache/karaf/1/xml/features");
+ assertThat(p1.toMvnURI(), equalTo("org.apache/karaf/1/xml/features"));
+ Parser p2 = new Parser("org.apache/karaf/1/xml");
+ assertThat(p2.toMvnURI(), equalTo("org.apache/karaf/1/xml"));
+ Parser p3 = new Parser("org.apache/karaf/1/jar/uber");
+ assertThat(p3.toMvnURI(), equalTo("org.apache/karaf/1/jar/uber"));
+ Parser p4 = new Parser("org.apache/karaf/1//uber");
+ assertThat(p4.toMvnURI(), equalTo("org.apache/karaf/1/jar/uber"));
+ Parser p5 = new Parser("org.apache/karaf/1/jar");
+ assertThat(p5.toMvnURI(), equalTo("org.apache/karaf/1"));
+ Parser p6 = new Parser("org.apache/karaf/1");
+ assertThat(p6.toMvnURI(), equalTo("org.apache/karaf/1"));
}
}
diff --git a/webconsole/features/src/main/java/org/apache/karaf/webconsole/features/ExtendedFeature.java b/webconsole/features/src/main/java/org/apache/karaf/webconsole/features/ExtendedFeature.java
index 61738f6..a601cf8 100644
--- a/webconsole/features/src/main/java/org/apache/karaf/webconsole/features/ExtendedFeature.java
+++ b/webconsole/features/src/main/java/org/apache/karaf/webconsole/features/ExtendedFeature.java
@@ -169,4 +169,10 @@ public class ExtendedFeature implements Feature {
public String getRepositoryUrl() {
return feature.getRepositoryUrl();
}
+
+ @Override
+ public boolean isBlacklisted() {
+ return feature.isBlacklisted();
+ }
+
}
--
To stop receiving notification emails like this one, please contact
"commits@karaf.apache.org" <co...@karaf.apache.org>.