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/11/15 15:29:25 UTC
[karaf] branch KARAF-5376-overrides_v2 updated (3bc2764 -> e730633)
This is an automated email from the ASF dual-hosted git repository.
ggrzybek pushed a change to branch KARAF-5376-overrides_v2
in repository https://gitbox.apache.org/repos/asf/karaf.git.
discard 3bc2764 [KARAF-5468] Cleaning up AssemblyMojo, Profiles and profile Builder
discard 48770ab [KARAF-5376] Polish LocationPattern and FeaturePattern helpers for blacklist/override matching
discard 56edacd [KARAF-5478] Provide org.apache.karaf.util.Version class
discard f2650df Revert "[KARAF-5416] Remove support for ext and endorsed libraries for Java 9 compatibility"
discard 9fca11c [KARAF-5376] Review BuilderTest
discard e91f6d1 [KARAF-5376] Include new Features JAXB model info in feature:* commands
discard 8057710 [KARAF-5376] Moving overrides and blacklist mechanisms to "features processor" service
add ef1595d Fix bin/inc to work with any shell
new 4662072 [KARAF-5376] Moving overrides and blacklist mechanisms to "features processor" service
new ddeb7db [KARAF-5376] Include new Features JAXB model info in feature:* commands
new e7ad6ea [KARAF-5376] Review BuilderTest
new 07bbb7d Revert "[KARAF-5416] Remove support for ext and endorsed libraries for Java 9 compatibility"
new 196b8ff [KARAF-5478] Provide org.apache.karaf.util.Version class
new 5e1c44d [KARAF-5376] Polish LocationPattern and FeaturePattern helpers for blacklist/override matching
new e730633 [KARAF-5468] Cleaning up AssemblyMojo, Profiles and profile Builder
This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version. This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:
* -- * -- B -- O -- O -- O (3bc2764)
\
N -- N -- N refs/heads/KARAF-5376-overrides_v2 (e730633)
You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.
Any revisions marked "omit" are not gone; other references still
refer to them. Any revisions marked "discard" are gone forever.
The 7 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
.../features/base/src/main/filtered-resources/resources/bin/inc | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
--
To stop receiving notification emails like this one, please contact
['"commits@karaf.apache.org" <co...@karaf.apache.org>'].
[karaf] 02/07: [KARAF-5376] Include new Features JAXB model info in
feature:* commands
Posted by gg...@apache.org.
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 ddeb7db58fe40f2e5ff40cdb78622a59112457b1
Author: Grzegorz Grzybek <gr...@gmail.com>
AuthorDate: Mon Nov 6 13:24:22 2017 +0100
[KARAF-5376] Include new Features JAXB model info in feature:* commands
---
.../karaf/features/command/InfoFeatureCommand.java | 3 +++
.../features/command/ListFeaturesCommand.java | 17 +++++++++++++++-
.../karaf/features/command/RepoListCommand.java | 23 +++++++++++++++++-----
.../internal/service/RepositoryCacheImpl.java | 2 ++
.../features/internal/service/RepositoryImpl.java | 6 ++++++
5 files changed, 45 insertions(+), 6 deletions(-)
diff --git a/features/command/src/main/java/org/apache/karaf/features/command/InfoFeatureCommand.java b/features/command/src/main/java/org/apache/karaf/features/command/InfoFeatureCommand.java
index 1ecc0a0..7c6a775 100644
--- a/features/command/src/main/java/org/apache/karaf/features/command/InfoFeatureCommand.java
+++ b/features/command/src/main/java/org/apache/karaf/features/command/InfoFeatureCommand.java
@@ -209,6 +209,9 @@ public class InfoFeatureCommand extends FeaturesCommandSupport {
if(startLevel > 0) {
sb.append(" start-level=").append(startLevel);
}
+ if (featureBundle.isOverriden()) {
+ sb.append(" (overriden from " + featureBundle.getOriginalLocation() + ")");
+ }
System.out.println(sb.toString());
}
}
diff --git a/features/command/src/main/java/org/apache/karaf/features/command/ListFeaturesCommand.java b/features/command/src/main/java/org/apache/karaf/features/command/ListFeaturesCommand.java
index 6bf9f9c..9e17153 100644
--- a/features/command/src/main/java/org/apache/karaf/features/command/ListFeaturesCommand.java
+++ b/features/command/src/main/java/org/apache/karaf/features/command/ListFeaturesCommand.java
@@ -27,6 +27,7 @@ import org.apache.karaf.features.Repository;
import org.apache.karaf.shell.api.action.Command;
import org.apache.karaf.shell.api.action.Option;
import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.support.table.Row;
import org.apache.karaf.shell.support.table.ShellTable;
@Command(scope = "feature", name = "list", description = "Lists all existing features available from the defined repositories.")
@@ -42,6 +43,9 @@ public class ListFeaturesCommand extends FeaturesCommandSupport {
@Option(name = "-s", aliases = {"--show-hidden"}, description = "Display hidden features", required = false, multiValued = false)
boolean showHidden;
+ @Option(name = "-b", aliases = {"--show-blacklisted"}, description = "Display blacklisted features", required = false, multiValued = false)
+ boolean showBlacklisted;
+
@Option(name = "-o", aliases = {"--ordered"}, description = "Display a list using alphabetical order ", required = false, multiValued = false)
boolean ordered;
@@ -58,6 +62,9 @@ public class ListFeaturesCommand extends FeaturesCommandSupport {
table.column("State");
table.column("Repository");
table.column("Description").maxSize(50);
+ if (showBlacklisted) {
+ table.column("Blacklisted");
+ }
table.emptyTableText(onlyInstalled ? "No features installed" : "No features available");
List<Repository> repos = Arrays.asList(featuresService.listRepositories());
@@ -75,17 +82,25 @@ public class ListFeaturesCommand extends FeaturesCommandSupport {
// Filter out not installed features if we only want to see the installed ones
continue;
}
+ if (!showBlacklisted && f.isBlacklisted()) {
+ // Filter out blacklisted
+ continue;
+ }
if (!showHidden && f.isHidden()) {
// Filter out hidden feature if not asked to display those
continue;
}
- table.addRow().addContent(
+ Row row = table.addRow();
+ row.addContent(
f.getName(),
f.getVersion(),
featuresService.isRequired(f) ? "x" : "",
featuresService.getState(f.getId()),
r.getName(),
f.getDescription());
+ if (showBlacklisted) {
+ row.addContent(f.isBlacklisted() ? "yes" : "no");
+ }
if (isInstalledViaDeployDir(r.getName())) {
needsLegend = true;
}
diff --git a/features/command/src/main/java/org/apache/karaf/features/command/RepoListCommand.java b/features/command/src/main/java/org/apache/karaf/features/command/RepoListCommand.java
index 50f3b95..a91182f 100644
--- a/features/command/src/main/java/org/apache/karaf/features/command/RepoListCommand.java
+++ b/features/command/src/main/java/org/apache/karaf/features/command/RepoListCommand.java
@@ -25,6 +25,7 @@ import org.apache.karaf.shell.api.action.Command;
import org.apache.karaf.shell.api.action.Option;
import org.apache.karaf.shell.api.action.lifecycle.Service;
import org.apache.karaf.shell.support.MultiException;
+import org.apache.karaf.shell.support.table.Row;
import org.apache.karaf.shell.support.table.ShellTable;
@Command(scope = "feature", name = "repo-list", description = "Displays a list of all defined repositories.")
@@ -34,9 +35,12 @@ public class RepoListCommand extends FeaturesCommandSupport {
@Option(name="-r", description="Reload all feature urls", required = false, multiValued = false)
boolean reload;
+ @Option(name = "-b", aliases = { " --show-blacklisted" }, description = "Also display blacklisted repositories", required = false, multiValued = false)
+ boolean showBlacklisted = false;
+
@Option(name = "--no-format", description = "Disable table rendered output", required = false, multiValued = false)
boolean noFormat;
-
+
protected void doExecute(FeaturesService featuresService) throws Exception {
if (reload) {
reloadAllRepos(featuresService);
@@ -45,15 +49,24 @@ public class RepoListCommand extends FeaturesCommandSupport {
ShellTable table = new ShellTable();
table.column("Repository");
table.column("URL");
+ if (showBlacklisted) {
+ table.column("Blacklisted");
+ }
table.emptyTableText("No repositories available");
Repository[] repos = featuresService.listRepositories();
- for (Repository repo : repos) {
+ for (Repository repo : repos) {
if (repo != null) {
- table.addRow().addContent(repo.getName(), repo.getURI().toString());
+ if (showBlacklisted || !repo.isBlacklisted()) {
+ Row row = table.addRow();
+ row.addContent(repo.getName(), repo.getURI().toString());
+ if (showBlacklisted) {
+ row.addContent(repo.isBlacklisted() ? "yes" : "no");
+ }
+ }
}
- }
- table.print(System.out, !noFormat);
+ }
+ table.print(System.out, !noFormat);
}
private void reloadAllRepos(FeaturesService featuresService) throws Exception {
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/RepositoryCacheImpl.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/RepositoryCacheImpl.java
index d33b39d..9874a20 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/service/RepositoryCacheImpl.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/RepositoryCacheImpl.java
@@ -46,7 +46,9 @@ public class RepositoryCacheImpl implements RepositoryCache {
public Repository create(URI uri, boolean validate) {
RepositoryImpl repository = new RepositoryImpl(uri, validate);
if (featuresProcessor != null) {
+ // maybe it could be done better - first we have to set if entire repo is blacklisted
repository.setBlacklisted(featuresProcessor.isRepositoryBlacklisted(uri));
+ // processing features will take the above flag into account to blacklist (if needed) the features
repository.processFeatures(featuresProcessor);
}
return repository;
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 31aadaf..3d76c23 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
@@ -107,6 +107,12 @@ public class RepositoryImpl implements Repository {
*/
public void processFeatures(FeaturesProcessor processor) {
processor.process(features);
+ if (blacklisted) {
+ // all features of blacklisted repository are blacklisted too
+ for (org.apache.karaf.features.internal.model.Feature feature : features.getFeature()) {
+ feature.setBlacklisted(true);
+ }
+ }
}
static class InterruptibleInputStream extends FilterInputStream {
--
To stop receiving notification emails like this one, please contact
"commits@karaf.apache.org" <co...@karaf.apache.org>.
[karaf] 07/07: [KARAF-5468] Cleaning up AssemblyMojo,
Profiles and profile Builder
Posted by gg...@apache.org.
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 e730633b019637d6ee0d694b3dbb018b2fabbe9c
Author: Grzegorz Grzybek <gr...@gmail.com>
AuthorDate: Wed Nov 15 15:43:01 2017 +0100
[KARAF-5468] Cleaning up AssemblyMojo, Profiles and profile Builder
* Added good amount of documentation for public API, Maven mojo
parameters and interfaces
* Removed unused karaf-maven-plugin:assembly parameters
* Renamed some of karaf-maven-plugin:assembly parameters
* Changed some of karaf-maven-plugin:assembly parameters from String to
List<String>
* Changed some string constants into enums (Builder.JavaVersion)
* Refactored AssemblyMojo
* Added new logging statements to AssemblyMojo and Builder
* Renamed "agent" to "profile" wherever possible
---
demos/profiles/dynamic/pom.xml | 6 +-
demos/profiles/static/pom.xml | 6 +-
profile/pom.xml | 1 +
.../java/org/apache/karaf/profile/Profile.java | 129 ++--
.../org/apache/karaf/profile/ProfileConstants.java | 128 ++++
.../org/apache/karaf/profile/assembly/Builder.java | 604 +++++++++++++++---
.../karaf/profile/command/ProfileDisplay.java | 14 +-
.../apache/karaf/profile/command/ProfileEdit.java | 28 +-
.../karaf/profile/impl/ProfileBuilderImpl.java | 28 +-
.../org/apache/karaf/profile/impl/ProfileImpl.java | 72 ++-
.../org/apache/karaf/profile/impl/Profiles.java | 99 ++-
.../apache/karaf/profile/impl/ProfilesTest.java | 142 +++++
tooling/karaf-maven-plugin/pom.xml | 4 -
.../org/apache/karaf/tooling/AssemblyMojo.java | 678 +++++++++++++--------
.../java/org/apache/karaf/tooling/VerifyMojo.java | 28 +-
.../org/apache/karaf/tooling/utils/MavenUtil.java | 32 +-
16 files changed, 1496 insertions(+), 503 deletions(-)
diff --git a/demos/profiles/dynamic/pom.xml b/demos/profiles/dynamic/pom.xml
index 9c2ec36..ce8f734 100644
--- a/demos/profiles/dynamic/pom.xml
+++ b/demos/profiles/dynamic/pom.xml
@@ -106,9 +106,9 @@
</execution>
</executions>
<configuration>
- <profilesUri>
- jar:mvn:org.apache.karaf.demos.profiles/registry/${project.version}!/
- </profilesUri>
+ <profilesUris>
+ <uri>jar:mvn:org.apache.karaf.demos.profiles/registry/${project.version}!/</uri>
+ </profilesUris>
<bootFeatures>
<feature>deployer</feature>
</bootFeatures>
diff --git a/demos/profiles/static/pom.xml b/demos/profiles/static/pom.xml
index 0542c6b..1250085 100644
--- a/demos/profiles/static/pom.xml
+++ b/demos/profiles/static/pom.xml
@@ -122,9 +122,9 @@
<configuration>
<useReferenceUrls>true</useReferenceUrls>
<environment>static</environment>
- <profilesUri>
- jar:mvn:org.apache.karaf.demos.profiles/registry/${project.version}!/
- </profilesUri>
+ <profilesUris>
+ <uri>jar:mvn:org.apache.karaf.demos.profiles/registry/${project.version}!/</uri>
+ </profilesUris>
<startupProfiles>
<profile>karaf</profile>
<profile>example-loanbroker-bank1</profile>
diff --git a/profile/pom.xml b/profile/pom.xml
index 223e522..118a14f 100644
--- a/profile/pom.xml
+++ b/profile/pom.xml
@@ -183,6 +183,7 @@
org.apache.karaf.profile.impl,
org.apache.karaf.profile.impl.osgi,
org.apache.karaf.profile.versioning,
+ org.apache.karaf.util,
org.apache.karaf.util.config,
org.apache.karaf.util.maven,
org.apache.felix.utils.manifest,
diff --git a/profile/src/main/java/org/apache/karaf/profile/Profile.java b/profile/src/main/java/org/apache/karaf/profile/Profile.java
index c9d4b3d..ca08bd2 100644
--- a/profile/src/main/java/org/apache/karaf/profile/Profile.java
+++ b/profile/src/main/java/org/apache/karaf/profile/Profile.java
@@ -21,88 +21,135 @@ import java.util.Map;
import java.util.Set;
/**
- * The immutable view of a profile
+ * <p>A <em>profile</em> is a container for configuration that can be applied to Karaf distribution.</p>
+ *
+ * <p>Profiles may inherit from other (single or multiple) profiles. An <em>overlay</em> profile is single
+ * profile with all the configurations, attributes and files from parent profiles, while configurations,
+ * attributes and files from <em>child</em> profile overwrites corresponding data from parent profiles.</p>
+ *
+ * <p>Configuration include:<ul>
+ * <li>Attributes</li>
+ * <li>ConfigAdmin configurations (PIDs) to put into <code>${karaf.etc}</code> directory</li>
+ * <li>Other resources to put into <code>${karaf.etc}</code> directory</li>
+ * </ul></p>
+ *
+ * <p>Attributes are properties in special file <code>profile.cfg</code> (<code>profile</code> PID) and may specify:<ul>
+ * <li>OSGi bundles to install (prefix: <code>bundle.</code>)</li>
+ * <li>Karaf features to install (prefix: <code>feature.</code>)</li>
+ * <li>Feature XML repositories to use to resolve bundles and features (prefix: <code>repository.</code>)</li>
+ * <li>Identifiers of parent profiles (property name: <code>attribute.parents</code>)</li>
+ * <li>Indication of abstract profile (property name: <code>abstract</code>)</li>
+ * <li>Indication of hidden profile (property name: <code>hidden</code>)</li>
+ * <li>Different attributes (prefix: <code>attribute.</code>)</li>
+ * <li>Properties to be added to <code>etc/config.properties</code> (prefix: <code>config.</code>)</li>
+ * <li>Properties to be added to <code>etc/system.properties</code> (prefix: <code>system.</code>)</li>
+ * <li>Additional libraries to be added to <code>lib</code> (prefix: <code>library.</code>)</li>
+ * <li>Additional libraries to be added to <code>lib/boot</code> (prefix: <code>boot.</code>)</li>
+ * <li>Additional libraries to be added to <code>lib/endorsed</code> (prefix: <code>endorsed.</code>)</li>
+ * <li>Additional libraries to be added to <code>lib/ext</code> (prefix: <code>ext.</code>)</li>
+ * <li>Bundle override definitions to be added to <code>etc/overrides.properties</code> (prefix: <code>override.</code>)</li>
+ * <li>Optional {@link org.osgi.resource.Resource resources} to be used during resolution (prefix: <code>optional.</code>)</li>
+ * </ul></p>
*/
-public interface Profile {
+public interface Profile extends ProfileConstants {
/**
- * The attribute key for the list of parents
+ * Returns an attribute map of this profile
+ * @return
*/
- String PARENTS = "parents";
+ Map<String, String> getAttributes();
/**
- * The attribute key for the description of the profile
+ * Returns a property map for additional properties to be added to <code>${karaf.etc}/config.properties</code>
+ * @return
*/
- String DESCRIPTION = "description";
+ Map<String, String> getConfig();
/**
- * The attribute key for the abstract flag
+ * Returns a property map for additional properties to be added to <code>${karaf.etc}/system.properties</code>
+ * @return
*/
- String ABSTRACT = "abstract";
+ Map<String, String> getSystem();
/**
- * The attribute key for the hidden flag
+ * Returns a unique identifier of this profile
+ * @return
*/
- String HIDDEN = "hidden";
+ String getId();
/**
- * Key indicating a deletion.
- * This value can appear as the value of a key in a configuration
- * or as a key itself. If used as a key, the whole configuration
- * is flagged has been deleted from its parent when computing the
- * overlay.
+ * Returns a list of parent profile identifiers for this profile
+ * @return
*/
- String DELETED = "#deleted#";
+ List<String> getParentIds();
/**
- * The pid of the configuration holding internal profile attributes
+ * Returns a list of bundles (bundle URIs) defined in this profile
+ * @return
*/
- String INTERNAL_PID = "profile";
+ List<String> getBundles();
/**
- * The file suffix for a configuration
+ * Returns a list of features (<code>feature-name[/feature-version]</code>) defined in this profile
+ * @return
*/
- String PROPERTIES_SUFFIX = ".cfg";
+ List<String> getFeatures();
/**
- * The attribute prefix for in the agent configuration
+ * Returns a list of features XML repositories (URIs) defined in this profile
+ * @return
*/
- String ATTRIBUTE_PREFIX = "attribute.";
+ List<String> getRepositories();
/**
- * The config prefix for in the agent configuration
+ * Returns a list of libraries (to be added to <code>${karaf.home}/lib</code>) defined in this profile
+ * @return
*/
- String CONFIG_PREFIX = "config.";
+ List<String> getLibraries();
/**
- * The config prefix for in the agent configuration
+ * Returns a list of boot libraries (to be added to <code>${karaf.home}/lib/boot</code>) defined in this profile
+ * @return
*/
- String SYSTEM_PREFIX = "system.";
+ List<String> getBootLibraries();
- Map<String, String> getAttributes();
- Map<String, String> getConfig();
- Map<String, String> getSystem();
+ /**
+ * Returns a list of endorsed libraries (to be added to <code>${karaf.home}/lib/endorsed</code>) defined in this profile
+ * @return
+ */
+ List<String> getEndorsedLibraries();
- List<String> getParentIds();
+ /**
+ * Returns a list of extension libraries (to be added to <code>${karaf.home}/lib/ext</code>) defined in this profile
+ * @return
+ */
+ List<String> getExtLibraries();
- List<String> getLibraries();
- List<String> getBundles();
- List<String> getFeatures();
- List<String> getRepositories();
+ /**
+ * Returns a list of bundle override definitions (to be added to <code>${karaf.etc}/overrides.properties</code>)
+ * defined in this profile
+ * @return
+ */
List<String> getOverrides();
- List<String> getOptionals();
- String getId();
+ /**
+ * Returns a list of optional {@link org.osgi.resource.Resource resources} (URIs) to be used during
+ * resolution
+ * @return
+ */
+ List<String> getOptionals();
/**
- * Get the configuration file names that are available on this profile.
+ * Get the configuration file names that are available on this profile. This list should contain at least
+ * <code>profile.cfg</code> file.
*
* @return The configuration file names in the profile.
*/
Set<String> getConfigurationFileNames();
/**
- * Get all file configurations.
+ * Get all file configurations. This list should contain at least
+ * <code>profile.cfg</code> file.
*
* @return The file configurations in the profile.
*/
@@ -117,7 +164,8 @@ public interface Profile {
byte[] getFileConfiguration(String fileName);
/**
- * Get all configuration properties.
+ * Get all configuration properties.This list should contain at least
+ * configuration from main profile file - <code>profile.cfg</code>.
*
* @return The configurations in the profile.
*/
@@ -132,7 +180,8 @@ public interface Profile {
Map<String, Object> getConfiguration(String pid);
/**
- * Indicate if this profile is an overlay or not.
+ * Indicate if this profile is an overlay or not. An <em>overlay</em> profile includes configurations and
+ * attributes of parent profiles, while descendant profiles always have priority over parent profiles.
*
* @return True if the profile is an overlay, false else.
*/
diff --git a/profile/src/main/java/org/apache/karaf/profile/ProfileConstants.java b/profile/src/main/java/org/apache/karaf/profile/ProfileConstants.java
new file mode 100644
index 0000000..fe9417d
--- /dev/null
+++ b/profile/src/main/java/org/apache/karaf/profile/ProfileConstants.java
@@ -0,0 +1,128 @@
+/*
+ * 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.profile;
+
+public interface ProfileConstants {
+
+ /**
+ * The attribute prefix for the profile configuration (<code>profile.cfg</code>)
+ */
+ String ATTRIBUTE_PREFIX = "attribute.";
+
+ /**
+ * The attribute key for whitespace-separated list of parent profile IDs
+ */
+ String PARENTS = ATTRIBUTE_PREFIX + "parents";
+
+ /**
+ * The attribute key for the description of the profile
+ */
+ String DESCRIPTION = "description";
+
+ /**
+ * The attribute key for the <em>abstract</em> flag
+ */
+ String ABSTRACT = "abstract";
+
+ /**
+ * The attribute key for the <em>hidden</em> flag
+ */
+ String HIDDEN = "hidden";
+
+ /**
+ * <p>Key indicating a deletion.</p>
+ * <p>This value can appear as the value of a key in a configuration
+ * or as a key itself. If used as a key, the whole configuration
+ * is flagged as deleted from its parent when computing the overlay.</p>
+ */
+ String DELETED = "#deleted#";
+
+ /**
+ * The pid of the configuration holding internal profile attributes
+ */
+ String INTERNAL_PID = "profile";
+
+ /**
+ * The file suffix for a configuration
+ */
+ String PROPERTIES_SUFFIX = ".cfg";
+
+ /**
+ * The prefix for attributes that are targeted for <code>${karaf.etc}/config.properties</code> file
+ */
+ String CONFIG_PREFIX = "config.";
+
+ /**
+ * The prefix for attributes that are targeted for <code>${karaf.etc}/system.properties</code> file
+ */
+ String SYSTEM_PREFIX = "system.";
+
+ /**
+ * The prefix for attributes that specify URIs of features XML files
+ */
+ String REPOSITORY_PREFIX = "repository.";
+
+ /**
+ * The prefix for attributes that specify feature names (<code>name[/version]</code>) to install/use
+ */
+ String FEATURE_PREFIX = "feature.";
+
+ /**
+ * The prefix for attributes that specify bundle URIs to install
+ */
+ String BUNDLE_PREFIX = "bundle.";
+
+ /**
+ * The prefix for attributes that specify additional libraries to add to <code>${karaf.home}/lib</code>.
+ * These are native libraries only. JARs that should be available in app classpath should go to
+ * <code>${karaf.home}/lib/boot</code> and use {@link #BOOT_PREFIX}.
+ */
+ String LIB_PREFIX = "library.";
+
+ /**
+ * The prefix for attributes that specify additional endorsed libraries to add to
+ * <code>${karaf.home}/lib/endorsed</code>
+ */
+ String ENDORSED_PREFIX = "endorsed.";
+
+ /**
+ * The prefix for attributes that specify additional extension libraries to add to
+ * <code>${karaf.home}/lib/ext</code>
+ */
+ String EXT_PREFIX = "ext.";
+
+ /**
+ * The prefix for attributes that specify additional endorsed libraries to add to
+ * <code>${karaf.home}/lib/boot</code>
+ */
+ String BOOT_PREFIX = "boot.";
+
+ /**
+ * The prefix for attributes that specify bundle overrides
+ * (see {@link org.apache.karaf.features.internal.service.Overrides}). In version 4.2 it's better to use
+ * {@link org.apache.karaf.features.internal.service.FeaturesProcessor} configuration.
+ */
+ String OVERRIDE_PREFIX = "override.";
+
+ /**
+ * The prefix for attributes that specify optional resources
+ */
+ String OPTIONAL_PREFIX = "optional.";
+
+}
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 e48b6cf..ba72134 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
@@ -39,6 +39,7 @@ import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
@@ -48,14 +49,17 @@ import java.util.concurrent.ScheduledExecutorService;
import java.util.function.Function;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
+import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import org.apache.felix.resolver.ResolverImpl;
import org.apache.felix.utils.manifest.Clause;
import org.apache.felix.utils.properties.Properties;
+import org.apache.karaf.features.FeaturePattern;
import org.apache.karaf.features.FeaturesService;
import org.apache.karaf.features.Library;
+import org.apache.karaf.features.LocationPattern;
import org.apache.karaf.features.internal.download.DownloadCallback;
import org.apache.karaf.features.internal.download.DownloadManager;
import org.apache.karaf.features.internal.download.Downloader;
@@ -67,17 +71,21 @@ import org.apache.karaf.features.internal.model.Dependency;
import org.apache.karaf.features.internal.model.Feature;
import org.apache.karaf.features.internal.model.Features;
import org.apache.karaf.features.internal.model.JaxbUtil;
+import org.apache.karaf.features.internal.model.processing.FeaturesProcessing;
import org.apache.karaf.features.internal.repository.BaseRepository;
import org.apache.karaf.features.internal.resolver.ResourceBuilder;
import org.apache.karaf.features.internal.service.Blacklist;
import org.apache.karaf.features.internal.service.Deployer;
import org.apache.karaf.features.internal.util.MapUtils;
+import org.apache.karaf.features.internal.util.MultiException;
import org.apache.karaf.kar.internal.Kar;
import org.apache.karaf.profile.Profile;
import org.apache.karaf.profile.ProfileBuilder;
import org.apache.karaf.profile.impl.Profiles;
import org.apache.karaf.tools.utils.KarafPropertiesEditor;
import org.apache.karaf.tools.utils.model.KarafPropertyEdits;
+import org.apache.karaf.util.ThreadUtils;
+import org.apache.karaf.util.Version;
import org.apache.karaf.util.config.PropertiesLoader;
import org.apache.karaf.util.maven.Parser;
import org.ops4j.pax.url.mvn.MavenResolver;
@@ -91,9 +99,11 @@ import org.slf4j.LoggerFactory;
import static java.util.Collections.singletonList;
import static java.util.jar.JarFile.MANIFEST_NAME;
-import static org.apache.karaf.features.internal.service.Blacklist.TYPE_REPOSITORY;
import static org.apache.karaf.profile.assembly.Builder.Stage.Startup;
+/**
+ * A builder-like class to create instances of {@link Profile profiles}.
+ */
public class Builder {
private static final String STATIC_FEATURES_KAR = "mvn:org.apache.karaf.features/static/%s/kar";
@@ -106,25 +116,105 @@ public class Builder {
private static final String LIBRARY_CLAUSE_TYPE = "type";
private static final String LIBRARY_CLAUSE_EXPORT = "export";
private static final String LIBRARY_CLAUSE_DELEGATE = "delegate";
+
public static final String ORG_OPS4J_PAX_URL_MVN_PID = "org.ops4j.pax.url.mvn";
+ /**
+ * <p>An indication of <em>stage</em> for bundles/features/repositories/kars/profiles.</p>
+ */
public enum Stage {
- Startup, Boot, Installed
+ /**
+ * Karaf runtime is in <em>startup</em> stage when it installs OSGi bundles into OSGi framework before
+ * passing this responsibility to {@link FeaturesService}. A list of bundles to install is defined
+ * in <code>${karaf.etc}/startup.properties</code>.
+ */
+ Startup,
+ /**
+ * Karaf runtime is in <em>boot</em> stage when it installs OSGi bundles using Karaf features. Features
+ * (and features XML repositories) are defined in <code>${karaf.etc}/org.apache.karaf.features.cfg</code>.
+ * Repositories and features available in startup stage should be <em>visible</em> in boot stage as well, as
+ * this is the stage where term <em>Karaf feature</em> gets its meaning.
+ */
+ Boot,
+ /**
+ * <em>Installed</em> stage is just a space where bundles and features may be installed after starting
+ * Karaf runtime (e.g., using Karaf shell commands, JMX or UI).
+ */
+ Installed;
+
+ /**
+ * Get a {@link Stage} corresponding to Maven scope.
+ * @param scope
+ * @return
+ */
+ public static Stage fromMavenScope(String scope) {
+ switch (scope) {
+ case "compile":
+ return Builder.Stage.Startup;
+ case "runtime":
+ return Builder.Stage.Boot;
+ case "provided":
+ return Builder.Stage.Installed;
+ default:
+ return null;
+ }
+ }
}
+ /**
+ * <p>An identifiier of Karaf version <em>family</em>. Each version family may have special methods
+ * or requirements for generating/preparing configuration.</p>
+ */
public enum KarafVersion {
v24, v3x, v4x
}
+ /**
+ * <p>An idenfifier for supported Java version. This version is used for example in
+ * <code>${karaf.etc}/jre.properties</code> to define system packages for given Java version. Only
+ * supported versions are defined.</p>
+ */
+ public enum JavaVersion {
+ Java16("1.6"), Java17("1.7"), Java18("1.8"), Java9("9");
+ private String version;
+
+ JavaVersion(String version) {
+ this.version = version;
+ }
+
+ public static JavaVersion from(String version) {
+ Optional<JavaVersion> v = Arrays.stream(values())
+ .filter(jv -> jv.version.equals(version))
+ .findFirst();
+
+ if (!v.isPresent()) {
+ throw new IllegalArgumentException("Java version \"" + version + "\" is not supported");
+ }
+ return v.get();
+ }
+ }
+
+ /**
+ * TODOCUMENT
+ */
public enum BlacklistPolicy {
Discard,
Fail
}
+ /**
+ * Configuration of features XML repository (standalone or inside KAR). <code>addAll</code> may configure
+ * given repository to install all defined features if no explicit feature is specified.
+ */
static class RepositoryInfo {
Stage stage;
boolean addAll;
+
+ public RepositoryInfo(Stage stage, boolean addAll) {
+ this.stage = stage;
+ this.addAll = addAll;
+ }
}
//
@@ -145,7 +235,7 @@ public class Builder {
List<String> blacklistedRepositories = new ArrayList<>();
BlacklistPolicy blacklistPolicy = BlacklistPolicy.Discard;
List<String> libraries = new ArrayList<>();
- String javase = "1.7";
+ JavaVersion javase = JavaVersion.Java18;
KarafVersion karafVersion = KarafVersion.v4x;
String environment = null;
boolean useReferenceUrls;
@@ -158,6 +248,7 @@ public class Builder {
Map<String, String> config = new LinkedHashMap<>();
Map<String, String> system = new LinkedHashMap<>();
List<String> pidsToExtract = new LinkedList<>();
+ boolean writeProfiles;
private ScheduledExecutorService executor;
private DownloadManager manager;
@@ -166,74 +257,139 @@ public class Builder {
private Path systemDirectory;
private Map<String, Profile> allProfiles;
private KarafPropertyEdits propertyEdits;
+ private FeaturesProcessing featuresProcessing = new FeaturesProcessing();
private Map<String, String> translatedUrls;
- private Function<MavenResolver, MavenResolver> resolverWrapper = null;
+ private Function<MavenResolver, MavenResolver> resolverWrapper = Function.identity();
public static Builder newInstance() {
return new Builder();
}
+ /**
+ * Sets the {@link Stage} used by next builder invocations.
+ * @param stage
+ * @return
+ */
public Builder defaultStage(Stage stage) {
this.defaultStage = stage;
return this;
}
+ /**
+ * Sets default <em>add all</em> flag for KARs and repositories.
+ * @param addAll
+ * @return
+ */
public Builder defaultAddAll(boolean addAll) {
this.defaultAddAll = addAll;
return this;
}
+ /**
+ * Configure a list of profile URIs to be used for profile import
+ * @param profilesUri
+ * @return
+ */
public Builder profilesUris(String... profilesUri) {
Collections.addAll(this.profilesUris, profilesUri);
return this;
}
+ /**
+ * Configure libraries to use. Each library may contain OSGi header-like directives: <code>type</code>,
+ * <code>url</code>, <code>export</code> and <code>delegate</code>.
+ * @param libraries
+ * @return
+ */
public Builder libraries(String... libraries) {
Collections.addAll(this.libraries, libraries);
return this;
}
+ /**
+ * Configure KARs to use at current {@link #defaultStage stage} with default <em>add all</em> flag
+ * @param kars
+ * @return
+ */
public Builder kars(String... kars) {
return kars(defaultStage, defaultAddAll, kars);
}
+ /**
+ * Configure KARs to use at current {@link #defaultStage stage} with given <em>add all</em> flag
+ * @param addAll
+ * @param kars
+ * @return
+ */
public Builder kars(boolean addAll, String... kars) {
return kars(defaultStage, addAll, kars);
}
+ /**
+ * Configure KARs to use at given stage with given <em>add all</em> flag
+ * @param stage
+ * @param addAll
+ * @param kars
+ * @return
+ */
public Builder kars(Stage stage, boolean addAll, String... kars) {
for (String kar : kars) {
- RepositoryInfo info = new RepositoryInfo();
- info.stage = stage;
- info.addAll = addAll;
- this.kars.put(kar, info);
+ this.kars.put(kar, new RepositoryInfo(stage, addAll));
}
return this;
}
+ /**
+ * Configure features XML repositories to use at current {@link #defaultStage stage} with default <em>add all</em> flag
+ * @param repositories
+ * @return
+ */
public Builder repositories(String... repositories) {
return repositories(defaultStage, defaultAddAll, repositories);
}
+ /**
+ * Configure features XML repositories to use at current {@link #defaultStage stage} with given <em>add all</em> flag
+ * @param addAll
+ * @param repositories
+ * @return
+ */
public Builder repositories(boolean addAll, String... repositories) {
return repositories(defaultStage, addAll, repositories);
}
+ /**
+ * Configure features XML repositories to use at given stage with given <em>add all</em> flag
+ * @param stage
+ * @param addAll
+ * @param repositories
+ * @return
+ */
public Builder repositories(Stage stage, boolean addAll, String... repositories) {
for (String repository : repositories) {
- RepositoryInfo info = new RepositoryInfo();
- info.stage = stage;
- info.addAll = addAll;
- this.repositories.put(repository, info);
+ this.repositories.put(repository, new RepositoryInfo(stage, addAll));
}
return this;
}
+ /**
+ * Configure features to use at current {@link #defaultStage stage}. Each feature may be specified as
+ * <code>name</code> or <code>name/version</code> (no version ranges allowed).
+ * @param features
+ * @return
+ */
public Builder features(String... features) {
return features(defaultStage, features);
}
+ /**
+ * Configure features to use at given stage. Each feature may be specified as <code>name</code> or
+ * <code>name/version</code> (no version ranges allowed).
+ * @param stage
+ * @param features
+ * @return
+ */
public Builder features(Stage stage, String... features) {
for (String feature : features) {
this.features.put(feature, stage);
@@ -241,10 +397,21 @@ public class Builder {
return this;
}
+ /**
+ * Configure bundle URIs to use at current {@link #defaultStage stage}.
+ * @param bundles
+ * @return
+ */
public Builder bundles(String... bundles) {
return bundles(defaultStage, bundles);
}
+ /**
+ * Configure bundle URIs to use at given stage.
+ * @param stage
+ * @param bundles
+ * @return
+ */
public Builder bundles(Stage stage, String... bundles) {
for (String bundle : bundles) {
this.bundles.put(bundle, stage);
@@ -252,10 +419,21 @@ public class Builder {
return this;
}
+ /**
+ * Configure profiles to use at current {@link #defaultStage stage}.
+ * @param profiles
+ * @return
+ */
public Builder profiles(String... profiles) {
return profiles(defaultStage, profiles);
}
+ /**
+ * Configure profiles to use at given stage.
+ * @param stage
+ * @param profiles
+ * @return
+ */
public Builder profiles(Stage stage, String... profiles) {
for (String profile : profiles) {
this.profiles.put(profile, stage);
@@ -263,6 +441,11 @@ public class Builder {
return this;
}
+ /**
+ * Configure target directory, where distribution is being assembled.
+ * @param homeDirectory
+ * @return
+ */
public Builder homeDirectory(Path homeDirectory) {
if (homeDirectory == null) {
throw new IllegalArgumentException("homeDirectory is null");
@@ -271,101 +454,213 @@ public class Builder {
return this;
}
+ /**
+ * Configure Java version to use. This version will be resolved in several property placeholders inside
+ * <code>${karaf.etc}/config.properties</code> and <code>${karaf.etc}/jre.properties</code>.
+ * @param javase
+ * @return
+ */
public Builder javase(String javase) {
if (javase == null) {
throw new IllegalArgumentException("javase is null");
}
- this.javase = javase;
+ this.javase = JavaVersion.from(javase);
return this;
}
+ /**
+ * Set environment to use that may be used to select different variant of PID configuration file, e.g.,
+ * <code>org.ops4j.pax.url.mvn.cfg#docker</code>.
+ * @param environment
+ * @return
+ */
public Builder environment(String environment) {
this.environment = environment;
return this;
}
+ /**
+ * Configure builder to generate <code>reference:</code>-like URIs in <code>${karaf.etc}/startup.properties</code>.
+ * Bundles declared in this way are not copied (by Felix) to <code>data/cache</code> directory, but are
+ * used from original location.
+ * @return
+ */
public Builder useReferenceUrls() {
return useReferenceUrls(true);
}
+ /**
+ * Configure builder to use (when <code>true</code>) <code>reference:</code>-like URIs in
+ * <code>${karaf.etc}/startup.properties</code>.
+ * @param useReferenceUrls
+ * @return
+ */
public Builder useReferenceUrls(boolean useReferenceUrls) {
this.useReferenceUrls = useReferenceUrls;
return this;
}
+ /**
+ * Configure builder to copy generated and configured profiles into <code>${karaf.etc}/profiles</code>
+ * directory.
+ * @param writeProfiles
+ */
+ public void writeProfiles(boolean writeProfiles) {
+ this.writeProfiles = writeProfiles;
+ }
+
+ /**
+ * Configure Karaf version to target. This impacts the way some configuration files are generated.
+ * @param karafVersion
+ * @return
+ */
public Builder karafVersion(KarafVersion karafVersion) {
this.karafVersion = karafVersion;
return this;
}
+ /**
+ * Sets default start level for bundles declared in <code>${karaf.etc}/startup.properties</code>.
+ * @param defaultStartLevel
+ * @return
+ */
public Builder defaultStartLevel(int defaultStartLevel) {
this.defaultStartLevel = defaultStartLevel;
return this;
}
+ /**
+ * Ignore the dependency attribute (dependency="[true|false]") on bundles, effectively forcing their
+ * installation.
+ */
public Builder ignoreDependencyFlag() {
return ignoreDependencyFlag(true);
}
+ /**
+ * Configures builder to ignore (or not) <code>dependency</code> flag on bundles declared
+ * in features XML file.
+ * @param ignoreDependencyFlag
+ * @return
+ */
public Builder ignoreDependencyFlag(boolean ignoreDependencyFlag) {
this.ignoreDependencyFlag = ignoreDependencyFlag;
return this;
}
+ /**
+ * Configures builder to use offline pax-url-aether resolver
+ * @return
+ */
+ public Builder offline() {
+ return offline(true);
+ }
+
+ /**
+ * Configures whether pax-url-aether resolver should work in offline mode
+ * @param offline
+ * @return
+ */
public Builder offline(boolean offline) {
this.offline = offline;
return this;
}
- public Builder offline() {
- return offline(true);
- }
-
+ /**
+ * Configures local Maven repository to use by pax-url-aether. By default, assembly mojo sets the value
+ * read from current Maven build.
+ * @param localRepository
+ * @return
+ */
public Builder localRepository(String localRepository) {
this.localRepository = localRepository;
return this;
}
+ /**
+ * Configures comma-separated list of remote Maven repositories to use by pax-url-aether.
+ * By default, assembly mojo sets the repositories from current Maven build.
+ * @param mavenRepositories
+ * @return
+ */
public Builder mavenRepositories(String mavenRepositories) {
this.mavenRepositories = mavenRepositories;
return this;
}
+ /**
+ * Configures a function that may alter/replace {@link MavenResolver} used to resolve <code>mvn:</code> URIs.
+ * @param wrapper
+ * @return
+ */
public Builder resolverWrapper(Function<MavenResolver, MavenResolver> wrapper) {
this.resolverWrapper = wrapper;
return this;
}
+ /**
+ * Short-hand builder configuration to use standard Karaf static KAR at current Karaf version
+ * @return
+ */
public Builder staticFramework() {
- // TODO: load this from resources
- return staticFramework("4.0.0-SNAPSHOT");
+ return staticFramework(Version.karafVersion());
}
+ /**
+ * Short-hand builder configuration to use standard Karaf static KAR at given Karaf version
+ * @param version
+ * @return
+ */
public Builder staticFramework(String version) {
String staticFeaturesKar = String.format(STATIC_FEATURES_KAR, version);
return this.defaultStage(Startup).useReferenceUrls().kars(Startup, true, staticFeaturesKar);
}
+ /**
+ * Configure a list of blacklisted profile names (possibly using <code>*</code> glob)
+ * @param profiles
+ * @return
+ */
public Builder blacklistProfiles(Collection<String> profiles) {
this.blacklistedProfiles.addAll(profiles);
return this;
}
+ /**
+ * Configure a list of blacklisted feature names (see {@link FeaturePattern})
+ * @param features
+ * @return
+ */
public Builder blacklistFeatures(Collection<String> features) {
this.blacklistedFeatures.addAll(features);
return this;
}
+ /**
+ * Configure a list of blacklisted bundle URIs (see {@link LocationPattern})
+ * @param bundles
+ * @return
+ */
public Builder blacklistBundles(Collection<String> bundles) {
this.blacklistedBundles.addAll(bundles);
return this;
}
+ /**
+ * Configure a list of blacklisted features XML repository URIs (see {@link LocationPattern})
+ * @param repositories
+ * @return
+ */
public Builder blacklistRepositories(Collection<String> repositories) {
this.blacklistedRepositories.addAll(repositories);
return this;
}
+ /**
+ * TODOCUMENT
+ * @param policy
+ * @return
+ */
public Builder blacklistPolicy(BlacklistPolicy policy) {
this.blacklistPolicy = policy;
return this;
@@ -381,6 +676,12 @@ public class Builder {
return this;
}
+ /**
+ * Configures a list of PIDs (or PID patterns) to copy to <code>${karaf.etc}</code> from features, when
+ * assembling a distribution
+ * @param pidsToExtract
+ * @return
+ */
public Builder pidsToExtract(List<String> pidsToExtract) {
if (pidsToExtract != null) {
for (String pid : pidsToExtract) {
@@ -401,11 +702,23 @@ public class Builder {
return this;
}
+ /**
+ * Configures additional properties to add to <code>${karaf.etc}/config.properties</code>
+ * @param key
+ * @param value
+ * @return
+ */
public Builder config(String key, String value) {
this.config.put(key, value);
return this;
}
+ /**
+ * Configures additional properties to add to <code>${karaf.etc}/system.properties</code>
+ * @param key
+ * @param value
+ * @return
+ */
public Builder system(String key, String value) {
this.system.put(key, value);
return this;
@@ -435,6 +748,10 @@ public class Builder {
return pidsToExtract;
}
+ /**
+ * Main method to generate custom Karaf distribution using configuration provided with builder-like methods.
+ * @throws Exception
+ */
public void generateAssembly() throws Exception {
if (javase == null) {
throw new IllegalArgumentException("javase is not set");
@@ -443,6 +760,11 @@ public class Builder {
throw new IllegalArgumentException("homeDirectory is not set");
}
try {
+ executor = Executors.newScheduledThreadPool(8, ThreadUtils.namedThreadFactory("builder"));
+
+ systemDirectory = homeDirectory.resolve("system");
+ etcDirectory = homeDirectory.resolve("etc");
+
doGenerateAssembly();
} finally {
if (executor != null) {
@@ -452,47 +774,35 @@ public class Builder {
}
private void doGenerateAssembly() throws Exception {
- systemDirectory = homeDirectory.resolve("system");
- etcDirectory = homeDirectory.resolve("etc");
-
- LOGGER.info("Generating karaf assembly: " + homeDirectory);
+ LOGGER.info("Generating Karaf assembly: " + homeDirectory);
//
- // Create download manager
+ // Create download manager - combination of pax-url-aether and a resolver wrapper that may
+ // alter the way pax-url-aether resolver works
//
- Dictionary<String, String> props = new Hashtable<>();
- if (offline) {
- props.put(ORG_OPS4J_PAX_URL_MVN_PID + "offline", "true");
- }
- if (localRepository != null) {
- props.put(Builder.ORG_OPS4J_PAX_URL_MVN_PID + ".localRepository", localRepository);
- }
- if (mavenRepositories != null) {
- props.put(Builder.ORG_OPS4J_PAX_URL_MVN_PID + ".repositories", mavenRepositories);
- }
- MavenResolver resolver = MavenResolvers.createMavenResolver(props, ORG_OPS4J_PAX_URL_MVN_PID);
- if (resolverWrapper != null) {
- resolver = resolverWrapper.apply(resolver);
- }
- executor = Executors.newScheduledThreadPool(8);
+ MavenResolver resolver = createMavenResolver();
manager = new CustomDownloadManager(resolver, executor, null, translatedUrls);
this.resolver = new ResolverImpl(new Slf4jResolverLog(LOGGER));
//
- // Unzip kars
+ // Unzip KARs
//
LOGGER.info("Unzipping kars");
- Map<String, RepositoryInfo> repositories = new LinkedHashMap<>(this.repositories);
+// Map<String, RepositoryInfo> repositories = new LinkedHashMap<>(this.repositories);
Downloader downloader = manager.createDownloader();
for (String kar : kars.keySet()) {
downloader.download(kar, null);
}
downloader.await();
+ // each KAR is extracted and all features XML repositories found there are added to the same
+ // stage as the KAR and with the same "add all" flag as the KAR itself
for (String karUri : kars.keySet()) {
+ LOGGER.info(" processing KAR: " + karUri);
Kar kar = new Kar(manager.getProviders().get(karUri).getFile().toURI());
kar.extract(systemDirectory.toFile(), homeDirectory.toFile());
RepositoryInfo info = kars.get(karUri);
for (URI repositoryUri : kar.getFeatureRepos()) {
+ LOGGER.info(" found repository: " + repositoryUri);
repositories.put(repositoryUri.toString(), info);
}
}
@@ -500,12 +810,13 @@ public class Builder {
//
// Propagate feature installation from repositories
//
- LOGGER.info(" Loading repositories");
- Map<String, Stage> features = new LinkedHashMap<>(this.features);
+ LOGGER.info("Loading repositories");
+// Map<String, Stage> features = new LinkedHashMap<>(this.features);
Map<String, Features> karRepositories = loadRepositories(manager, repositories.keySet(), false);
for (String repo : repositories.keySet()) {
RepositoryInfo info = repositories.get(repo);
if (info.addAll) {
+ LOGGER.info(" adding all features from repository: " + repo + " (stage: " + info.stage + ")");
for (Feature feature : karRepositories.get(repo).getFeature()) {
features.put(feature.getId(), info.stage);
}
@@ -515,72 +826,74 @@ public class Builder {
//
// Load profiles
//
- LOGGER.info("Loading profiles");
- allProfiles = new HashMap<>();
- for (String profilesUri : profilesUris) {
- String uri = profilesUri;
- if (uri.startsWith("jar:") && uri.contains("!/")) {
- uri = uri.substring("jar:".length(), uri.indexOf("!/"));
- }
- if (!uri.startsWith("file:")) {
- downloader = manager.createDownloader();
- downloader.download(uri, null);
- downloader.await();
- StreamProvider provider = manager.getProviders().get(uri);
- profilesUri = profilesUri.replace(uri, provider.getFile().toURI().toString());
- }
- URI profileURI = URI.create(profilesUri);
- Path profilePath;
- try {
- profilePath = Paths.get(profileURI);
- } catch (FileSystemNotFoundException e) {
- // file system does not exist, try to create it
- FileSystem fs = FileSystems.newFileSystem(profileURI, new HashMap<>(), Builder.class.getClassLoader());
- profilePath = fs.provider().getPath(profileURI);
- }
- allProfiles.putAll(Profiles.loadProfiles(profilePath));
- // Handle blacklisted profiles
- if (!blacklistedProfiles.isEmpty()) {
- if (blacklistPolicy == BlacklistPolicy.Discard) {
- // Override blacklisted profiles with empty ones
- for (String profile : blacklistedProfiles) {
- allProfiles.put(profile, ProfileBuilder.Factory.create(profile).getProfile());
- }
- } else {
- // Remove profiles completely
- allProfiles.keySet().removeAll(blacklistedProfiles);
- }
- }
+ LOGGER.info("Loading profiles from:");
+ profilesUris.forEach(p -> LOGGER.info(" " + p));
+ allProfiles = loadExternalProfiles(profilesUris);
+ if (allProfiles.size() > 0) {
+ StringBuilder sb = new StringBuilder();
+ LOGGER.info(" Found profiles: " + allProfiles.keySet().stream().collect(Collectors.joining(", ")));
}
- // Generate profiles
+ //
+ // Generate profiles. If user has configured additional profiles, they'll be used as parents
+ // of the generated ones.
+ //
Profile startupProfile = generateProfile(Stage.Startup, profiles, repositories, features, bundles);
+ allProfiles.put(startupProfile.getId(), startupProfile);
+
+ // generated startup profile should be used (together with configured startup and boot profiles) as parent
+ // of the generated boot profile - similar visibility rule (boot stage requires startup stage) is applied
+ // for repositories and features
profiles.put(startupProfile.getId(), Stage.Boot);
Profile bootProfile = generateProfile(Stage.Boot, profiles, repositories, features, bundles);
+ allProfiles.put(bootProfile.getId(), bootProfile);
+
Profile installedProfile = generateProfile(Stage.Installed, profiles, repositories, features, bundles);
+ allProfiles.put(installedProfile.getId(), installedProfile);
//
- // Compute overall profile
+ // Compute "overlay" profile - a single profile with all parent profiles included (when there's the same
+ // file in both profiles, parent profile's version has lower priority)
//
ProfileBuilder builder = ProfileBuilder.Factory.create(UUID.randomUUID().toString())
.setParents(Arrays.asList(startupProfile.getId(), bootProfile.getId(), installedProfile.getId()));
config.forEach((k ,v) -> builder.addConfiguration(Profile.INTERNAL_PID, Profile.CONFIG_PREFIX + k, v));
system.forEach((k ,v) -> builder.addConfiguration(Profile.INTERNAL_PID, Profile.SYSTEM_PREFIX + k, v));
- Profile overallProfile = builder
- .getProfile();
+ // profile with all the parents configured
+ Profile overallProfile = builder.getProfile();
+
+ // profile with parents included and "flattened" using inheritance rules (child files overwrite parent
+ // files and child PIDs are merged with parent PIDs and same properties are taken from child profiles)
Profile overallOverlay = Profiles.getOverlay(overallProfile, allProfiles, environment);
+
+ // profile with property placeholders resolved or left unchanged (if there's no property value available,
+ // so property placeholders are preserved - like ${karaf.base})
Profile overallEffective = Profiles.getEffective(overallOverlay, false);
+ if (writeProfiles) {
+ Path profiles = etcDirectory.resolve("profiles");
+ LOGGER.info("Adding profiles to {}", homeDirectory.relativize(profiles));
+ allProfiles.forEach((id, profile) -> {
+ try {
+ Profiles.writeProfile(profiles, profile);
+ } catch (IOException e) {
+ LOGGER.warn("Problem writing profile {}: {}", id, e.getMessage());
+ }
+ });
+ }
+
manager = new CustomDownloadManager(resolver, executor, overallEffective, translatedUrls);
-// Hashtable<String, String> agentProps = new Hashtable<>(overallEffective.getConfiguration(ORG_OPS4J_PAX_URL_MVN_PID));
+// Hashtable<String, String> profileProps = new Hashtable<>(overallEffective.getConfiguration(ORG_OPS4J_PAX_URL_MVN_PID));
// final Map<String, String> properties = new HashMap<>();
// properties.put("karaf.default.repository", "system");
-// InterpolationHelper.performSubstitution(agentProps, properties::get, false, false, true);
+// InterpolationHelper.performSubstitution(profileProps, properties::get, false, false, true);
//
// Write config and system properties
//
+ LOGGER.info("Configuring etc/config.properties and etc/system.properties");
+
Path configPropertiesPath = etcDirectory.resolve("config.properties");
Properties configProperties = new Properties(configPropertiesPath.toFile());
configProperties.putAll(overallEffective.getConfig());
@@ -595,11 +908,13 @@ public class Builder {
// Download libraries
//
// TODO: handle karaf 2.x and 3.x libraries
- LOGGER.info("Downloading libraries");
downloader = manager.createDownloader();
+ LOGGER.info("Downloading libraries for generated profiles");
downloadLibraries(downloader, configProperties, overallEffective.getLibraries(), "");
+ LOGGER.info("Downloading additional libraries");
downloadLibraries(downloader, configProperties, libraries, "");
downloader.await();
+
// Reformat clauses
reformatClauses(configProperties, Constants.FRAMEWORK_SYSTEMPACKAGES_EXTRA);
reformatClauses(configProperties, Constants.FRAMEWORK_BOOTDELEGATION);
@@ -678,6 +993,65 @@ public class Builder {
installStage(installedProfile, allBootFeatures);
}
+ private MavenResolver createMavenResolver() {
+ Dictionary<String, String> props = new Hashtable<>();
+ if (offline) {
+ props.put(ORG_OPS4J_PAX_URL_MVN_PID + "offline", "true");
+ }
+ if (localRepository != null) {
+ props.put(ORG_OPS4J_PAX_URL_MVN_PID + ".localRepository", localRepository);
+ }
+ if (mavenRepositories != null) {
+ props.put(ORG_OPS4J_PAX_URL_MVN_PID + ".repositories", mavenRepositories);
+ }
+ MavenResolver resolver = MavenResolvers.createMavenResolver(props, ORG_OPS4J_PAX_URL_MVN_PID);
+ return resolverWrapper.apply(resolver);
+ }
+
+ /**
+ * Loads all profiles declared in profile URIs. These will be used in addition to generated
+ * <em>startup</em>, <em>boot</em> and <em>installed</em> profiles.
+ */
+ private Map<String, Profile> loadExternalProfiles(List<String> profilesUris) throws IOException, MultiException, InterruptedException {
+ Map<String, Profile> profiles = new LinkedHashMap<>();
+ for (String profilesUri : profilesUris) {
+ String uri = profilesUri;
+ if (uri.startsWith("jar:") && uri.contains("!/")) {
+ uri = uri.substring("jar:".length(), uri.indexOf("!/"));
+ }
+ if (!uri.startsWith("file:")) {
+ Downloader downloader = manager.createDownloader();
+ downloader.download(uri, null);
+ downloader.await();
+ StreamProvider provider = manager.getProviders().get(uri);
+ profilesUri = profilesUri.replace(uri, provider.getFile().toURI().toString());
+ }
+ URI profileURI = URI.create(profilesUri);
+ Path profilePath;
+ try {
+ profilePath = Paths.get(profileURI);
+ } catch (FileSystemNotFoundException e) {
+ // file system does not exist, try to create it
+ FileSystem fs = FileSystems.newFileSystem(profileURI, new HashMap<>(), Builder.class.getClassLoader());
+ profilePath = fs.provider().getPath(profileURI);
+ }
+ profiles.putAll(Profiles.loadProfiles(profilePath));
+ // Handle blacklisted profiles
+ if (!blacklistedProfiles.isEmpty()) {
+ if (blacklistPolicy == BlacklistPolicy.Discard) {
+ // Override blacklisted profiles with empty ones
+ for (String profile : blacklistedProfiles) {
+ profiles.put(profile, ProfileBuilder.Factory.create(profile).getProfile());
+ }
+ } else {
+ // Remove profiles completely
+ profiles.keySet().removeAll(blacklistedProfiles);
+ }
+ }
+ }
+ return profiles;
+ }
+
private void reformatClauses(Properties config, String key) {
String val = config.getProperty(key);
if (val != null && !val.isEmpty()) {
@@ -898,8 +1272,8 @@ public class Builder {
Map<String, List<String>> prereqs = new HashMap<>();
prereqs.put("blueprint:", Arrays.asList("deployer", "aries-blueprint"));
prereqs.put("spring:", Arrays.asList("deployer", "spring"));
- prereqs.put("wrap:", Arrays.asList("wrap"));
- prereqs.put("war:", Arrays.asList("war"));
+ prereqs.put("wrap:", Collections.singletonList("wrap"));
+ prereqs.put("war:", Collections.singletonList("war"));
ArtifactInstaller installer = new ArtifactInstaller(systemDirectory, downloader, blacklistedBundles);
for (String location : locations) {
installer.installArtifact(location);
@@ -1100,6 +1474,12 @@ public class Builder {
return startupEffective;
}
+ /**
+ * Gets a list of objects (bundle URIs, profile IDs, feature IDs) configured for given stage
+ * @param stage
+ * @param data
+ * @return
+ */
private List<String> getStaged(Stage stage, Map<String, Stage> data) {
List<String> staged = new ArrayList<>();
for (String s : data.keySet()) {
@@ -1110,6 +1490,13 @@ public class Builder {
return staged;
}
+ /**
+ * Gets a list of features XML repository URIs configured for given stage. There's one special rule - startup
+ * repositories are added as boot repositories as well.
+ * @param stage
+ * @param data
+ * @return
+ */
private List<String> getStagedRepositories(Stage stage, Map<String, RepositoryInfo> data) {
List<String> staged = new ArrayList<>();
for (String s : data.keySet()) {
@@ -1128,8 +1515,7 @@ public class Builder {
final List<String> blacklist = new ArrayList<>();
blacklist.addAll(blacklistedBundles);
blacklist.addAll(blacklistedFeatures);
- final List<String> blacklistRepos = new ArrayList<>();
- blacklistRepos.addAll(blacklistedRepositories);
+ final List<String> blacklistRepos = new ArrayList<>(blacklistedRepositories);
final Blacklist blacklistOther = new Blacklist(blacklist);
final Blacklist repoBlacklist = new Blacklist(blacklistRepos);
for (String repository : repositories) {
@@ -1138,7 +1524,7 @@ public class Builder {
public void downloaded(final StreamProvider provider) throws Exception {
String url = provider.getUrl();
if (repoBlacklist.isRepositoryBlacklisted(url)) {
- LOGGER.info(" feature repository " + url + " is blacklisted");
+ LOGGER.info(" feature repository " + url + " is blacklisted");
return;
}
synchronized (loaded) {
@@ -1170,15 +1556,31 @@ public class Builder {
return loaded;
}
- private Profile generateProfile(Stage stage, Map<String, Stage> profiles, Map<String, RepositoryInfo> repositories, Map<String, Stage> features, Map<String, Stage> bundles) {
- Profile profile = ProfileBuilder.Factory.create(UUID.randomUUID().toString())
- .setParents(getStaged(stage, profiles))
+ /**
+ * Generate internal profile (for the purpose of custom assembly builder) for given <code>stage</code>.
+ * @param stage a {@link Stage} for which the profile is being generated
+ * @param parentProfiles all profiles for given stage will be used as parent profiles
+ * @param repositories repositories to use in generated profile
+ * @param features features to declare in generated profile
+ * @param bundles bundles to declare in generated profile
+ * @return
+ */
+ private Profile generateProfile(Stage stage, Map<String, Stage> parentProfiles, Map<String, RepositoryInfo> repositories, Map<String, Stage> features, Map<String, Stage> bundles) {
+ String name = "generated-" + stage.name().toLowerCase();
+ List<String> stagedParentProfiles = getStaged(stage, parentProfiles);
+
+ if (stagedParentProfiles.isEmpty()) {
+ LOGGER.info("Generating {} profile", name);
+ } else {
+ LOGGER.info("Generating {} profile with parents: {}", name, stagedParentProfiles.stream().collect(Collectors.joining(", ")));
+ }
+
+ return ProfileBuilder.Factory.create(name)
+ .setParents(stagedParentProfiles)
.setRepositories(getStagedRepositories(stage, repositories))
.setFeatures(getStaged(stage, features))
.setBundles(getStaged(stage, bundles))
.getProfile();
- allProfiles.put(profile.getId(), profile);
- return profile;
}
private Map<String, Integer> resolve(
@@ -1250,7 +1652,7 @@ public class Builder {
private BundleRevision getSystemBundle() throws Exception {
Path configPropPath = etcDirectory.resolve("config.properties");
Properties configProps = PropertiesLoader.loadPropertiesOrFail(configPropPath.toFile());
- configProps.put("java.specification.version", javase);
+ configProps.put("java.specification.version", javase.version);
configProps.substitute();
Attributes attributes = new Attributes();
@@ -1258,14 +1660,18 @@ public class Builder {
attributes.putValue(Constants.BUNDLE_SYMBOLICNAME, "system.bundle");
attributes.putValue(Constants.BUNDLE_VERSION, "0.0.0");
- String exportPackages = configProps.getProperty(Constants.FRAMEWORK_SYSTEMPACKAGES);
+ String exportPackages = configProps.getProperty(Constants.FRAMEWORK_SYSTEMPACKAGES, "");
+ if ("".equals(exportPackages.trim())) {
+ throw new IllegalArgumentException("\"org.osgi.framework.system.packages\" property should specify system bundle" +
+ " packages. It can't be empty, please check etc/config.properties of the assembly.");
+ }
if (configProps.containsKey(Constants.FRAMEWORK_SYSTEMPACKAGES_EXTRA)) {
exportPackages += "," + configProps.getProperty(Constants.FRAMEWORK_SYSTEMPACKAGES_EXTRA);
}
exportPackages = exportPackages.replaceAll(",\\s*,", ",");
attributes.putValue(Constants.EXPORT_PACKAGE, exportPackages);
- String systemCaps = configProps.getProperty(Constants.FRAMEWORK_SYSTEMCAPABILITIES);
+ String systemCaps = configProps.getProperty(Constants.FRAMEWORK_SYSTEMCAPABILITIES, "");
attributes.putValue(Constants.PROVIDE_CAPABILITY, systemCaps);
final Hashtable<String, String> headers = new Hashtable<>();
diff --git a/profile/src/main/java/org/apache/karaf/profile/command/ProfileDisplay.java b/profile/src/main/java/org/apache/karaf/profile/command/ProfileDisplay.java
index e6d4619..d92bf8a 100644
--- a/profile/src/main/java/org/apache/karaf/profile/command/ProfileDisplay.java
+++ b/profile/src/main/java/org/apache/karaf/profile/command/ProfileDisplay.java
@@ -54,7 +54,7 @@ public class ProfileDisplay implements Action {
private ProfileService profileService;
@Override
- public Object execute() throws Exception {
+ public Object execute() {
displayProfile(profileService.getRequiredProfile(profileId));
return null;
}
@@ -87,11 +87,11 @@ public class ProfileDisplay implements Action {
Map<String, Map<String, Object>> configuration = new HashMap<>(profile.getConfigurations());
Map<String, byte[]> resources = profile.getFileConfigurations();
- Map<String,Object> agentConfiguration = profile.getConfiguration(Profile.INTERNAL_PID);
- List<String> agentProperties = new ArrayList<>();
+ Map<String,Object> profileConfiguration = profile.getConfiguration(Profile.INTERNAL_PID);
+ List<String> profileProperties = new ArrayList<>();
List<String> systemProperties = new ArrayList<>();
List<String> configProperties = new ArrayList<>();
- for (Map.Entry<String, Object> entry : agentConfiguration.entrySet()) {
+ for (Map.Entry<String, Object> entry : profileConfiguration.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
if (value instanceof String && ((String) value).contains(",")) {
@@ -107,7 +107,7 @@ public class ProfileDisplay implements Action {
else if (!key.startsWith("feature.") && !key.startsWith("repository") &&
!key.startsWith("bundle.") && !key.startsWith("fab.") &&
!key.startsWith("override.") && !key.startsWith("attribute.")) {
- agentProperties.add(" " + key + " = " + value);
+ profileProperties.add(" " + key + " = " + value);
}
}
@@ -131,8 +131,8 @@ public class ProfileDisplay implements Action {
printConfigList("Overrides : ", output, profile.getOverrides());
}
- if (agentProperties.size() > 0) {
- printConfigList("Agent Properties : ", output, agentProperties);
+ if (profileProperties.size() > 0) {
+ printConfigList("Profile Properties : ", output, profileProperties);
}
if (systemProperties.size() > 0) {
diff --git a/profile/src/main/java/org/apache/karaf/profile/command/ProfileEdit.java b/profile/src/main/java/org/apache/karaf/profile/command/ProfileEdit.java
index 596731a..6e52086 100644
--- a/profile/src/main/java/org/apache/karaf/profile/command/ProfileEdit.java
+++ b/profile/src/main/java/org/apache/karaf/profile/command/ProfileEdit.java
@@ -26,6 +26,7 @@ import java.util.Map;
import org.apache.karaf.profile.Profile;
import org.apache.karaf.profile.ProfileBuilder;
+import org.apache.karaf.profile.ProfileConstants;
import org.apache.karaf.profile.ProfileService;
import org.apache.karaf.shell.api.action.Action;
import org.apache.karaf.shell.api.action.Argument;
@@ -48,15 +49,6 @@ public class ProfileEdit implements Action {
private static final Logger LOGGER = LoggerFactory.getLogger(ProfileEdit.class);
- static final String FEATURE_PREFIX = "feature.";
- static final String REPOSITORY_PREFIX = "repository.";
- static final String BUNDLE_PREFIX = "bundle.";
- static final String OVERRIDE_PREFIX = "override.";
- static final String CONFIG_PREFIX = "config.";
- static final String SYSTEM_PREFIX = "system.";
- static final String LIB_PREFIX = "lib.";
- static final String ENDORSED_PREFIX = "endorsed.";
- static final String EXT_PREFIX = "ext.";
static final String DELIMITER = ",";
static final String PID_KEY_SEPARATOR = "/";
@@ -156,15 +148,15 @@ public class ProfileEdit implements Action {
}
if (libs != null && libs.length > 0) {
editInLine = true;
- handleLibraries(builder, libs, profile, "lib", LIB_PREFIX);
+ handleLibraries(builder, libs, profile, "lib", ProfileConstants.LIB_PREFIX);
}
if (endorsed != null && endorsed.length > 0) {
editInLine = true;
- handleLibraries(builder, endorsed, profile, "endorsed lib", ENDORSED_PREFIX);
+ handleLibraries(builder, endorsed, profile, "endorsed lib", ProfileConstants.ENDORSED_PREFIX);
}
if (extension != null && extension.length > 0) {
editInLine = true;
- handleLibraries(builder, extension, profile, "extension lib", EXT_PREFIX);
+ handleLibraries(builder, extension, profile, "extension lib", ProfileConstants.EXT_PREFIX);
}
if (bundles != null && bundles.length > 0) {
editInLine = true;
@@ -215,7 +207,7 @@ public class ProfileEdit implements Action {
} else {
System.out.println("Adding feature:" + feature + " to profile:" + profile.getId());
}
- updateConfig(conf, FEATURE_PREFIX + feature.replace('/', '_'), feature, set, delete);
+ updateConfig(conf, ProfileConstants.FEATURE_PREFIX + feature.replace('/', '_'), feature, set, delete);
builder.addConfiguration(Profile.INTERNAL_PID, conf);
}
}
@@ -231,7 +223,7 @@ public class ProfileEdit implements Action {
} else if (delete) {
System.out.println("Deleting feature repository:" + repositoryURI + " from profile:" + profile.getId());
}
- updateConfig(conf, REPOSITORY_PREFIX + repositoryURI.replace('/', '_'), repositoryURI, set, delete);
+ updateConfig(conf, ProfileConstants.REPOSITORY_PREFIX + repositoryURI.replace('/', '_'), repositoryURI, set, delete);
}
builder.addConfiguration(Profile.INTERNAL_PID, conf);
}
@@ -269,7 +261,7 @@ public class ProfileEdit implements Action {
} else if (delete) {
System.out.println("Deleting bundle:" + bundle + " from profile:" + profile.getId());
}
- updateConfig(conf, BUNDLE_PREFIX + bundle.replace('/', '_'), bundle, set, delete);
+ updateConfig(conf, ProfileConstants.BUNDLE_PREFIX + bundle.replace('/', '_'), bundle, set, delete);
}
builder.addConfiguration(Profile.INTERNAL_PID, conf);
}
@@ -287,7 +279,7 @@ public class ProfileEdit implements Action {
} else if (delete) {
System.out.println("Deleting override:" + override + " from profile:" + profile.getId());
}
- updateConfig(conf, OVERRIDE_PREFIX + override.replace('/', '_'), override, set, delete);
+ updateConfig(conf, ProfileConstants.OVERRIDE_PREFIX + override.replace('/', '_'), override, set, delete);
}
builder.addConfiguration(Profile.INTERNAL_PID, conf);
}
@@ -375,7 +367,7 @@ public class ProfileEdit implements Action {
} else {
System.out.println("Removing value:" + value + " key:" + key + " from system properties and profile:" + profile.getId());
}
- updatedDelimitedList(conf, SYSTEM_PREFIX + key, value, delimiter, set, delete, append, remove);
+ updatedDelimitedList(conf, ProfileConstants.SYSTEM_PREFIX + key, value, delimiter, set, delete, append, remove);
}
}
builder.addConfiguration(Profile.INTERNAL_PID, conf);
@@ -400,7 +392,7 @@ public class ProfileEdit implements Action {
} else if (set) {
System.out.println("Setting value:" + value + " key:" + key + " from config properties and profile:" + profile.getId());
}
- updatedDelimitedList(conf, CONFIG_PREFIX + key, value, delimiter, set, delete, append, remove);
+ updatedDelimitedList(conf, ProfileConstants.CONFIG_PREFIX + key, value, delimiter, set, delete, append, remove);
}
}
builder.addConfiguration(Profile.INTERNAL_PID, conf);
diff --git a/profile/src/main/java/org/apache/karaf/profile/impl/ProfileBuilderImpl.java b/profile/src/main/java/org/apache/karaf/profile/impl/ProfileBuilderImpl.java
index 532caf6..10331bd 100644
--- a/profile/src/main/java/org/apache/karaf/profile/impl/ProfileBuilderImpl.java
+++ b/profile/src/main/java/org/apache/karaf/profile/impl/ProfileBuilderImpl.java
@@ -38,8 +38,6 @@ import static org.apache.karaf.profile.impl.ProfileImpl.ConfigListType;
*/
public final class ProfileBuilderImpl implements ProfileBuilder {
- private static final String PARENTS_ATTRIBUTE_KEY = Profile.ATTRIBUTE_PREFIX + Profile.PARENTS;
-
private String profileId;
private Map<String, byte[]> fileMapping = new HashMap<>();
private boolean isOverlay;
@@ -60,7 +58,7 @@ public final class ProfileBuilderImpl implements ProfileBuilder {
@Override
public List<String> getParents() {
Map<String, Object> config = getConfigurationInternal(Profile.INTERNAL_PID);
- String pspec = (String) config.get(PARENTS_ATTRIBUTE_KEY);
+ String pspec = (String) config.get(Profile.PARENTS);
String[] parentIds = pspec != null ? pspec.split(" ") : new String[0];
return Arrays.asList(parentIds);
}
@@ -102,9 +100,9 @@ public final class ProfileBuilderImpl implements ProfileBuilder {
private void updateParentsAttribute(Collection<String> parentIds) {
Map<String, Object> config = getConfigurationInternal(Profile.INTERNAL_PID);
- config.remove(PARENTS_ATTRIBUTE_KEY);
+ config.remove(Profile.PARENTS);
if (parentIds.size() > 0) {
- config.put(PARENTS_ATTRIBUTE_KEY, parentsAttributeValue(parentIds));
+ config.put(Profile.PARENTS, parentsAttributeValue(parentIds));
}
addConfiguration(Profile.INTERNAL_PID, config);
}
@@ -195,49 +193,49 @@ public final class ProfileBuilderImpl implements ProfileBuilder {
@Override
public ProfileBuilder setBundles(List<String> values) {
- addAgentConfiguration(ConfigListType.BUNDLES, values);
+ addProfileConfiguration(ConfigListType.BUNDLES, values);
return this;
}
@Override
public ProfileBuilder addBundle(String value) {
- addAgentConfiguration(ConfigListType.BUNDLES, value);
+ addProfileConfiguration(ConfigListType.BUNDLES, value);
return this;
}
@Override
public ProfileBuilder setFeatures(List<String> values) {
- addAgentConfiguration(ConfigListType.FEATURES, values);
+ addProfileConfiguration(ConfigListType.FEATURES, values);
return this;
}
@Override
public ProfileBuilder addFeature(String value) {
- addAgentConfiguration(ConfigListType.FEATURES, value);
+ addProfileConfiguration(ConfigListType.FEATURES, value);
return this;
}
@Override
public ProfileBuilder setRepositories(List<String> values) {
- addAgentConfiguration(ConfigListType.REPOSITORIES, values);
+ addProfileConfiguration(ConfigListType.REPOSITORIES, values);
return this;
}
@Override
public ProfileBuilder addRepository(String value) {
- addAgentConfiguration(ConfigListType.REPOSITORIES, value);
+ addProfileConfiguration(ConfigListType.REPOSITORIES, value);
return this;
}
@Override
public ProfileBuilder setOverrides(List<String> values) {
- addAgentConfiguration(ConfigListType.OVERRIDES, values);
+ addProfileConfiguration(ConfigListType.OVERRIDES, values);
return this;
}
@Override
public ProfileBuilder setOptionals(List<String> values) {
- addAgentConfiguration(ConfigListType.OPTIONALS, values);
+ addProfileConfiguration(ConfigListType.OPTIONALS, values);
return this;
}
@@ -267,7 +265,7 @@ public final class ProfileBuilderImpl implements ProfileBuilder {
return null;
}
- private void addAgentConfiguration(ConfigListType type, List<String> values) {
+ private void addProfileConfiguration(ConfigListType type, List<String> values) {
String prefix = type + ".";
Map<String, Object> config = getConfigurationInternal(Profile.INTERNAL_PID);
for (String key : new ArrayList<>(config.keySet())) {
@@ -281,7 +279,7 @@ public final class ProfileBuilderImpl implements ProfileBuilder {
addConfiguration(Profile.INTERNAL_PID, config);
}
- private void addAgentConfiguration(ConfigListType type, String value) {
+ private void addProfileConfiguration(ConfigListType type, String value) {
String prefix = type + ".";
Map<String, Object> config = getConfigurationInternal(Profile.INTERNAL_PID);
config.put(prefix + value, value);
diff --git a/profile/src/main/java/org/apache/karaf/profile/impl/ProfileImpl.java b/profile/src/main/java/org/apache/karaf/profile/impl/ProfileImpl.java
index bcd1b03..c8707ea 100644
--- a/profile/src/main/java/org/apache/karaf/profile/impl/ProfileImpl.java
+++ b/profile/src/main/java/org/apache/karaf/profile/impl/ProfileImpl.java
@@ -37,7 +37,7 @@ import static org.apache.karaf.profile.impl.Utils.assertTrue;
*/
final class ProfileImpl implements Profile {
- private static final Pattern ALLOWED_PROFILE_NAMES_PATTERN = Pattern.compile("^[A-Za-z0-9]+[\\.A-Za-z0-9_-]*$");
+ private static final Pattern ALLOWED_PROFILE_NAMES_PATTERN = Pattern.compile("^[A-Za-z0-9]+[.A-Za-z0-9_-]*$");
private final String profileId;
private final Map<String, String> attributes;
@@ -53,7 +53,7 @@ final class ProfileImpl implements Profile {
assertNotNull(profileId, "profileId is null");
assertNotNull(parents, "parents is null");
assertNotNull(fileConfigs, "fileConfigs is null");
- assertTrue(ALLOWED_PROFILE_NAMES_PATTERN.matcher(profileId).matches(), "Profile id '" + profileId + "' is invalid. Profile id must be: lower-case letters, numbers, and . _ or - characters");
+ assertTrue(ALLOWED_PROFILE_NAMES_PATTERN.matcher(profileId).matches(), "Profile id '" + profileId + "' is invalid. Profile id must be: upper-case or lower-case letters, numbers, and . _ or - characters");
this.profileId = profileId;
this.isOverlay = isOverlay;
@@ -72,14 +72,10 @@ final class ProfileImpl implements Profile {
}
}
- // Attributes are agent configuration with prefix 'attribute.'
+ // Attributes are profile configuration properties with prefix "attribute." contained in "profile" PID
attributes = getPrefixedMap(ATTRIBUTE_PREFIX);
}
- public String getId() {
- return profileId;
- }
-
@Override
public Map<String, String> getAttributes() {
return Collections.unmodifiableMap(attributes);
@@ -95,12 +91,17 @@ final class ProfileImpl implements Profile {
return getPrefixedMap(SYSTEM_PREFIX);
}
+ @Override
+ public String getId() {
+ return profileId;
+ }
+
private Map<String, String> getPrefixedMap(String prefix) {
Map<String, String> map = new HashMap<>();
- Map<String, Object> agentConfig = configurations.get(Profile.INTERNAL_PID);
- if (agentConfig != null) {
+ Map<String, Object> profileConfig = configurations.get(Profile.INTERNAL_PID);
+ if (profileConfig != null) {
int prefixLength = prefix.length();
- for (Entry<String, Object> entry : agentConfig.entrySet()) {
+ for (Entry<String, Object> entry : profileConfig.entrySet()) {
String key = entry.getKey();
if (key.startsWith(prefix)) {
map.put(key.substring(prefixLength), entry.getValue().toString());
@@ -111,8 +112,8 @@ final class ProfileImpl implements Profile {
}
@Override
- public List<String> getLibraries() {
- return getContainerConfigList(ConfigListType.LIBRARIES);
+ public List<String> getParentIds() {
+ return Collections.unmodifiableList(parents);
}
@Override
@@ -131,6 +132,26 @@ final class ProfileImpl implements Profile {
}
@Override
+ public List<String> getLibraries() {
+ return getContainerConfigList(ConfigListType.LIBRARIES);
+ }
+
+ @Override
+ public List<String> getBootLibraries() {
+ return getContainerConfigList(ConfigListType.BOOT_LIBRARIES);
+ }
+
+ @Override
+ public List<String> getEndorsedLibraries() {
+ return getContainerConfigList(ConfigListType.ENDORSED_LIBRARIES);
+ }
+
+ @Override
+ public List<String> getExtLibraries() {
+ return getContainerConfigList(ConfigListType.EXT_LIBRARIES);
+ }
+
+ @Override
public List<String> getOverrides() {
return getContainerConfigList(ConfigListType.OVERRIDES);
}
@@ -141,26 +162,27 @@ final class ProfileImpl implements Profile {
}
@Override
- public List<String> getParentIds() {
- return Collections.unmodifiableList(parents);
+ public boolean isOverlay() {
+ return isOverlay;
}
@Override
public boolean isAbstract() {
- return parseBoolean(getAttributes().get(ABSTRACT));
+ return parseBoolean(attributes.get(ABSTRACT));
}
@Override
public boolean isHidden() {
- return parseBoolean(getAttributes().get(HIDDEN));
+ return parseBoolean(attributes.get(HIDDEN));
}
private Boolean parseBoolean(Object obj) {
- return obj instanceof Boolean ? (Boolean) obj : Boolean.parseBoolean(obj.toString());
+ return obj instanceof Boolean ? (Boolean) obj : obj != null && Boolean.parseBoolean(obj.toString());
}
- public boolean isOverlay() {
- return isOverlay;
+ @Override
+ public Set<String> getConfigurationFileNames() {
+ return Collections.unmodifiableSet(fileConfigurations.keySet());
}
@Override
@@ -169,15 +191,11 @@ final class ProfileImpl implements Profile {
}
@Override
- public Set<String> getConfigurationFileNames() {
- return Collections.unmodifiableSet(fileConfigurations.keySet());
- }
-
- @Override
public byte[] getFileConfiguration(String fileName) {
return fileConfigurations.get(fileName);
}
+ @Override
public Map<String, Map<String, Object>> getConfigurations() {
return Collections.unmodifiableMap(configurations);
}
@@ -230,13 +248,16 @@ final class ProfileImpl implements Profile {
@Override
public String toString() {
- return "Profile[id=" + profileId + ",attrs=" + getAttributes() + "]";
+ return "Profile[id=" + profileId + ", attrs=" + getAttributes() + "]";
}
enum ConfigListType {
BUNDLES("bundle"),
FEATURES("feature"),
LIBRARIES("library"),
+ BOOT_LIBRARIES("boot"),
+ ENDORSED_LIBRARIES("endorsed"),
+ EXT_LIBRARIES("ext"),
OPTIONALS("optional"),
OVERRIDES("override"),
REPOSITORIES("repository");
@@ -251,4 +272,5 @@ final class ProfileImpl implements Profile {
return value;
}
}
+
}
diff --git a/profile/src/main/java/org/apache/karaf/profile/impl/Profiles.java b/profile/src/main/java/org/apache/karaf/profile/impl/Profiles.java
index 9fbfdcc..c3843d6 100644
--- a/profile/src/main/java/org/apache/karaf/profile/impl/Profiles.java
+++ b/profile/src/main/java/org/apache/karaf/profile/impl/Profiles.java
@@ -38,16 +38,29 @@ import org.apache.karaf.profile.ProfileBuilder;
import static org.apache.karaf.profile.impl.Utils.assertNotNull;
+/**
+ * Static utilities to work with {@link Profile profiles}.
+ */
public final class Profiles {
public static final String PROFILE_FOLDER_SUFFIX = ".profile";
+ /**
+ * <p>Loads profiles from given directory path. A profile is represented as directory with <code>.profile</code>
+ * extension. Subdirectories constitute part of {@linl Profile#getId} - directory separators are changed to
+ * <code>-</code>.</p>
+ * <p>For example, profile contained in directory <code>mq/broker/standalone.profile</code> will have
+ * id = <code>mq-broker-standalone</code>.</p>
+ * @param root
+ * @return
+ * @throws IOException
+ */
public static Map<String, Profile> loadProfiles(final Path root) throws IOException {
final Map<String, Profile> profiles = new HashMap<>();
Files.walkFileTree(root, new SimpleFileVisitor<Path>() {
ProfileBuilder builder;
@Override
- public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
+ public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
Path fileName = dir.getFileName();
if (fileName != null && (fileName.toString().endsWith(PROFILE_FOLDER_SUFFIX)
|| fileName.toString().endsWith(PROFILE_FOLDER_SUFFIX + "/"))) {
@@ -87,6 +100,12 @@ public final class Profiles {
return profiles;
}
+ /**
+ * Deletes profile by given {@link Profile#getId()} from <code>root</code> path.
+ * @param root
+ * @param id
+ * @throws IOException
+ */
public static void deleteProfile(Path root, String id) throws IOException {
Path path = root.resolve(id.replaceAll("-", root.getFileSystem().getSeparator()) + PROFILE_FOLDER_SUFFIX);
if (Files.isDirectory(path)) {
@@ -105,6 +124,13 @@ public final class Profiles {
}
}
+ /**
+ * Writes given {@link Profile} under a path specified as <code>root</code>. Directory name to store a profile is
+ * derived from {@link Profile#getId()}
+ * @param root
+ * @param profile
+ * @throws IOException
+ */
public static void writeProfile(Path root, Profile profile) throws IOException {
Path path = root.resolve(profile.getId().replaceAll("-", root.getFileSystem().getSeparator()) + PROFILE_FOLDER_SUFFIX);
Files.createDirectories(path);
@@ -113,10 +139,28 @@ public final class Profiles {
}
}
+ /**
+ * <p>Gets an <em>overlay</em> profile for given <code>profile</code>, where passed in map of additional profiles
+ * is searched for possible parent profiles of given <code>profile</code>.</p>
+ * @param profile
+ * @param profiles
+ * @return
+ */
public static Profile getOverlay(Profile profile, Map<String, Profile> profiles) {
return getOverlay(profile, profiles, null);
}
+ /**
+ * <p>Gets an <em>overlay</em> profile for given <code>profile</code>, where passed in map of additional profiles
+ * is searched for possible parent profiles of given <code>profile</code>.</p>
+ * <p><code>environment</code> may be used to select different <em>variants</em> of profile configuration files.
+ * For example, if <code>environment</code> is specified, configuration for <code>my.pid</code> PID will be read
+ * from <code>my.pid.cfg#<environment></code>.</p>
+ * @param profile
+ * @param profiles
+ * @param environment
+ * @return
+ */
public static Profile getOverlay(Profile profile, Map<String, Profile> profiles, String environment) {
assertNotNull(profile, "profile is null");
assertNotNull(profile, "profiles is null");
@@ -130,22 +174,49 @@ public final class Profiles {
}
}
+ /**
+ * Gets an <code>effective</code> profile with single property placeholder resolver for <code>${profile:xxx}</code>
+ * placeholders and with <code>finalSubstitution</code> set to <code>true</code>.
+ * @param profile
+ * @return
+ */
public static Profile getEffective(final Profile profile) {
- return getEffective(profile,
- true);
+ return getEffective(profile, true);
}
+ /**
+ * Gets an <code>effective</code> profile with single property placeholder resolver for <code>${profile:xxx}</code>
+ * placeholders.
+ * @param profile
+ * @param finalSubstitution
+ * @return
+ */
public static Profile getEffective(final Profile profile, boolean finalSubstitution) {
return getEffective(profile,
Collections.singleton(new PlaceholderResolvers.ProfilePlaceholderResolver()),
finalSubstitution);
}
+ /**
+ * Gets an <code>effective</code> profile with <code>finalSubstitution</code> set to <code>true</code>.
+ * @param profile
+ * @param resolvers
+ * @return
+ */
public static Profile getEffective(final Profile profile,
final Collection<PlaceholderResolver> resolvers) {
return getEffective(profile, resolvers, true);
}
+ /**
+ * <p>Gets an <em>effective</em> profile for given <code>profile</code>. Effective profile has all property
+ * placeholders resolved. When <code>finalSubstitution</code> is <code>true</code>, placeholders that can't
+ * be resolved are replaced with empty strings. When it's <code>false</code>, placeholders are left unchanged.</p>
+ * @param profile
+ * @param resolvers
+ * @param finalSubstitution
+ * @return
+ */
public static Profile getEffective(final Profile profile,
final Collection<PlaceholderResolver> resolvers,
boolean finalSubstitution) {
@@ -208,6 +279,28 @@ public final class Profiles {
return builder.getProfile();
}
+ /**
+ * <p>Helper internal class to configure {@link ProfileBuilder} used to create an <em>overlay</em> profile.</p>
+ * <p>There are strict rules built on a concept of profiles being <em>containers of file configurations</em>.
+ * Each profile may contain files with the same name. Profiles may be set in multi-parent - child relationship.
+ * Such graph of profiles is searched in depth-first fashion, while child (being a root of the graph) has
+ * highest priority.</p>
+ * <p>Files from higher-priority profile override files from parent profiles. Special case are PID files (with
+ * {@link Profile#PROPERTIES_SUFFIX} extension). These files are not simply taken from child profiles. Child
+ * profiles may have own version of given PID configuration file, but these files are overwritten at property
+ * level.</p>
+ * <p>For example, if parent profile specifies:<pre>
+ * property1 = v1
+ * property2 = v2
+ * </pre> and child profile specifies:<pre>
+ * property1 = v1a
+ * property3 = v3a
+ * </pre>an <em>overlay</em> profile for child profile uses:<pre>
+ * property1 = v1a
+ * property2 = v2
+ * property3 = v3a
+ * </pre></p>
+ */
static private class OverlayOptionsProvider {
private final Map<String, Profile> profiles;
diff --git a/profile/src/test/java/org/apache/karaf/profile/impl/ProfilesTest.java b/profile/src/test/java/org/apache/karaf/profile/impl/ProfilesTest.java
index 056332c..5a1e886 100644
--- a/profile/src/test/java/org/apache/karaf/profile/impl/ProfilesTest.java
+++ b/profile/src/test/java/org/apache/karaf/profile/impl/ProfilesTest.java
@@ -16,17 +16,65 @@
*/
package org.apache.karaf.profile.impl;
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
+import java.util.LinkedHashMap;
import java.util.Map;
+import java.util.UUID;
import org.apache.karaf.profile.Profile;
import org.apache.karaf.profile.ProfileBuilder;
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.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
public class ProfilesTest {
+ public static Logger LOG = LoggerFactory.getLogger(ProfilesTest.class);
+
+ @Test
+ public void testProfilesApi() throws IOException {
+ ProfileBuilder builder = ProfileBuilder.Factory.create("my-simple-profile");
+ builder.addParents(Collections.emptyList());
+ builder.addAttribute("attr1", "val1");
+ builder.addBundle("mvn:commons-everything/commons-everything/42");
+ builder.addConfiguration("my.pid", "a1", "v1${profile:my.pid2/a2}");
+ builder.addConfiguration("my.pid", "a2", "v1${profile:my.pid2/a3}");
+ builder.addFeature("feature1");
+ builder.addFileConfiguration("my.pid2.txt", "hello!".getBytes("UTF-8"));
+ builder.addFileConfiguration("my.pid2.cfg", "a2=v2".getBytes("UTF-8"));
+ builder.addRepository("mvn:my/repository/1/xml/features");
+ builder.setOptionals(Arrays.asList("mvn:g/a/1", "mvn:g/a/2"));
+ builder.setOverrides(Arrays.asList("mvn:g/a/4", "mvn:g/a/3"));
+ Profile profile = builder.getProfile();
+ LOG.info("Profile: {}", profile.toString());
+ LOG.info("Config: {}", profile.getConfig());
+ LOG.info("Libraries: {}", profile.getLibraries());
+ LOG.info("System: {}", profile.getSystem());
+ LOG.info("Configurations: {}", profile.getConfigurations());
+ LOG.info("ConfigurationFileNames: {}", profile.getConfigurationFileNames());
+ LOG.info("FileConfigurations: {}", profile.getFileConfigurations().keySet());
+
+ Profile effectiveProfile1 = Profiles.getEffective(profile, false);
+ Profile effectiveProfile2 = Profiles.getEffective(profile, true);
+ Map<String, Profile> profiles = new HashMap<>();
+ profiles.put("x", profile);
+ Profile overlayProfile = Profiles.getOverlay(profile, profiles);
+ Profiles.writeProfile(Paths.get("target/p-" + UUID.randomUUID().toString()), profile);
+ Profiles.writeProfile(Paths.get("target/ep1-" + UUID.randomUUID().toString()), effectiveProfile1);
+ Profiles.writeProfile(Paths.get("target/ep2-" + UUID.randomUUID().toString()), effectiveProfile2);
+ Profiles.writeProfile(Paths.get("target/op-" + UUID.randomUUID().toString()), overlayProfile);
+ }
+
@Test
public void testProfilePlaceholderResolver() {
Profile profile = ProfileBuilder.Factory.create("test")
@@ -87,4 +135,98 @@ public class ProfilesTest {
String outPid1 = new String(overlay.getFileConfiguration("pid1.cfg"));
assertEquals(String.format("%1$s%n%2$s%n","# My comment","foo = bar2"), outPid1);
}
+
+ @Test
+ public void overlayProfiles() {
+ Profile p1 = ProfileBuilder.Factory.create("p1")
+ .addAttribute("p1a1", "p1v1")
+ .addConfiguration("p1p1", "p1p1p1", "p1p1v1")
+ .addConfiguration("pp1", "pp1p1", "p1p1v1")
+ .getProfile();
+ Profile p2 = ProfileBuilder.Factory.create("p2")
+ .addAttribute("p2a1", "p2v1")
+ .addConfiguration("p2p1", "p2p1p1", "p2p1v1")
+ .addConfiguration("pp1", "pp1p1", "p2p1v1")
+ .getProfile();
+
+ Profile c1 = ProfileBuilder.Factory.create("c2")
+ .addParents(Arrays.asList("p1", "p2"))
+ .getProfile();
+
+ assertThat(c1.getAttributes().get("p1a1"), nullValue());
+ assertThat(c1.getAttributes().get("p2a1"), nullValue());
+ assertThat(c1.getConfigurations().size(), equalTo(1));
+ assertTrue(c1.getConfigurations().containsKey("profile"));
+
+ Map<String, Profile> parents = new LinkedHashMap<>();
+ parents.put("p1", p1);
+ parents.put("p2", p2);
+ Profile oc1 = Profiles.getOverlay(c1, parents);
+ assertThat(oc1.getAttributes().get("p1a1"), equalTo("p1v1"));
+ assertThat(oc1.getAttributes().get("p2a1"), equalTo("p2v1"));
+ assertThat(oc1.getConfigurations().size(), equalTo(4));
+ assertTrue(oc1.getConfigurations().containsKey("p1p1"));
+ assertTrue(oc1.getConfigurations().containsKey("p2p1"));
+ assertTrue(oc1.getConfigurations().containsKey("pp1"));
+ assertTrue(oc1.getConfigurations().containsKey("profile"));
+ }
+
+ @Test
+ public void inheritanceOrder() {
+ Profile gp1 = ProfileBuilder.Factory.create("gp1")
+ .addAttribute("a", "1")
+ .addFileConfiguration("f", new byte[] { 0x01 })
+ .addAttribute("b", "1")
+ .addAttribute("c", "1")
+ .addConfiguration("p", "p", "1")
+ .addConfiguration("p", "px", "1")
+ .getProfile();
+ Profile gp2 = ProfileBuilder.Factory.create("gp2")
+ .addAttribute("a", "2")
+ .addAttribute("c", "2")
+ .addFileConfiguration("f", new byte[] { 0x02 })
+ .addConfiguration("p", "p", "2")
+ .getProfile();
+ Profile p1 = ProfileBuilder.Factory.create("p1")
+ .addParents(Arrays.asList("gp1", "gp2"))
+ .addAttribute("a", "3")
+ .addFileConfiguration("f", new byte[] { 0x03 })
+ .addConfiguration("p", "p", "3")
+ .getProfile();
+ Profile p2 = ProfileBuilder.Factory.create("p2")
+ .addAttribute("a", "4")
+ .addAttribute("b", "4")
+ .addFileConfiguration("f", new byte[] { 0x04 })
+ .addConfiguration("p", "p", "4")
+ .getProfile();
+ Profile c = ProfileBuilder.Factory.create("p2")
+ .addParents(Arrays.asList("p1", "p2"))
+ .addAttribute("a", "5")
+ .addFileConfiguration("f", new byte[] { 0x05 })
+ .addConfiguration("p", "p", "5")
+ .getProfile();
+
+ Map<String, Profile> parents = new LinkedHashMap<>();
+ parents.put("gp1", gp1);
+ parents.put("gp2", gp2);
+ parents.put("p1", p1);
+ parents.put("p2", p2);
+
+ assertThat(Profiles.getOverlay(c, parents).getAttributes().get("a"), equalTo("5"));
+ assertThat(Profiles.getOverlay(c, parents).getAttributes().get("b"), equalTo("4"));
+ assertThat(Profiles.getOverlay(c, parents).getAttributes().get("c"), equalTo("2"));
+ assertThat(Profiles.getOverlay(c, parents).getConfiguration("p").get("p"), equalTo("5"));
+ assertThat(Profiles.getOverlay(c, parents).getConfiguration("p").get("px"), equalTo("1"));
+ assertThat(Profiles.getOverlay(c, parents).getFileConfiguration("f"), equalTo(new byte[] { 0x05 }));
+ }
+
+ @Test
+ public void overrides() {
+ Profile p = ProfileBuilder.Factory.create("p")
+ .setOverrides(Arrays.asList("a", "b"))
+ .getProfile();
+
+ assertThat(p.getConfiguration("profile").size(), equalTo(2));
+ }
+
}
diff --git a/tooling/karaf-maven-plugin/pom.xml b/tooling/karaf-maven-plugin/pom.xml
index 4ea78ab..1dc91d2 100644
--- a/tooling/karaf-maven-plugin/pom.xml
+++ b/tooling/karaf-maven-plugin/pom.xml
@@ -226,10 +226,6 @@
<resource>
<directory>${project.basedir}/src/main/resources</directory>
</resource>
- <resource>
- <directory>${project.basedir}/src/main/filtered-resources</directory>
- <filtering>true</filtering>
- </resource>
</resources>
<testResources>
<testResource>
diff --git a/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/AssemblyMojo.java b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/AssemblyMojo.java
index 6671614..f8d78b9 100644
--- a/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/AssemblyMojo.java
+++ b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/AssemblyMojo.java
@@ -20,6 +20,7 @@ package org.apache.karaf.tooling;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.io.File;
import java.io.FileInputStream;
@@ -30,10 +31,11 @@ import java.nio.file.attribute.PosixFilePermissions;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
-import java.util.Properties;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.stream.Collectors;
@@ -46,6 +48,7 @@ import org.apache.karaf.tooling.utils.MojoSupport;
import org.apache.karaf.tooling.utils.ReactorMavenResolver;
import org.apache.karaf.tools.utils.model.KarafPropertyEdits;
import org.apache.karaf.tools.utils.model.io.stax.KarafPropertyInstructionsModelStaxReader;
+import org.apache.karaf.util.Version;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
@@ -54,17 +57,21 @@ import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
+import org.apache.maven.project.MavenProject;
import org.eclipse.aether.repository.WorkspaceReader;
import org.osgi.framework.Constants;
+import org.osgi.framework.launch.FrameworkFactory;
/**
- * Creates a customized Karaf distribution by installing features and setting up
- * configuration files. The plugin gets features from feature.xml files and KAR
+ * <p>Creates a customized Karaf distribution by installing features and setting up
+ * configuration files.</p>
+ *
+ * <p>The plugin gets features from feature.xml files and KAR
* archives declared as dependencies or as files configured with the
- * featureRespositories parameter. It picks up other files, such as config files,
+ * [startup|boot|installed]Respositories parameters. It picks up other files, such as config files,
* from ${project.build.directory}/classes. Thus, a file in src/main/resources/etc
* will be copied by the resource plugin to ${project.build.directory}/classes/etc,
- * and then added to the assembly by this goal.
+ * and then added to the assembly by this goal.</p>
*/
@Mojo(name = "assembly", defaultPhase = LifecyclePhase.PACKAGE, requiresDependencyResolution = ResolutionScope.RUNTIME, threadSafe = true)
public class AssemblyMojo extends MojoSupport {
@@ -81,130 +88,260 @@ public class AssemblyMojo extends MojoSupport {
@Parameter(defaultValue = "${project.build.directory}/assembly")
protected File workDirectory;
- /**
- * Features configuration file (etc/org.apache.karaf.features.cfg).
+ /*
+ * There are three builder stages related to maven dependency scopes:
+ * - Stage.Startup : scope=compile
+ * - Stage.Boot : scope=runtime
+ * - Stage.Installed : scope=provided
+ * There's special category not related to stage - Blacklisted
+ *
+ * There are five kinds of artifacts/dependencies that may go into any of the above stages/categories/scopes:
+ * - kars: maven artifacts with "kar" type
+ * - repositories: maven artifacts with "features" classifier
+ * - features: Karaf feature names (name[/version])
+ * - bundles: maven artifacts with "jar" or "bundle" type
+ * - profiles: directories with Karaf 4 profiles
+ * (Not all artifacts/dependencies may be connected with every stage/category/scope.)
+ *
+ * Blacklisting:
+ * - kars: there are no blacklisted kars
+ * - repositories: won't be processed at all (also affects transitive repositories)
+ * - features: will be removed from JAXB model of features XML after loading
+ * - bundles: will be removed from features of JAXB model after loading
+ * - profiles: will be removed
+ *
+ * Stage.Startup:
+ * - bundles: will be put to etc/startup.properties
+ * - features: their bundles will be put to etc/startup.properties
+ * - repositories: will be used to resolve startup bundles/feature before adding them to etc/startup.properties
+ * - kars: unpacked to assembly, detected features XML repositories added as Stage.Startup repositories
+ *
+ * Stage.Boot:
+ * - bundles: special etc/<UUID>.xml features XML file will be created with <UUID> feature.
+ * etc/org.apacha.karaf.features.cfg will have this features XML file in featuresRepositories property and
+ * the feature itself in featuresBoot property
+ * - features: will be added to etc/org.apacha.karaf.features.cfg file, featuresBoot property
+ * also features from Stage.Startup will be used here.
+ * - repositories: will be added to etc/org.apacha.karaf.features.cfg file, featuresRepositories property
+ * also repositories from Stage.Startup will be used here.
+ * - kars: unpacked to assembly, detected features XML repositories added as Stage.Boot repositories
+ *
+ * Stage.Installed:
+ * - bundles: will be copied to system/
+ * - features: their bundles and config files will be copied to system/
+ * - repositories: will be used to find Stage.Installed features
+ * also repositories from Stage.Boot will be searched for Stage.Installed features
+ * - kars: unpacked to assembly, detected features XML repositories added as Stage.Installed repositories
*/
- @Parameter(defaultValue = "${project.build.directory}/assembly/etc/org.apache.karaf.features.cfg")
- protected File featuresCfgFile;
/**
- * startup.properties file.
+ * For given stage (startup, boot, install) if there are no stage-specific features and profiles, all features
+ * from stage-specific repositories will be used.
*/
- @Parameter(defaultValue = "${project.build.directory}/assembly/etc/startup.properties")
- protected File startupPropertiesFile;
+ @Parameter(defaultValue = "true")
+ protected boolean installAllFeaturesByDefault = true;
/**
- * Directory used during build to construction the Karaf system repository.
+ * An environment identifier that may be used to select different variant of PID configuration file, e.g.,
+ * <code>org.ops4j.pax.url.mvn.cfg#docker</code>.
*/
- @Parameter(defaultValue="${project.build.directory}/assembly/system")
- protected File systemDirectory;
+ @Parameter
+ private String environment;
/**
- * default start level for bundles in features that don't specify it.
+ * List of compile-scope features XML files to be used in startup stage (etc/startup.properties)
*/
@Parameter
- protected int defaultStartLevel = 30;
-
- @Parameter
private List<String> startupRepositories;
+ /**
+ * List of runtime-scope features XML files to be used in boot stage (etc/org.apache.karaf.features.cfg)
+ */
@Parameter
private List<String> bootRepositories;
+ /**
+ * List of provided-scope features XML files to be used in install stage
+ */
@Parameter
private List<String> installedRepositories;
-
+ /**
+ * List of blacklisted repository URIs. Blacklisted URI may use globs and version ranges. See
+ * {@link org.apache.karaf.features.LocationPattern}.
+ */
@Parameter
private List<String> blacklistedRepositories;
/**
- * List of features from runtime-scope features xml and kars to be installed into system and listed in startup.properties.
+ * List of features from compile-scope features XML files and KARs to be installed into system repo
+ * and listed in etc/startup.properties.
*/
@Parameter
private List<String> startupFeatures;
-
/**
- * List of features from runtime-scope features xml and kars to be installed into system repo and listed in features service boot features.
+ * List of features from runtime-scope features XML files and KARs to be installed into system repo
+ * and listed in featuresBoot property in etc/org.apache.karaf.features.cfg
*/
@Parameter
private List<String> bootFeatures;
-
/**
- * List of features from runtime-scope features xml and kars to be installed into system repo and not mentioned elsewhere.
+ * List of features from provided-scope features XML files and KARs to be installed into system repo
+ * and not mentioned elsewhere.
*/
@Parameter
private List<String> installedFeatures;
-
+ /**
+ * <p>List of feature blacklisting clauses. Each clause is in one of the formats ({@link org.apache.karaf.features.FeaturePattern}):<ul>
+ * <li><code>feature-name</code></li>
+ * <li><code>feature-name;range=version-or-range</code></li>
+ * <li><code>feature-name/version-or-range</code></li>
+ * </ul></p>
+ */
@Parameter
private List<String> blacklistedFeatures;
+ /**
+ * List of compile-scope bundles added to etc/startup.properties
+ */
@Parameter
private List<String> startupBundles;
+ /**
+ * List of runtime-scope bundles wrapped in special feature added to featuresBoot property
+ * in etc/org.apache.karaf.features.cfg
+ */
@Parameter
private List<String> bootBundles;
+ /**
+ * List of provided-scope bundles added to system repo
+ */
@Parameter
private List<String> installedBundles;
+ /**
+ * List of blacklisted bundle URIs. Blacklisted URI may use globs and version ranges. See
+ * {@link org.apache.karaf.features.LocationPattern}.
+ */
@Parameter
private List<String> blacklistedBundles;
-
- @Parameter
- private String profilesUri;
+ /**
+ * List of profile URIs to use
+ */
@Parameter
- private List<String> bootProfiles;
+ private List<String> profilesUris;
+ /**
+ * List of profiles names to load from configured <code>profilesUris</code> and use as startup profiles.
+ */
@Parameter
private List<String> startupProfiles;
-
+ /**
+ * List of profiles names to load from configured <code>profilesUris</code> and use as boot profiles.
+ */
+ @Parameter
+ private List<String> bootProfiles;
+ /**
+ * List of profiles names to load from configured <code>profilesUris</code> and use as installed profiles.
+ */
@Parameter
private List<String> installedProfiles;
-
+ /**
+ * List of blacklisted profile names (possibly using <code>*</code> glob)
+ */
@Parameter
private List<String> blacklistedProfiles;
+ /**
+ * When assembly custom distribution, we can include generated and added profiles in the distribution itself,
+ * in <code>${karaf.etc}/profiles</code> directory.
+ */
+ @Parameter(defaultValue = "false")
+ private boolean writeProfiles;
+
+ /*
+ * KARs are not configured using Maven plugin configuration, but rather detected from dependencies.
+ * All KARs are just unzipped into the assembly being constructed, but additionally KAR's embedded
+ * features XML repositories are added to relevant stage.
+ */
+
+ private List<String> startupKars = new ArrayList<>();
+ private List<String> bootKars = new ArrayList<>();
+ private List<String> installedKars = new ArrayList<>();
+
+ /**
+ * TODOCUMENT
+ */
@Parameter
private Builder.BlacklistPolicy blacklistPolicy = Builder.BlacklistPolicy.Discard;
/**
- * Ignore the dependency attribute (dependency="[true|false]") on bundle
+ * Ignore the dependency attribute (dependency="[true|false]") on bundles, effectively forcing their
+ * installation.
*/
@Parameter(defaultValue = "false")
protected boolean ignoreDependencyFlag;
/**
- * Additional feature repositories
+ * <p>Additional libraries to add into assembled distribution. Libraries are specified using
+ * <code>name[;url:=<url>][;type:=<type>][;export:=true|false][;delegate:=true|false]</code>
+ * syntax. If there's no <code>url</code> header directive, <code>name</code> is used as URI. Otherwise
+ * <code>name</code> is used as target file name to use.</p>
+ *
+ * <p><code>type</code> may be:<ul>
+ * <li>endorsed - library will be added to <code>${karaf.home}/lib/endorsed</code></li>
+ * <li>extension - library will be added to <code>${karaf.home}/lib/ext</code></li>
+ * <li>boot - library will be added to <code>${karaf.home}/lib/boot</code></li>
+ * <li>by default, library is put directly into <code>${karaf.home}/lib</code> - these libraries will
+ * be used in default classloader for OSGi framework which will load {@link FrameworkFactory} implementation.</li>
+ * </ul></p>
+ *
+ * <p><code>export</code> flag determines whether packages from <code>Export-Package</code> manifest
+ * header of the library will be added to <code>org.osgi.framework.system.packages.extra</code> property in
+ * <code>${karaf.etc}/config.properties</code>.</p>
+ *
+ * <p><code>delegate</code> flag determines whether packages from <code>Export-Pavkage</code> manifest
+ * header of the library will be added to <code>org.osgi.framework.bootdelegation</code> property in
+ * <code>${karaf.etc}/config.properties</code>.</p>
*/
@Parameter
- protected List<String> featureRepositories;
-
- @Parameter
protected List<String> libraries;
/**
- * Use reference: style urls in startup.properties
+ * Use <code>reference:file:gr/oup/Id/artifactId/version/artifactId-version-classifier.type</code> style
+ * urls in <code>etc/startup.properties</code>.
*/
+ // see:
+ // - org.apache.felix.framework.cache.BundleArchive.createRevisionFromLocation()
+ // - org.apache.karaf.main.Main.installAndStartBundles()
@Parameter(defaultValue = "false")
protected boolean useReferenceUrls;
/**
- * Include project build output directory in the assembly
+ * Include project build output directory in the assembly. This allows (filtered or unfiltered) Maven
+ * resources directories to be used to provide additional resources in the assembly.
*/
@Parameter(defaultValue = "true")
protected boolean includeBuildOutputDirectory;
- @Parameter
- protected boolean installAllFeaturesByDefault = true;
-
+ /**
+ * Karaf version changes the way some configuration files are prepared (to adjust to given Karaf version
+ * requirements).
+ */
@Parameter
protected Builder.KarafVersion karafVersion = Builder.KarafVersion.v4x;
/**
- * Specify the version of Java SE to be assumed for osgi.ee.
+ * <p>Specify the version of Java SE to be assumed for osgi.ee. The value will be used in
+ * <code>etc/config.properties</code> file, in <code>java.specification.version</code> placeholder used in
+ * several properties:<ul>
+ * <li><code>org.osgi.framework.system.packages</code></li>
+ * <li><code>org.osgi.framework.system.capabilities</code></li>
+ * </ul></p>
+ * <p>Valid values are: 1.6, 1.7, 1.8, 9</p>
*/
@Parameter(defaultValue = "1.8")
protected String javase;
/**
* Specify which framework to use
- * (one of framework, framework-logback, static-framework, static-framework-logback).
+ * (one of framework, framework-logback, static-framework, static-framework-logback, custom).
*/
@Parameter
protected String framework;
@@ -248,8 +385,8 @@ public class AssemblyMojo extends MojoSupport {
protected String propertyFileEdits;
/**
- * Glob specifying which configuration pids in the selected boot features
- * should be extracted to the etc directory.
+ * Glob specifying which configuration PIDs in the selected boot features
+ * should be extracted to <code>${karaf.etc}</code> directory. By default all PIDs are extracted.
*/
@Parameter
protected List<String> pidsToExtract = Collections.singletonList("*");
@@ -262,9 +399,15 @@ public class AssemblyMojo extends MojoSupport {
@Parameter
protected Map<String, String> translatedUrls;
+ /**
+ * Specify a list of additional properties that should be added to <code>${karaf.etc}/config.properties</code>
+ */
@Parameter
protected Map<String, String> config;
+ /**
+ * Specify a list of additional properties that should be added to <code>${karaf.etc}/system.properties</code>
+ */
@Parameter
protected Map<String, String> system;
@@ -274,77 +417,56 @@ public class AssemblyMojo extends MojoSupport {
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
try {
+ setNullListsToEmpty();
+ setNullMapsToEmpty();
+
doExecute();
- }
- catch (MojoExecutionException | MojoFailureException e) {
+ } catch (MojoExecutionException | MojoFailureException e) {
throw e;
- }
- catch (Exception e) {
+ } catch (Exception e) {
throw new MojoExecutionException("Unable to build assembly", e);
}
}
+ /**
+ * Main processing method. Most of the work involves configuring and invoking {@link Builder a profile builder}.
+ * @throws Exception
+ */
protected void doExecute() throws Exception {
- startupRepositories = nonNullList(startupRepositories);
- bootRepositories = nonNullList(bootRepositories);
- installedRepositories = nonNullList(installedRepositories);
- startupBundles = nonNullList(startupBundles);
- bootBundles = nonNullList(bootBundles);
- installedBundles = nonNullList(installedBundles);
- blacklistedBundles = nonNullList(blacklistedBundles);
- startupFeatures = nonNullList(startupFeatures);
- bootFeatures = nonNullList(bootFeatures);
- installedFeatures = nonNullList(installedFeatures);
- blacklistedFeatures = nonNullList(blacklistedFeatures);
- startupProfiles = nonNullList(startupProfiles);
- bootProfiles = nonNullList(bootProfiles);
- installedProfiles = nonNullList(installedProfiles);
- blacklistedProfiles = nonNullList(blacklistedProfiles);
- blacklistedRepositories = nonNullList(blacklistedRepositories);
-
if (!startupProfiles.isEmpty() || !bootProfiles.isEmpty() || !installedProfiles.isEmpty()) {
- if (profilesUri == null) {
- throw new IllegalArgumentException("profilesDirectory must be specified");
- }
- }
-
- if (featureRepositories != null && !featureRepositories.isEmpty()) {
- getLog().warn("Use of featureRepositories is deprecated, use startupRepositories, bootRepositories or installedRepositories instead");
- startupRepositories.addAll(featureRepositories);
- bootRepositories.addAll(featureRepositories);
- installedRepositories.addAll(featureRepositories);
- }
-
- StringBuilder remote = new StringBuilder();
- for (Object obj : project.getRemoteProjectRepositories()) {
- if (remote.length() > 0) {
- remote.append(",");
- }
- remote.append(invoke(obj, "getUrl"));
- remote.append("@id=").append(invoke(obj, "getId"));
- if (!((Boolean) invoke(getPolicy(obj, false), "isEnabled"))) {
- remote.append("@noreleases");
- }
- if ((Boolean) invoke(getPolicy(obj, true), "isEnabled")) {
- remote.append("@snapshots");
+ if (profilesUris.size() == 0) {
+ throw new IllegalArgumentException("profilesUris option must be specified");
}
}
- getLog().info("Using repositories: " + remote.toString());
Builder builder = Builder.newInstance();
+
+ // Set up miscellaneous options
builder.offline(mavenSession.isOffline());
builder.localRepository(localRepo.getBasedir());
- builder.mavenRepositories(remote.toString());
builder.resolverWrapper((resolver) -> new ReactorMavenResolver(reactor, resolver));
builder.javase(javase);
-
- // Set up config and system props
- if (config != null) {
- config.forEach(builder::config);
- }
- if (system != null) {
- system.forEach(builder::system);
+ builder.karafVersion(karafVersion);
+ builder.useReferenceUrls(useReferenceUrls);
+ builder.defaultAddAll(installAllFeaturesByDefault);
+ builder.ignoreDependencyFlag(ignoreDependencyFlag);
+ builder.propertyEdits(configurePropertyEdits());
+ builder.translatedUrls(configureTranslatedUrls());
+ builder.pidsToExtract(pidsToExtract);
+ builder.writeProfiles(writeProfiles);
+ builder.environment(environment);
+
+ // Set up remote repositories from Maven build, to be used by pax-url-aether resolver
+ String remoteRepositories = MavenUtil.remoteRepositoryList(project.getRemoteProjectRepositories());
+ getLog().info("Using repositories:");
+ for (String r : remoteRepositories.split(",")) {
+ getLog().info(" " + r);
}
+ builder.mavenRepositories(remoteRepositories);
+
+ // Set up config and system properties
+ config.forEach(builder::config);
+ system.forEach(builder::system);
// Set up blacklisted items
builder.blacklistBundles(blacklistedBundles);
@@ -353,163 +475,37 @@ public class AssemblyMojo extends MojoSupport {
builder.blacklistRepositories(blacklistedRepositories);
builder.blacklistPolicy(blacklistPolicy);
- if (propertyFileEdits != null) {
- File file = new File(propertyFileEdits);
- if (file.exists()) {
- KarafPropertyEdits edits;
- try (InputStream editsStream = new FileInputStream(propertyFileEdits)) {
- KarafPropertyInstructionsModelStaxReader kipmsr = new KarafPropertyInstructionsModelStaxReader();
- edits = kipmsr.read(editsStream, true);
- }
- builder.propertyEdits(edits);
- }
- }
- builder.pidsToExtract(pidsToExtract);
-
- Map<String, String> urls = new HashMap<>();
- List<Artifact> artifacts = new ArrayList<>(project.getAttachedArtifacts());
- artifacts.add(project.getArtifact());
- for (Artifact artifact : artifacts) {
- if (artifact.getFile() != null && artifact.getFile().exists()) {
- String mvnUrl = "mvn:" + artifact.getGroupId() + "/" + artifact.getArtifactId()
- + "/" + artifact.getVersion();
- String type = artifact.getType();
- if ("bundle".equals(type)) {
- type = "jar";
- }
- if (!"jar".equals(type) || artifact.getClassifier() != null) {
- mvnUrl += "/" + type;
- if (artifact.getClassifier() != null) {
- mvnUrl += "/" + artifact.getClassifier();
- }
- }
- urls.put(mvnUrl, artifact.getFile().toURI().toString());
- }
- }
- if (translatedUrls != null) {
- urls.putAll(translatedUrls);
- }
- builder.translatedUrls(urls);
-
- // creating system directory
- getLog().info("Creating work directory");
+ // Creating system directory
+ configureWorkDirectory();
+ getLog().info("Creating work directory: " + workDirectory);
builder.homeDirectory(workDirectory.toPath());
- IoUtils.deleteRecursive(workDirectory);
- workDirectory.mkdirs();
- List<String> startupKars = new ArrayList<>();
- List<String> bootKars = new ArrayList<>();
- List<String> installedKars = new ArrayList<>();
+ // Loading KARs and features repositories
+ getLog().info("Loading direct KAR and features XML dependencies");
+ processDirectMavenDependencies();
- // Loading kars and features repositories
- getLog().info("Loading kar and features repositories dependencies");
- for (Artifact artifact : project.getDependencyArtifacts()) {
- Builder.Stage stage;
- switch (artifact.getScope()) {
- case "compile":
- stage = Builder.Stage.Startup;
- break;
- case "runtime":
- stage = Builder.Stage.Boot;
- break;
- case "provided":
- stage = Builder.Stage.Installed;
- break;
- default:
- continue;
- }
- String uri = artifactToMvn(artifact);
- String type = getType(artifact);
- if ("kar".equals(type)) {
- switch (stage) {
- case Startup: startupKars.add(uri); break;
- case Boot: bootKars.add(uri); break;
- case Installed: installedKars.add(uri); break;
- }
- } else if ("features".equals(type)) {
- switch (stage) {
- case Startup: startupRepositories.add(uri); break;
- case Boot: bootRepositories.add(uri); break;
- case Installed: installedRepositories.add(uri); break;
- }
- } else if ("bundle".equals(type)) {
- switch (stage) {
- case Startup: startupBundles.add(uri); break;
- case Boot: bootBundles.add(uri); break;
- case Installed: installedBundles.add(uri); break;
- }
- }
- }
+ // Set up profiles and libraries
+ profilesUris.forEach(builder::profilesUris);
+ libraries.forEach(builder::libraries);
- builder.karafVersion(karafVersion)
- .useReferenceUrls(useReferenceUrls)
- .defaultAddAll(installAllFeaturesByDefault)
- .ignoreDependencyFlag(ignoreDependencyFlag);
- if (profilesUri != null) {
- builder.profilesUris(profilesUri);
- }
- if (libraries != null) {
- builder.libraries(libraries.toArray(new String[libraries.size()]));
- }
- // Startup
- boolean hasFrameworkKar = false;
- for (String kar : startupKars) {
- if (kar.startsWith("mvn:org.apache.karaf.features/framework/")
- || kar.startsWith("mvn:org.apache.karaf.features/static/")) {
- hasFrameworkKar = true;
- startupKars.remove(kar);
- if (framework == null) {
- framework = kar.startsWith("mvn:org.apache.karaf.features/framework/")
- ? "framework" : "static-framework";
- }
- builder.kars(Builder.Stage.Startup, false, kar);
- break;
- }
- }
- if (!hasFrameworkKar) {
- Properties versions = new Properties();
- try (InputStream is = getClass().getResourceAsStream("versions.properties")) {
- versions.load(is);
- } catch (IOException e) {
- throw new IllegalStateException(e);
- }
- String realKarafVersion = versions.getProperty("karaf-version");
- String kar;
- switch (framework) {
- case "framework":
- kar = "mvn:org.apache.karaf.features/framework/" + realKarafVersion + "/xml/features";
- break;
- case "framework-logback":
- kar = "mvn:org.apache.karaf.features/framework/" + realKarafVersion + "/xml/features";
- break;
- case "static-framework":
- kar = "mvn:org.apache.karaf.features/static/" + realKarafVersion + "/xml/features";
- break;
- case "static-framework-logback":
- kar = "mvn:org.apache.karaf.features/static/" + realKarafVersion + "/xml/features";
- break;
- default:
- throw new IllegalArgumentException("Unsupported framework: " + framework);
- }
- builder.kars(Builder.Stage.Startup, false, kar);
- }
- if (!startupFeatures.contains(framework)) {
- builder.features(Builder.Stage.Startup, framework);
- }
+ // Startup stage
+ detectStartupKarsAndFeatures(builder);
builder.defaultStage(Builder.Stage.Startup)
.kars(toArray(startupKars))
.repositories(startupFeatures.isEmpty() && startupProfiles.isEmpty() && installAllFeaturesByDefault, toArray(startupRepositories))
.features(toArray(startupFeatures))
.bundles(toArray(startupBundles))
.profiles(toArray(startupProfiles));
- // Boot
+
+ // Boot stage
builder.defaultStage(Builder.Stage.Boot)
.kars(toArray(bootKars))
.repositories(bootFeatures.isEmpty() && bootProfiles.isEmpty() && installAllFeaturesByDefault, toArray(bootRepositories))
.features(toArray(bootFeatures))
.bundles(toArray(bootBundles))
.profiles(toArray(bootProfiles));
- // Installed
+
+ // Installed stage
builder.defaultStage(Builder.Stage.Installed)
.kars(toArray(installedKars))
.repositories(installedFeatures.isEmpty() && installedProfiles.isEmpty() && installAllFeaturesByDefault, toArray(installedRepositories))
@@ -520,47 +516,180 @@ public class AssemblyMojo extends MojoSupport {
// Generate the assembly
builder.generateAssembly();
- // Include project classes content
+ // Include project classes content if not specified otherwise
if (includeBuildOutputDirectory)
IoUtils.copyDirectory(new File(project.getBuild().getOutputDirectory()), workDirectory);
- // Overwrite assembly dir contents
+ // Overwrite assembly dir contents with source directory (not filtered) when directory exists
if (sourceDirectory.exists())
IoUtils.copyDirectory(sourceDirectory, workDirectory);
// Chmod the bin/* scripts
File[] files = new File(workDirectory, "bin").listFiles();
- if( files!=null ) {
+ if (files != null) {
for (File file : files) {
- if( !file.getName().endsWith(".bat") ) {
+ if (!file.getName().endsWith(".bat")) {
try {
Files.setPosixFilePermissions(file.toPath(), PosixFilePermissions.fromString("rwxr-xr-x"));
} catch (Throwable ignore) {
- // we tried our best, perhaps the OS does not support posix file perms.
+ // we tried our best, perhaps the OS does not support POSIX file perms.
}
}
}
}
}
- private Object invoke(Object object, String getter) throws MojoExecutionException {
- try {
- return object.getClass().getMethod(getter).invoke(object);
- } catch (Exception e) {
- throw new MojoExecutionException("Unable to build remote repository from " + object.toString(), e);
+ private void configureWorkDirectory() {
+ IoUtils.deleteRecursive(workDirectory);
+ workDirectory.mkdirs();
+ new File(workDirectory, "etc").mkdirs();
+ new File(workDirectory, "system").mkdirs();
+ }
+
+ /**
+ * <p>Turns direct maven dependencies into startup/boot/installed artifacts.</p>
+ * <p>{@link MavenProject#getDependencyArtifacts()} is deprecated, but we don't want (?) transitive
+ * dependencies given by {@link MavenProject#getArtifacts()}.</p>
+ */
+ @SuppressWarnings("deprecation")
+ private void processDirectMavenDependencies() {
+ for (Artifact artifact : project.getDependencyArtifacts()) {
+ Builder.Stage stage = Builder.Stage.fromMavenScope(artifact.getScope());
+ if (stage == null) {
+ continue;
+ }
+ String uri = artifactToMvn(artifact);
+ switch (getType(artifact)) {
+ case "kar":
+ addUris(stage, uri, startupKars, bootKars, installedKars);
+ break;
+ case "features":
+ addUris(stage, uri, startupRepositories, bootRepositories, installedRepositories);
+ break;
+ case "bundle":
+ addUris(stage, uri, startupBundles, bootBundles, installedBundles);
+ break;
+ }
}
}
- private Object getPolicy(Object object, boolean snapshots) throws MojoExecutionException {
- return invoke(object, "getPolicy", new Class[] { Boolean.TYPE }, new Object[] { snapshots });
+ private void addUris(Builder.Stage stage, String uri, List<String> startup, List<String> boot, List<String> installed) {
+ switch (stage) {
+ case Startup:
+ startup.add(uri);
+ break;
+ case Boot:
+ boot.add(uri);
+ break;
+ case Installed:
+ installed.add(uri);
+ break;
+ }
}
- private Object invoke(Object object, String getter, Class[] types, Object[] params) throws MojoExecutionException {
- try {
- return object.getClass().getMethod(getter, types).invoke(object, params);
- } catch (Exception e) {
- throw new MojoExecutionException("Unable to build remote repository from " + object.toString(), e);
+ /**
+ * <p>Custom distribution is created from at least one <em>startup KAR</em> and one <em>startup</em>
+ * feature. Such startup KAR + feature is called <em>framework</em>.</p>
+ *
+ * <p>We can specify one of 5 <em>frameworks</em>:<ul>
+ * <li>framework: <code>mvn:org.apache.karaf.features/framework/VERSION/kar</code> and <code>framework</code> feature</li>
+ * <li>framework-logback: <code>mvn:org.apache.karaf.features/framework/VERSION/kar</code> and <code>framework-logback</code> feature</li>
+ * <li>static-framework: <code>mvn:org.apache.karaf.features/static/VERSION/kar</code> and <code>static-framework</code> feature</li>
+ * <li>static-framework-logback: <code>mvn:org.apache.karaf.features/static/VERSION/kar</code> and <code>static-framework-logback</code> feature</li>
+ * <li>custom: both startup KAR and startup feature has to be specified explicitly</li>
+ * </ul></p>
+ * @param builder
+ */
+ private void detectStartupKarsAndFeatures(Builder builder) {
+ boolean hasStandardKarafFrameworkKar = false;
+ boolean hasCustomFrameworkKar = false;
+ for (Iterator<String> iterator = startupKars.iterator(); iterator.hasNext(); ) {
+ String kar = iterator.next();
+ if (kar.startsWith("mvn:org.apache.karaf.features/framework/")
+ || kar.startsWith("mvn:org.apache.karaf.features/static/")) {
+ hasStandardKarafFrameworkKar = true;
+ iterator.remove();
+ if (framework == null) {
+ framework = kar.startsWith("mvn:org.apache.karaf.features/framework/")
+ ? "framework" : "static-framework";
+ }
+ getLog().info(" Standard startup Karaf KAR found: " + kar);
+ builder.kars(Builder.Stage.Startup, false, kar);
+ break;
+ }
+ }
+
+ if (!hasStandardKarafFrameworkKar) {
+ if ("custom".equals(framework)) {
+ // we didn't detect standard Karaf KAR (framework or static), so we expect at least one
+ // other KAR dependency with compile scope and at least one startup feature
+ if (startupKars.isEmpty()) {
+ throw new IllegalArgumentException("Custom KAR was declared, but there's no Maven dependency with type=kar and scope=compile." +
+ " Please specify at least one KAR for custom assembly.");
+ }
+ if (startupFeatures.isEmpty()) {
+ throw new IllegalArgumentException("Custom KAR was declared, but there's no startup feature declared." +
+ " Please specify at least one startup feature defined in features XML repository inside custom startup KAR or startup repository.");
+ }
+ hasCustomFrameworkKar = true;
+ for (String startupKar : startupKars) {
+ getLog().info(" Custom startup KAR found: " + startupKar);
+ }
+ } else if (framework == null) {
+ throw new IllegalArgumentException("Can't determine framework to use (framework, framework-logback, static-framework, static-framework-logback, custom)." +
+ " Please specify valid \"framework\" option or add Maven dependency with \"kar\" type and \"compile\" scope for one of standard Karaf KARs.");
+ } else {
+ String realKarafVersion = Version.karafVersion();
+ String kar;
+ switch (framework) {
+ case "framework":
+ case "framework-logback":
+ kar = "mvn:org.apache.karaf.features/framework/" + realKarafVersion + "/kar";
+ break;
+ case "static-framework":
+ case "static-framework-logback":
+ kar = "mvn:org.apache.karaf.features/static/" + realKarafVersion + "/kar";
+ break;
+ default:
+ throw new IllegalArgumentException("Unsupported framework: " + framework);
+ }
+ getLog().info(" Standard startup KAR implied from framework (" + framework + "): " + kar);
+ builder.kars(Builder.Stage.Startup, false, kar);
+ }
+ }
+
+ if (hasStandardKarafFrameworkKar && !startupFeatures.contains(framework)) {
+ getLog().info(" Feature " + framework + " will be added as a startup feature");
+ builder.features(Builder.Stage.Startup, framework);
+ }
+ }
+
+ private KarafPropertyEdits configurePropertyEdits() throws IOException, XMLStreamException {
+ KarafPropertyEdits edits = null;
+ if (propertyFileEdits != null) {
+ File file = new File(propertyFileEdits);
+ if (file.exists()) {
+ try (InputStream editsStream = new FileInputStream(propertyFileEdits)) {
+ KarafPropertyInstructionsModelStaxReader kipmsr = new KarafPropertyInstructionsModelStaxReader();
+ edits = kipmsr.read(editsStream, true);
+ }
+ }
+ }
+ return edits;
+ }
+
+ private Map<String,String> configureTranslatedUrls() {
+ Map<String, String> urls = new HashMap<>();
+ List<Artifact> artifacts = new ArrayList<>(project.getAttachedArtifacts());
+ artifacts.add(project.getArtifact());
+ for (Artifact artifact : artifacts) {
+ if (artifact.getFile() != null && artifact.getFile().exists()) {
+ String mvnUrl = artifactToMvn(artifact);
+ urls.put(mvnUrl, artifact.getFile().toURI().toString());
+ }
}
+ urls.putAll(translatedUrls);
+ return urls;
}
private String getType(Artifact artifact) {
@@ -616,7 +745,7 @@ public class AssemblyMojo extends MojoSupport {
return "unknown";
}
- private String artifactToMvn(Artifact artifact) throws MojoExecutionException {
+ private String artifactToMvn(Artifact artifact) {
String uri;
String groupId = artifact.getGroupId();
@@ -641,9 +770,40 @@ public class AssemblyMojo extends MojoSupport {
return strings.toArray(new String[strings.size()]);
}
+ private void setNullListsToEmpty() {
+ startupRepositories = nonNullList(startupRepositories);
+ bootRepositories = nonNullList(bootRepositories);
+ installedRepositories = nonNullList(installedRepositories);
+ blacklistedRepositories = nonNullList(blacklistedRepositories);
+ startupBundles = nonNullList(startupBundles);
+ bootBundles = nonNullList(bootBundles);
+ installedBundles = nonNullList(installedBundles);
+ blacklistedBundles = nonNullList(blacklistedBundles);
+ startupFeatures = nonNullList(startupFeatures);
+ bootFeatures = nonNullList(bootFeatures);
+ installedFeatures = nonNullList(installedFeatures);
+ blacklistedFeatures = nonNullList(blacklistedFeatures);
+ startupProfiles = nonNullList(startupProfiles);
+ bootProfiles = nonNullList(bootProfiles);
+ installedProfiles = nonNullList(installedProfiles);
+ blacklistedProfiles = nonNullList(blacklistedProfiles);
+ libraries = nonNullList(libraries);
+ profilesUris = nonNullList(profilesUris);
+ }
+
+ private void setNullMapsToEmpty() {
+ config = nonNullMap(config);
+ system = nonNullMap(system);
+ translatedUrls = nonNullMap(translatedUrls);
+ }
+
private List<String> nonNullList(List<String> list) {
final List<String> nonNullList = list == null ? new ArrayList<>() : list;
return nonNullList.stream().filter(Objects::nonNull).collect(Collectors.toList());
}
+ private Map<String, String> nonNullMap(Map<String, String> map) {
+ return map == null ? new LinkedHashMap<>() : map;
+ }
+
}
diff --git a/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/VerifyMojo.java b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/VerifyMojo.java
index 2ff0a53..d13e786 100644
--- a/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/VerifyMojo.java
+++ b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/VerifyMojo.java
@@ -80,6 +80,7 @@ import org.apache.karaf.features.internal.service.StaticInstallSupport;
import org.apache.karaf.features.internal.util.MapUtils;
import org.apache.karaf.features.internal.util.MultiException;
import org.apache.karaf.profile.assembly.CustomDownloadManager;
+import org.apache.karaf.tooling.utils.MavenUtil;
import org.apache.karaf.tooling.utils.MojoSupport;
import org.apache.karaf.tooling.utils.ReactorMavenResolver;
import org.apache.karaf.util.config.PropertiesLoader;
@@ -167,32 +168,13 @@ public class VerifyMojo extends MojoSupport {
}
if (karafVersion == null) {
- Properties versions = new Properties();
- try (InputStream is = getClass().getResourceAsStream("versions.properties")) {
- versions.load(is);
- } catch (IOException e) {
- throw new IllegalStateException(e);
- }
- karafVersion = versions.getProperty("karaf-version");
+ karafVersion = org.apache.karaf.util.Version.karafVersion();
}
Hashtable<String, String> config = new Hashtable<>();
- StringBuilder remote = new StringBuilder();
- for (Object obj : project.getRemoteProjectRepositories()) {
- if (remote.length() > 0) {
- remote.append(",");
- }
- remote.append(invoke(obj, "getUrl"));
- remote.append("@id=").append(invoke(obj, "getId"));
- if (!((Boolean) invoke(getPolicy(obj, false), "isEnabled"))) {
- remote.append("@noreleases");
- }
- if ((Boolean) invoke(getPolicy(obj, true), "isEnabled")) {
- remote.append("@snapshots");
- }
- }
- getLog().info("Using repositories: " + remote.toString());
- config.put("maven.repositories", remote.toString());
+ String remoteRepositories = MavenUtil.remoteRepositoryList(project.getRemoteProjectRepositories());
+ getLog().info("Using repositories: " + remoteRepositories);
+ config.put("maven.repositories", remoteRepositories);
config.put("maven.localRepository", localRepo.getBasedir());
config.put("maven.settings", mavenSession.getRequest().getUserSettingsFile().toString());
// TODO: add more configuration bits ?
diff --git a/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/utils/MavenUtil.java b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/utils/MavenUtil.java
index 4d11dd5..13fcd80 100644
--- a/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/utils/MavenUtil.java
+++ b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/utils/MavenUtil.java
@@ -23,6 +23,7 @@ import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.Date;
+import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -33,6 +34,7 @@ import org.apache.maven.artifact.repository.metadata.Snapshot;
import org.apache.maven.artifact.repository.metadata.SnapshotVersion;
import org.apache.maven.artifact.repository.metadata.Versioning;
import org.apache.maven.artifact.repository.metadata.io.xpp3.MetadataXpp3Writer;
+import org.eclipse.aether.repository.RemoteRepository;
/**
* Util method for Maven manipulation (URL convert, metadata generation, etc).
@@ -41,7 +43,7 @@ public class MavenUtil {
static final DefaultRepositoryLayout layout = new DefaultRepositoryLayout();
private static final Pattern aetherPattern = Pattern.compile("([^: ]+):([^: ]+)(:([^: ]*)(:([^: ]+))?)?:([^: ]+)");
- private static final Pattern mvnPattern = Pattern.compile("(?:(?:wrap:)|(?:blueprint:))?mvn:([^/ ]+)/([^/ ]+)/([^/\\$ ]*)(/([^/\\$ ]+)(/([^/\\$ ]+))?)?(/\\$.+)?");
+ private static final Pattern mvnPattern = Pattern.compile("(?:(?:wrap:)|(?:blueprint:))?mvn:([^/ ]+)/([^/ ]+)/([^/$ ]*)(/([^/$ ]+)(/([^/$ ]+))?)?(/\\$.+)?");
/**
* Convert PAX URL mvn format to aether coordinate format.
@@ -153,13 +155,35 @@ public class MavenUtil {
}
public static String getFileName(Artifact artifact) {
- String name = artifact.getArtifactId() + "-" + artifact.getBaseVersion()
+ return artifact.getArtifactId() + "-" + artifact.getBaseVersion()
+ (artifact.getClassifier() != null ? "-" + artifact.getClassifier() : "") + "." + artifact.getType();
- return name;
}
-
+
public static String getDir(Artifact artifact) {
return artifact.getGroupId().replace('.', '/') + "/" + artifact.getArtifactId() + "/" + artifact.getBaseVersion() + "/";
}
+ /**
+ * Changes maven configuration of remote repositories to a list of repositories for pax-url-aether
+ * @param remoteRepositories
+ * @return
+ */
+ public static String remoteRepositoryList(List<RemoteRepository> remoteRepositories) {
+ StringBuilder remotes = new StringBuilder();
+ for (RemoteRepository rr : remoteRepositories) {
+ if (remotes.length() > 0) {
+ remotes.append(",");
+ }
+ remotes.append(rr.getUrl());
+ remotes.append("@id=").append(rr.getId());
+ if (!rr.getPolicy(false).isEnabled()) {
+ remotes.append("@noreleases");
+ }
+ if (rr.getPolicy(true).isEnabled()) {
+ remotes.append("@snapshots");
+ }
+ }
+ return remotes.toString();
+ }
+
}
--
To stop receiving notification emails like this one, please contact
"commits@karaf.apache.org" <co...@karaf.apache.org>.
[karaf] 01/07: [KARAF-5376] Moving overrides and blacklist
mechanisms to "features processor" service
Posted by gg...@apache.org.
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 46620727ce5b7daa1da3dd193e3fd7cf7a851ef9
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 01f9a4a..d9e156d 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>.
[karaf] 03/07: [KARAF-5376] Review BuilderTest
Posted by gg...@apache.org.
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 e7ad6ea169d756fa8741225fd5d3ff17f67402f5
Author: Grzegorz Grzybek <gr...@gmail.com>
AuthorDate: Tue Nov 7 13:23:17 2017 +0100
[KARAF-5376] Review BuilderTest
---
profile/pom.xml | 2 +-
.../apache/karaf/profile/assembly/BuilderTest.java | 28 ++++++++++++++++++++++
profile/src/test/resources/log4j.properties | 26 ++++++++++++++++++++
3 files changed, 55 insertions(+), 1 deletion(-)
diff --git a/profile/pom.xml b/profile/pom.xml
index c50d3dc..223e522 100644
--- a/profile/pom.xml
+++ b/profile/pom.xml
@@ -134,7 +134,7 @@
<dependency>
<groupId>org.slf4j</groupId>
- <artifactId>slf4j-jdk14</artifactId>
+ <artifactId>slf4j-log4j12</artifactId>
<scope>test</scope>
</dependency>
diff --git a/profile/src/test/java/org/apache/karaf/profile/assembly/BuilderTest.java b/profile/src/test/java/org/apache/karaf/profile/assembly/BuilderTest.java
index 3b82371..8003e56 100644
--- a/profile/src/test/java/org/apache/karaf/profile/assembly/BuilderTest.java
+++ b/profile/src/test/java/org/apache/karaf/profile/assembly/BuilderTest.java
@@ -35,6 +35,34 @@ import static org.hamcrest.MatcherAssert.assertThat;
public class BuilderTest {
@Test
+ public void testAssemblyGoalLikeBuild() throws Exception {
+ Path workDir = Paths.get("target/assembly");
+ recursiveDelete(workDir);
+
+ // Create dummy etc/config.properties file
+ Path config = workDir.resolve("etc/config.properties");
+ Files.createDirectories(config.getParent());
+ try (BufferedWriter w = Files.newBufferedWriter(config)) {
+ w.write(Constants.FRAMEWORK_SYSTEMPACKAGES + " = org.osgi.dto");
+ w.newLine();
+ w.write(Constants.FRAMEWORK_SYSTEMCAPABILITIES + " = ");
+ w.newLine();
+ }
+
+ Path mvnRepo = Paths.get("target/test-classes/repo");
+ Builder builder = Builder.newInstance();
+ // defaultValue = "${project.build.directory}/assembly"
+ builder.homeDirectory(workDir);
+// .repositories(Builder.Stage.Startup, true, "mvn:foo/baz/1.0/xml/features")
+
+// .localRepository(mvnRepo.toString());
+ builder.generateAssembly();
+
+ // org.apache.karaf.tooling.AssemblyMojo.includeBuildOutputDirectory? -
+ // ${project.basedir}/src/main/resources/assembly - copied over assembly (no filtering - source files)
+ }
+
+ @Test
public void testCyclicRepos() throws Exception {
Path workDir = Paths.get("target/distrib");
recursiveDelete(workDir);
diff --git a/profile/src/test/resources/log4j.properties b/profile/src/test/resources/log4j.properties
new file mode 100644
index 0000000..8c07c5c
--- /dev/null
+++ b/profile/src/test/resources/log4j.properties
@@ -0,0 +1,26 @@
+## ---------------------------------------------------------------------------
+## 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
+
+# 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} [%5p] {%t} %C (%F:%L) %m%n
--
To stop receiving notification emails like this one, please contact
"commits@karaf.apache.org" <co...@karaf.apache.org>.
[karaf] 05/07: [KARAF-5478] Provide org.apache.karaf.util.Version
class
Posted by gg...@apache.org.
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 196b8ff6331b03057dbd229737acf6027157816b
Author: Grzegorz Grzybek <gr...@gmail.com>
AuthorDate: Fri Nov 10 13:23:11 2017 +0100
[KARAF-5478] Provide org.apache.karaf.util.Version class
---
util/pom.xml | 9 +++++
.../org/apache/karaf/util}/versions.properties | 0
.../main/java/org/apache/karaf/util/Version.java | 43 ++++++++++++++++++++++
3 files changed, 52 insertions(+)
diff --git a/util/pom.xml b/util/pom.xml
index 672b39a..fb12697 100644
--- a/util/pom.xml
+++ b/util/pom.xml
@@ -68,4 +68,13 @@
<appendedResourcesDirectory>${basedir}/../etc/appended-resources</appendedResourcesDirectory>
</properties>
+ <build>
+ <resources>
+ <resource>
+ <directory>${project.basedir}/src/main/filtered-resources</directory>
+ <filtering>true</filtering>
+ </resource>
+ </resources>
+ </build>
+
</project>
diff --git a/tooling/karaf-maven-plugin/src/main/filtered-resources/org/apache/karaf/tooling/versions.properties b/util/src/main/filtered-resources/org/apache/karaf/util/versions.properties
similarity index 100%
rename from tooling/karaf-maven-plugin/src/main/filtered-resources/org/apache/karaf/tooling/versions.properties
rename to util/src/main/filtered-resources/org/apache/karaf/util/versions.properties
diff --git a/util/src/main/java/org/apache/karaf/util/Version.java b/util/src/main/java/org/apache/karaf/util/Version.java
new file mode 100644
index 0000000..73d3476
--- /dev/null
+++ b/util/src/main/java/org/apache/karaf/util/Version.java
@@ -0,0 +1,43 @@
+/*
+ * 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.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+
+public class Version {
+
+ private static String version = "unknown";
+
+ static {
+ Properties versions = new Properties();
+ try (InputStream is = Version.class.getResourceAsStream("versions.properties")) {
+ versions.load(is);
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+ version = versions.getProperty("karaf-version");
+ }
+
+ public static String karafVersion() {
+ return version;
+ }
+
+}
--
To stop receiving notification emails like this one, please contact
"commits@karaf.apache.org" <co...@karaf.apache.org>.
[karaf] 04/07: Revert "[KARAF-5416] Remove support for ext and
endorsed libraries for Java 9 compatibility"
Posted by gg...@apache.org.
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 07bbb7d465427dd3cb394b815ec7f6d2b3f58cbe
Author: Grzegorz Grzybek <gr...@gmail.com>
AuthorDate: Fri Nov 10 12:16:11 2017 +0100
Revert "[KARAF-5416] Remove support for ext and endorsed libraries for Java 9 compatibility"
This reverts commit 3f1f44b6521f058abea6f6df19ce03a4c353c991.
---
assemblies/features/base/pom.xml | 10 ++
.../main/filtered-resources/resources/bin/karaf | 6 +
.../filtered-resources/resources/bin/karaf.bat | 2 +
.../src/main/resources/resources/lib/boot/README | 21 +++
.../main/resources/resources/lib/endorsed/README | 23 +++
assemblies/features/framework/pom.xml | 5 +
exception/NOTICE | 67 +++++++
exception/pom.xml | 38 ++++
exception/src/main/java/java/lang/Exception.java | 194 +++++++++++++++++++++
.../main/asciidoc/user-guide/os-integration.adoc | 2 +
pom.xml | 6 +
.../org/apache/karaf/profile/assembly/Builder.java | 4 -
.../karaf/wrapper/internal/unix/karaf-wrapper.conf | 4 +-
.../wrapper/internal/windows/karaf-wrapper.conf | 4 +-
.../wrapper/internal/windows64/karaf-wrapper.conf | 4 +-
15 files changed, 383 insertions(+), 7 deletions(-)
diff --git a/assemblies/features/base/pom.xml b/assemblies/features/base/pom.xml
index 072c1bf..d14c041 100644
--- a/assemblies/features/base/pom.xml
+++ b/assemblies/features/base/pom.xml
@@ -44,6 +44,11 @@
</dependency>
<dependency>
<groupId>org.apache.karaf</groupId>
+ <artifactId>org.apache.karaf.exception</artifactId>
+ <scope>runtime</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.karaf</groupId>
<artifactId>org.apache.karaf.client</artifactId>
<scope>runtime</scope>
</dependency>
@@ -130,6 +135,11 @@
<outputDirectory>target/classes/resources/system/org/apache/karaf/org.apache.karaf.client/${project.version}</outputDirectory>
</artifactItem>
<artifactItem>
+ <groupId>org.apache.karaf</groupId>
+ <artifactId>org.apache.karaf.exception</artifactId>
+ <outputDirectory>target/classes/resources/lib/endorsed</outputDirectory>
+ </artifactItem>
+ <artifactItem>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.framework</artifactId>
<outputDirectory>target/classes/resources/system/org/apache/felix/org.apache.felix.framework/${felix.framework.version}</outputDirectory>
diff --git a/assemblies/features/base/src/main/filtered-resources/resources/bin/karaf b/assemblies/features/base/src/main/filtered-resources/resources/bin/karaf
index a70a896..674b8d0 100644
--- a/assemblies/features/base/src/main/filtered-resources/resources/bin/karaf
+++ b/assemblies/features/base/src/main/filtered-resources/resources/bin/karaf
@@ -155,8 +155,12 @@ run() {
if [ "x$CHECK_ROOT_INSTANCE_RUNNING" = "x" ]; then
CHECK_ROOT_INSTANCE_RUNNING=true
fi
+ JAVA_ENDORSED_DIRS="${JAVA_HOME}/jre/lib/endorsed:${JAVA_HOME}/lib/endorsed:${KARAF_HOME}/lib/endorsed"
+ JAVA_EXT_DIRS="${JAVA_HOME}/jre/lib/ext:${JAVA_HOME}/lib/ext:${KARAF_HOME}/lib/ext"
if ${cygwin}; then
JAVA_HOME=$(cygpath --path --windows "${JAVA_HOME}")
+ JAVA_ENDORSED_DIRS=$(cygpath --path --windows "${JAVA_ENDORSED_DIRS}")
+ JAVA_EXT_DIRS=$(cygpath --path --windows "${JAVA_EXT_DIRS}")
fi
convertPaths
cd "${KARAF_BASE}" || exit 2
@@ -290,6 +294,8 @@ run() {
${MAIN} "$@"
else
${KARAF_EXEC} "${JAVA}" ${JAVA_OPTS} \
+ -Djava.endorsed.dirs="${JAVA_ENDORSED_DIRS}" \
+ -Djava.ext.dirs="${JAVA_EXT_DIRS}" \
-Dkaraf.instances="${KARAF_HOME}/instances" \
-Dkaraf.home="${KARAF_HOME}" \
-Dkaraf.base="${KARAF_BASE}" \
diff --git a/assemblies/features/base/src/main/filtered-resources/resources/bin/karaf.bat b/assemblies/features/base/src/main/filtered-resources/resources/bin/karaf.bat
index 0cb31c2..1f95c21 100644
--- a/assemblies/features/base/src/main/filtered-resources/resources/bin/karaf.bat
+++ b/assemblies/features/base/src/main/filtered-resources/resources/bin/karaf.bat
@@ -414,6 +414,8 @@ if "%KARAF_PROFILER%" == "" goto :RUN
) else (
"%JAVA%" %JAVA_OPTS% %OPTS% ^
-classpath "%CLASSPATH%" ^
+ -Djava.endorsed.dirs="%JAVA_HOME%\jre\lib\endorsed;%JAVA_HOME%\lib\endorsed;%KARAF_HOME%\lib\endorsed" ^
+ -Djava.ext.dirs="%JAVA_HOME%\jre\lib\ext;%JAVA_HOME%\lib\ext;%KARAF_HOME%\lib\ext" ^
-Dkaraf.instances="%KARAF_HOME%\instances" ^
-Dkaraf.home="%KARAF_HOME%" ^
-Dkaraf.base="%KARAF_BASE%" ^
diff --git a/assemblies/features/base/src/main/resources/resources/lib/boot/README b/assemblies/features/base/src/main/resources/resources/lib/boot/README
new file mode 100644
index 0000000..b3a06ce
--- /dev/null
+++ b/assemblies/features/base/src/main/resources/resources/lib/boot/README
@@ -0,0 +1,21 @@
+################################################################################
+#
+# 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.
+#
+################################################################################
+
+Any jar in this folder will be added to the classpath by the JVM and will be
+available to the Main class. Custom locking libraries can be added here.
diff --git a/assemblies/features/base/src/main/resources/resources/lib/endorsed/README b/assemblies/features/base/src/main/resources/resources/lib/endorsed/README
new file mode 100644
index 0000000..0665a52
--- /dev/null
+++ b/assemblies/features/base/src/main/resources/resources/lib/endorsed/README
@@ -0,0 +1,23 @@
+################################################################################
+#
+# 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.
+#
+################################################################################
+
+This directory is the Java endorsed directory.
+Any jar in this folder will be used to override classes defined by the JVM.
+For more information, see:
+ http://download.oracle.com/javase/6/docs/technotes/guides/standards/
diff --git a/assemblies/features/framework/pom.xml b/assemblies/features/framework/pom.xml
index a55051c..61525b8 100644
--- a/assemblies/features/framework/pom.xml
+++ b/assemblies/features/framework/pom.xml
@@ -51,6 +51,11 @@
</dependency>
<dependency>
<groupId>org.apache.karaf</groupId>
+ <artifactId>org.apache.karaf.exception</artifactId>
+ <scope>runtime</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.karaf</groupId>
<artifactId>org.apache.karaf.client</artifactId>
<scope>runtime</scope>
</dependency>
diff --git a/exception/NOTICE b/exception/NOTICE
new file mode 100644
index 0000000..f1f81e0
--- /dev/null
+++ b/exception/NOTICE
@@ -0,0 +1,67 @@
+Apache Karaf
+Copyright 2010-2014 The Apache Software Foundation
+
+
+I. Included Software
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
+Licensed under the Apache License 2.0.
+
+This product uses software developed at
+The OSGi Alliance (http://www.osgi.org/).
+Copyright (c) OSGi Alliance (2000, 2010).
+Licensed under the Apache License 2.0.
+
+This product includes software developed at
+OW2 (http://www.ow2.org/).
+Licensed under the BSD License.
+
+This product includes software developed at
+OPS4J (http://www.ops4j.org/).
+Licensed under the Apache License 2.0.
+
+This product includes software developed at
+Eclipse Foundation (http://www.eclipse.org/).
+Licensed under the EPL.
+
+This product includes software written by
+Antony Lesuisse.
+Licensed under Public Domain.
+
+
+II. Used Software
+
+This product uses software developed at
+FUSE Source (http://www.fusesource.org/).
+Licensed under the Apache License 2.0.
+
+This product uses software developed at
+AOP Alliance (http://aopalliance.sourceforge.net/).
+Licensed under the Public Domain.
+
+This product uses software developed at
+Tanuki Software (http://www.tanukisoftware.com/).
+Licensed under the Apache License 2.0.
+
+This product uses software developed at
+Jasypt (http://jasypt.sourceforge.net/).
+Licensed under the Apache License 2.0.
+
+This product uses software developed at
+JLine (https://github.com/jline/).
+Licensed under the BSD License.
+
+This product uses software developed at
+SLF4J (http://www.slf4j.org/).
+Licensed under the MIT License.
+
+This product uses software developed at
+SpringSource (http://www.springsource.org/).
+Licensed under the Apache License 2.0.
+
+III. License Summary
+- Apache License 2.0
+- BSD License
+- EPL License
+- MIT License
diff --git a/exception/pom.xml b/exception/pom.xml
new file mode 100644
index 0000000..370ddb6
--- /dev/null
+++ b/exception/pom.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <!--
+
+ 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.
+ -->
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.apache.karaf</groupId>
+ <artifactId>karaf</artifactId>
+ <version>4.2.0-SNAPSHOT</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+
+ <artifactId>org.apache.karaf.exception</artifactId>
+ <name>Apache Karaf :: Exception</name>
+
+ <properties>
+ <appendedResourcesDirectory>${basedir}/../etc/appended-resources</appendedResourcesDirectory>
+ </properties>
+
+</project>
diff --git a/exception/src/main/java/java/lang/Exception.java b/exception/src/main/java/java/lang/Exception.java
new file mode 100644
index 0000000..940658b
--- /dev/null
+++ b/exception/src/main/java/java/lang/Exception.java
@@ -0,0 +1,194 @@
+/*
+ * 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 java.lang;
+
+import java.lang.reflect.Field;
+import java.lang.ref.Reference;
+import java.lang.ref.WeakReference;
+
+import javax.xml.bind.annotation.XmlTransient;
+
+
+/**
+ * {@code Exception} is the superclass of all classes that represent recoverable
+ * exceptions. When exceptions are thrown, they may be caught by application
+ * code.
+ *
+ * @see Throwable
+ * @see Error
+ * @see RuntimeException
+ */
+public class Exception extends Throwable {
+ private static final long serialVersionUID = -3387516993124229948L;
+
+ private transient Reference<Class<?>>[] classContext = null;
+
+ /**
+ * Constructs a new {@code Exception} that includes the current stack trace.
+ */
+ public Exception() {
+ super();
+ initClassContext();
+ }
+
+ /**
+ * Constructs a new {@code Exception} with the current stack trace and the
+ * specified detail message.
+ *
+ * @param detailMessage
+ * the detail message for this exception.
+ */
+ public Exception(String detailMessage) {
+ super(detailMessage);
+ initClassContext();
+ }
+
+ /**
+ * Constructs a new {@code Exception} with the current stack trace, the
+ * specified detail message and the specified cause.
+ *
+ * @param detailMessage
+ * the detail message for this exception.
+ * @param throwable
+ * the cause of this exception.
+ */
+ public Exception(String detailMessage, Throwable throwable) {
+ super(detailMessage, throwable);
+ initClassContext();
+ }
+
+ /**
+ * Constructs a new {@code Exception} with the current stack trace and the
+ * specified cause.
+ *
+ * @param throwable
+ * the cause of this exception.
+ */
+ public Exception(Throwable throwable) {
+ super(throwable);
+ initClassContext();
+ }
+
+ /**
+ * Constructs a new exception with the specified detail message,
+ * cause, suppression enabled or disabled, and writable stack
+ * trace enabled or disabled.
+ *
+ * @param message the detail message.
+ * @param cause the cause. (A {@code null} value is permitted,
+ * and indicates that the cause is nonexistent or unknown.)
+ * @param enableSuppression whether or not suppression is enabled
+ * or disabled
+ * @param writableStackTrace whether or not the stack trace should
+ * be writable
+ */
+ protected Exception(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+ try {
+ Field field = null;
+ if (writableStackTrace) {
+ fillInStackTrace();
+ } else {
+ field = Throwable.class.getDeclaredField("stackTrace");
+ field.setAccessible(true);
+ field.set(this, null);
+ }
+ field = Throwable.class.getDeclaredField("detailMessage");
+ field.setAccessible(true);
+ field.set(this, message);
+ field = Throwable.class.getDeclaredField("cause");
+ field.setAccessible(true);
+ field.set(this, cause);
+ if (!enableSuppression) {
+ field = Throwable.class.getDeclaredField("suppressedExceptions");
+ field.setAccessible(true);
+ field.set(this, null);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ initClassContext();
+ }
+
+ @XmlTransient
+ @Deprecated
+ public Class[] getClassContext() {
+ Class<?>[] context = null;
+ if (classContext != null) {
+ context = new Class<?>[classContext.length];
+ for (int i = 0; i < classContext.length; i++) {
+ Class<?> c = classContext[i].get();
+ context[i] = c == null ? Object.class : c;
+ }
+ }
+ return context;
+ }
+
+ @SuppressWarnings("unchecked")
+ private void initClassContext() {
+ Class[] context = SecurityManagerEx.getInstance().getThrowableContext(this);
+ if (context != null) {
+ classContext = new Reference[context.length];
+ for (int i = 0; i < context.length; i++) {
+ classContext[i] = new WeakReference<Class<?>>(context[i]);
+ }
+ }
+ }
+
+ protected Class[] classContext() {
+ Class<?>[] context = new Class<?>[classContext.length];
+ for (int i = 0; i < classContext.length; i++) {
+ Class<?> c = classContext[i].get();
+ context[i] = c == null ? Object.class : c;
+ }
+ return context;
+ }
+
+ private static class SecurityManagerEx extends SecurityManager
+ {
+
+ private static SecurityManagerEx sm;
+
+ public static SecurityManagerEx getInstance() {
+ // No synchronized block because we don't really care
+ // if multiple instances are created at some point
+ if (sm == null) {
+ sm = new SecurityManagerEx();
+ }
+ return sm;
+ }
+
+ public Class[] getThrowableContext(Throwable t) {
+ try {
+ Class[] context = getClassContext();
+ int nb = 0;
+ for (;;) {
+ if (context[context.length - 1 - nb] == t.getClass()) {
+ break;
+ }
+ nb++;
+ }
+ Class[] nc = new Class[nb];
+ System.arraycopy(context, context.length - nb, nc, 0, nb);
+ return nc;
+ } catch (Exception e) {
+ return null;
+ }
+ }
+ }
+
+}
diff --git a/manual/src/main/asciidoc/user-guide/os-integration.adoc b/manual/src/main/asciidoc/user-guide/os-integration.adoc
index 70022e4..b6310a1 100644
--- a/manual/src/main/asciidoc/user-guide/os-integration.adoc
+++ b/manual/src/main/asciidoc/user-guide/os-integration.adoc
@@ -291,6 +291,8 @@ wrapper.java.additional.4=-Dkaraf.etc=%KARAF_ETC%
wrapper.java.additional.5=-Dcom.sun.management.jmxremote
wrapper.java.additional.6=-Dkaraf.startLocalConsole=false
wrapper.java.additional.7=-Dkaraf.startRemoteShell=true
+wrapper.java.additional.8=-Djava.endorsed.dirs=%JAVA_HOME%/jre/lib/endorsed:%JAVA_HOME%/lib/endorsed:%KARAF_HOME%/lib/endorsed
+wrapper.java.additional.9=-Djava.ext.dirs=%JAVA_HOME%/jre/lib/ext:%JAVA_HOME%/lib/ext:%KARAF_HOME%/lib/ext
# Uncomment to enable jmx
#wrapper.java.additional.n=-Dcom.sun.management.jmxremote.port=1616
diff --git a/pom.xml b/pom.xml
index 215eb11..6bead48 100644
--- a/pom.xml
+++ b/pom.xml
@@ -56,6 +56,7 @@
<module>web</module>
<module>wrapper</module>
<module>webconsole</module>
+ <module>exception</module>
<module>scheduler</module>
<module>scr</module>
<module>diagnostic</module>
@@ -390,6 +391,11 @@
</dependency>
<dependency>
<groupId>org.apache.karaf</groupId>
+ <artifactId>org.apache.karaf.exception</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.karaf</groupId>
<artifactId>org.apache.karaf.client</artifactId>
<version>${project.version}</version>
</dependency>
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 d9e156d..e48b6cf 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
@@ -715,10 +715,6 @@ public class Builder {
}
final String type = clause.getDirective(LIBRARY_CLAUSE_TYPE) != null
? clause.getDirective(LIBRARY_CLAUSE_TYPE) : Library.TYPE_DEFAULT;
- if (type == Library.TYPE_ENDORSED || type == Library.TYPE_EXTENSION) {
- LOGGER.warn("Ignoring library " + library + " which is of an unsupported type " + type + ".");
- continue;
- }
final String path;
switch (type) {
case Library.TYPE_ENDORSED: path = "lib/endorsed"; break;
diff --git a/wrapper/src/main/resources/org/apache/karaf/wrapper/internal/unix/karaf-wrapper.conf b/wrapper/src/main/resources/org/apache/karaf/wrapper/internal/unix/karaf-wrapper.conf
index 9bc2f9c..38c1afe 100644
--- a/wrapper/src/main/resources/org/apache/karaf/wrapper/internal/unix/karaf-wrapper.conf
+++ b/wrapper/src/main/resources/org/apache/karaf/wrapper/internal/unix/karaf-wrapper.conf
@@ -44,7 +44,9 @@ wrapper.java.additional.4=-Dkaraf.etc=%KARAF_ETC%
wrapper.java.additional.5=-Dcom.sun.management.jmxremote
wrapper.java.additional.6=-Dkaraf.startLocalConsole=false
wrapper.java.additional.7=-Dkaraf.startRemoteShell=true
-wrapper.java.additional.8=-Djava.io.tmpdir=%KARAF_DATA%/tmp
+wrapper.java.additional.8=-Djava.endorsed.dirs=%JAVA_HOME%/jre/lib/endorsed:%JAVA_HOME%/lib/endorsed:%KARAF_HOME%/lib/endorsed
+wrapper.java.additional.9=-Djava.ext.dirs=%JAVA_HOME%/jre/lib/ext:%JAVA_HOME%/lib/ext:%KARAF_HOME%/lib/ext
+wrapper.java.additional.10=-Djava.io.tmpdir=%KARAF_DATA%/tmp
# Uncomment to enable jmx
#wrapper.java.additional.n=-Dcom.sun.management.jmxremote.port=1616
diff --git a/wrapper/src/main/resources/org/apache/karaf/wrapper/internal/windows/karaf-wrapper.conf b/wrapper/src/main/resources/org/apache/karaf/wrapper/internal/windows/karaf-wrapper.conf
index 4765361..426761d 100644
--- a/wrapper/src/main/resources/org/apache/karaf/wrapper/internal/windows/karaf-wrapper.conf
+++ b/wrapper/src/main/resources/org/apache/karaf/wrapper/internal/windows/karaf-wrapper.conf
@@ -44,7 +44,9 @@ wrapper.java.additional.4=-Dkaraf.etc="%KARAF_ETC%"
wrapper.java.additional.5=-Dcom.sun.management.jmxremote
wrapper.java.additional.6=-Dkaraf.startLocalConsole=false
wrapper.java.additional.7=-Dkaraf.startRemoteShell=true
-wrapper.java.additional.8=-Djava.io.tmpdir="%KARAF_DATA%/tmp"
+wrapper.java.additional.8=-Djava.endorsed.dirs="%JAVA_HOME%/jre/lib/endorsed;%JAVA_HOME%/lib/endorsed;%KARAF_HOME%/lib/endorsed"
+wrapper.java.additional.9=-Djava.ext.dirs="%JAVA_HOME%/jre/lib/ext;%JAVA_HOME%/lib/ext;%KARAF_HOME%/lib/ext"
+wrapper.java.additional.10=-Djava.io.tmpdir="%KARAF_DATA%/tmp"
# Uncomment to enable jmx
#wrapper.java.additional.n=-Dcom.sun.management.jmxremote.port=1616
diff --git a/wrapper/src/main/resources/org/apache/karaf/wrapper/internal/windows64/karaf-wrapper.conf b/wrapper/src/main/resources/org/apache/karaf/wrapper/internal/windows64/karaf-wrapper.conf
index 4765361..426761d 100644
--- a/wrapper/src/main/resources/org/apache/karaf/wrapper/internal/windows64/karaf-wrapper.conf
+++ b/wrapper/src/main/resources/org/apache/karaf/wrapper/internal/windows64/karaf-wrapper.conf
@@ -44,7 +44,9 @@ wrapper.java.additional.4=-Dkaraf.etc="%KARAF_ETC%"
wrapper.java.additional.5=-Dcom.sun.management.jmxremote
wrapper.java.additional.6=-Dkaraf.startLocalConsole=false
wrapper.java.additional.7=-Dkaraf.startRemoteShell=true
-wrapper.java.additional.8=-Djava.io.tmpdir="%KARAF_DATA%/tmp"
+wrapper.java.additional.8=-Djava.endorsed.dirs="%JAVA_HOME%/jre/lib/endorsed;%JAVA_HOME%/lib/endorsed;%KARAF_HOME%/lib/endorsed"
+wrapper.java.additional.9=-Djava.ext.dirs="%JAVA_HOME%/jre/lib/ext;%JAVA_HOME%/lib/ext;%KARAF_HOME%/lib/ext"
+wrapper.java.additional.10=-Djava.io.tmpdir="%KARAF_DATA%/tmp"
# Uncomment to enable jmx
#wrapper.java.additional.n=-Dcom.sun.management.jmxremote.port=1616
--
To stop receiving notification emails like this one, please contact
"commits@karaf.apache.org" <co...@karaf.apache.org>.
[karaf] 06/07: [KARAF-5376] Polish LocationPattern and
FeaturePattern helpers for blacklist/override matching
Posted by gg...@apache.org.
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 5e1c44d7569b28b3e0e1b1b0431d2b2b5cd35128
Author: Grzegorz Grzybek <gr...@gmail.com>
AuthorDate: Wed Nov 15 15:27:34 2017 +0100
[KARAF-5376] Polish LocationPattern and FeaturePattern helpers for blacklist/override matching
---
.../org/apache/karaf/features/FeaturePattern.java | 108 +++++++++++++
.../{internal/service => }/LocationPattern.java | 23 +--
.../model/processing/BundleReplacements.java | 4 +-
.../model/processing/FeaturesProcessing.java | 5 +-
.../karaf/features/internal/service/Blacklist.java | 176 ++++++++-------------
.../internal/service/FeaturesProcessorImpl.java | 17 +-
.../features/karaf-features-processing-1.0.0.xsd | 2 +-
.../internal/service/FeaturePatternTest.java | 73 +++++++++
.../internal/service/LocationPatternTest.java | 1 +
9 files changed, 267 insertions(+), 142 deletions(-)
diff --git a/features/core/src/main/java/org/apache/karaf/features/FeaturePattern.java b/features/core/src/main/java/org/apache/karaf/features/FeaturePattern.java
new file mode 100644
index 0000000..06db75a
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/FeaturePattern.java
@@ -0,0 +1,108 @@
+/*
+ * 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;
+
+import java.util.regex.Pattern;
+
+import org.apache.felix.utils.manifest.Clause;
+import org.apache.felix.utils.version.VersionCleaner;
+import org.apache.felix.utils.version.VersionRange;
+import org.osgi.framework.Version;
+
+/**
+ * <p>Helper class to compare feature identifiers that may use globs and version ranges.</p>
+ *
+ * <p>Following feature identifiers are supported:<ul>
+ * <li>name (simple name)</li>
+ * <li>name/version (Karaf feature ID syntax)</li>
+ * <li>name/version-range (Karaf feature ID syntax using version-range)</li>
+ * <li>name;range=version (OSGi manifest header with <code>range</code> <em>attribute</em>)</li>
+ * <li>name;range=version-range (OSGi manifest header with <code>range</code> <em>attribute</em>)</li>
+ * </ul></p>
+ */
+public class FeaturePattern {
+
+ public static final String RANGE = "range";
+
+ private String originalId;
+ private Pattern namePattern;
+ private String versionString;
+ private Version version;
+ private VersionRange versionRange;
+
+ public FeaturePattern(String featureId) throws IllegalArgumentException {
+ if (featureId == null) {
+ throw new IllegalArgumentException("Feature ID to match should not be null");
+ }
+ originalId = featureId;
+ String name = originalId;
+ if (name.indexOf("/") > 0) {
+ name = originalId.substring(0, originalId.indexOf("/"));
+ versionString = originalId.substring(originalId.indexOf("/") + 1);
+ } else if (name.contains(";")) {
+ Clause[] c = org.apache.felix.utils.manifest.Parser.parseClauses(new String[] { originalId });
+ name = c[0].getName();
+ versionString = c[0].getAttribute(RANGE);
+ }
+ namePattern = LocationPattern.toRegExp(name);
+
+ 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) {
+ throw new IllegalArgumentException("Can't parse version \"" + versionString + "\" as OSGi version object.", e);
+ }
+ } else {
+ versionRange = new VersionRange(Version.emptyVersion);
+ }
+ }
+
+ /**
+ * Returns <code>if this feature pattern</code> matches given feature/version
+ * @param featureName
+ * @param featureVersion
+ * @return
+ */
+ public boolean matches(String featureName, String featureVersion) {
+ if (featureName == null) {
+ return false;
+ }
+ boolean match = namePattern.matcher(featureName).matches();
+ if (!match) {
+ return false;
+ }
+ if (featureVersion == null) {
+ featureVersion = "0";
+ }
+ Version otherVersion = new Version(VersionCleaner.clean(featureVersion));
+ if (versionRange != null) {
+ match = versionRange.contains(otherVersion);
+ } else if (version != null) {
+ match = version.equals(otherVersion);
+ }
+ return match;
+ }
+
+}
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/LocationPattern.java
similarity index 94%
rename from features/core/src/main/java/org/apache/karaf/features/internal/service/LocationPattern.java
rename to features/core/src/main/java/org/apache/karaf/features/LocationPattern.java
index 7e55b9b..20390ca 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/service/LocationPattern.java
+++ b/features/core/src/main/java/org/apache/karaf/features/LocationPattern.java
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.karaf.features.internal.service;
+package org.apache.karaf.features;
import java.net.MalformedURLException;
import java.util.regex.Matcher;
@@ -30,12 +30,15 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
- * <p>Helper class to compare Maven URIs that may use globs and version ranges.</p>
+ * <p>Helper class to compare Maven URIs (and falling back to other 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>
+ * classifier. Concrete URIs do not use globs and use precise versions (we do not consider <code>LATEST</code>
+ * and <code>RELEASE</code> Maven versions 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>
*/
@@ -110,7 +113,7 @@ public class LocationPattern {
* @param value
* @return
*/
- private Pattern toRegExp(String value) {
+ static Pattern toRegExp(String value) {
// TODO: escape all RegExp special chars that are valid path characters, only convert '*' into '.*'
return Pattern.compile(value
.replaceAll("\\.", "\\\\\\.")
@@ -133,10 +136,6 @@ public class LocationPattern {
// 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 {
@@ -145,6 +144,12 @@ public class LocationPattern {
LOG.debug("Can't parse \"" + otherUri + "\" as Maven URI. Ignoring.");
return false;
}
+
+ if (other.originalPattern != null) {
+ // other pattern is not mvn:
+ return false;
+ }
+
if (other.versionRange != null) {
LOG.warn("Matched URI can't use version ranges: " + otherUri);
return false;
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
index f51a8b9..ad309be 100644
--- 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
@@ -28,7 +28,7 @@ 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;
+import org.apache.karaf.features.LocationPattern;
@XmlType(name = "bundleReplacements", propOrder = {
"overrideBundles"
@@ -91,7 +91,7 @@ public class BundleReplacements {
}
/**
- * Changes String for <code>originalUri</code> into {@link org.apache.karaf.features.internal.service.LocationPattern}
+ * Changes String for <code>originalUri</code> into {@link 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/FeaturesProcessing.java b/features/core/src/main/java/org/apache/karaf/features/internal/model/processing/FeaturesProcessing.java
index 142a16b..c2a0765 100644
--- 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
@@ -38,7 +38,7 @@ 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.apache.karaf.features.LocationPattern;
import org.osgi.framework.Version;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -90,6 +90,9 @@ public class FeaturesProcessing {
private Blacklist blacklist;
public FeaturesProcessing() {
+ overrideBundleDependency = new OverrideBundleDependency();
+ bundleReplacements = new BundleReplacements();
+ featureReplacements = new FeatureReplacements();
}
public List<String> getBlacklistedRepositories() {
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 8c6bfb9..046c4be 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
@@ -25,33 +25,27 @@ 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.LinkedList;
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;
-import org.apache.felix.utils.version.VersionRange;
-import org.apache.felix.utils.version.VersionTable;
-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.FeaturePattern;
+import org.apache.karaf.features.LocationPattern;
import org.apache.karaf.features.internal.model.Features;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
- * Helper class to deal with blacklisted features and bundles.
+ * Helper class to deal with blacklisted features and bundles. It doesn't process JAXB model at all - it only
+ * provides information about repository/feature/bundle being blacklisted.
*/
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"; // null -> "feature"
public static final String TYPE_FEATURE = "feature";
public static final String TYPE_BUNDLE = "bundle";
@@ -59,7 +53,10 @@ public class Blacklist {
private static final Logger LOGGER = LoggerFactory.getLogger(Blacklist.class);
private Clause[] clauses;
- private Map<String, LocationPattern> bundleBlacklist = new LinkedHashMap<>();
+
+ private List<LocationPattern> repositoryBlacklist = new LinkedList<>();
+ private List<FeaturePattern> featureBlacklist = new LinkedList<>();
+ private List<LocationPattern> bundleBlacklist = new LinkedList<>();
public Blacklist() {
this(Collections.emptyList());
@@ -105,11 +102,33 @@ public class Blacklist {
type = TYPE_FEATURE;
}
}
+ String location;
switch (type) {
+ case TYPE_REPOSITORY:
+ location = c.getName();
+ if (c.getAttribute(BLACKLIST_URL) != null) {
+ location = c.getAttribute(BLACKLIST_URL);
+ }
+ if (location == null) {
+ // should not happen?
+ LOG.warn("Repository blacklist URI is empty. Ignoring.");
+ } else {
+ try {
+ repositoryBlacklist.add(new LocationPattern(location));
+ } catch (MalformedURLException e) {
+ LOG.warn("Problem parsing repository blacklist URI \"" + location + "\": " + e.getMessage() + ". Ignoring.");
+ }
+ }
+ break;
case TYPE_FEATURE:
+ try {
+ featureBlacklist.add(new FeaturePattern(c.toString()));
+ } catch (IllegalArgumentException e) {
+ LOG.warn("Problem parsing blacklisted feature identifier \"" + c.toString() + "\": " + e.getMessage() + ". Ignoring.");
+ }
break;
case TYPE_BUNDLE:
- String location = c.getName();
+ location = c.getName();
if (c.getAttribute(BLACKLIST_URL) != null) {
location = c.getAttribute(BLACKLIST_URL);
}
@@ -118,74 +137,24 @@ public class Blacklist {
LOG.warn("Bundle blacklist URI is empty. Ignoring.");
} else {
try {
- bundleBlacklist.put(location, location.startsWith("mvn:") ? new LocationPattern(location) : null);
+ bundleBlacklist.add(new LocationPattern(location));
} catch (MalformedURLException e) {
- LOG.warn("Problem parsing blacklist URI \"" + location + "\": " + e.getMessage() + ". Ignoring.");
+ LOG.warn("Problem parsing bundle blacklist URI \"" + location + "\": " + e.getMessage() + ". Ignoring.");
}
}
break;
- case TYPE_REPOSITORY:
}
}
}
/**
- * TODO: set {@link Feature#setBlacklisted(boolean)} instead of removing from collection
- * @param features
+ * Checks whether features XML repository URI is blacklisted.
+ * @param uri
+ * @return
*/
- public void blacklist(Features features) {
- features.getFeature().removeIf(this::blacklist);
- }
-
- public boolean blacklist(Feature feature) {
- for (Clause clause : clauses) {
- // Check feature name
- if (clause.getName().equals(feature.getName())) {
- // Check feature version
- VersionRange range = VersionRange.ANY_VERSION;
- String vr = clause.getAttribute(BLACKLIST_RANGE);
- if (vr != null) {
- range = new VersionRange(vr, true);
- }
- if (range.contains(VersionTable.getVersion(feature.getVersion()))) {
- String type = clause.getAttribute(BLACKLIST_TYPE);
- if (type == null || TYPE_FEATURE.equals(type)) {
- return true;
- }
- }
- }
- // Check bundles
- blacklist(feature.getBundle());
- // Check conditional bundles
- for (Conditional cond : feature.getConditional()) {
- blacklist(cond.getBundle());
- }
- }
- return false;
- }
-
- private void blacklist(List<Bundle> bundles) {
- for (Iterator<Bundle> iterator = bundles.iterator(); iterator.hasNext();) {
- Bundle info = iterator.next();
- for (Clause clause : clauses) {
- String url = clause.getName();
- if (clause.getAttribute(BLACKLIST_URL) != null) {
- url = clause.getAttribute(BLACKLIST_URL);
- }
- if (info.getLocation().equals(url)) {
- String type = clause.getAttribute(BLACKLIST_TYPE);
- if (type == null || TYPE_BUNDLE.equals(type)) {
- iterator.remove();
- break;
- }
- }
- }
- }
- }
-
- public boolean isBundleBlacklisted(String uri) {
- for (Map.Entry<String, LocationPattern> clause : bundleBlacklist.entrySet()) {
- if (mavenMatches(clause.getKey(), clause.getValue(), uri)) {
+ public boolean isRepositoryBlacklisted(String uri) {
+ for (LocationPattern pattern : repositoryBlacklist) {
+ if (pattern.matches(uri)) {
return true;
}
}
@@ -193,56 +162,30 @@ public class Blacklist {
}
/**
- * Checks whether given <code>uri</code> matches Maven artifact pattern (group, artifact, optional type/classifier, version
- * range, globs).
- * @param blacklistedUri
- * @param compiledUri
- * @param uri
+ * Checks whether the feature is blacklisted according to configured rules by name
+ * (possibly with wildcards) and optional version (possibly specified as version range)
+ * @param name
+ * @param version
* @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) {
- 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);
- if (vr != null) {
- range = new VersionRange(vr, true);
- }
- if (range.contains(VersionTable.getVersion(version))) {
- if (type == null || TYPE_FEATURE.equals(type)) {
- return true;
- }
- }
+ for (FeaturePattern pattern : featureBlacklist) {
+ if (pattern.matches(name, version)) {
+ return true;
}
}
return false;
}
- public boolean isRepositoryBlacklisted(String uri) {
- for (Clause clause : clauses) {
- String url = clause.getName();
- if (clause.getAttribute(BLACKLIST_URL) != null) {
- url = clause.getAttribute(BLACKLIST_URL);
- }
- if (uri.equals(url)) {
- String type = clause.getAttribute(BLACKLIST_TYPE);
- if (type == null || TYPE_REPOSITORY.equals(type)) {
- return true;
- }
+ /**
+ * Checks whether the bundle URI is blacklisted according to configured rules
+ * @param uri
+ * @return
+ */
+ public boolean isBundleBlacklisted(String uri) {
+ for (LocationPattern pattern : bundleBlacklist) {
+ if (pattern.matches(uri)) {
+ return true;
}
}
return false;
@@ -262,7 +205,9 @@ public class Blacklist {
System.arraycopy(others.clauses, ours.length, this.clauses, 0, others.clauses.length);
}
if (others != null) {
- this.bundleBlacklist.putAll(others.bundleBlacklist);
+ this.repositoryBlacklist.addAll(others.repositoryBlacklist);
+ this.featureBlacklist.addAll(others.featureBlacklist);
+ this.bundleBlacklist.addAll(others.bundleBlacklist);
}
}
@@ -270,4 +215,7 @@ public class Blacklist {
return clauses;
}
+ public void blacklist(Features featuresModel) {
+ }
+
}
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
index 03dadea..c14c559 100644
--- 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
@@ -29,15 +29,14 @@ import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import org.apache.karaf.features.BundleInfo;
+import org.apache.karaf.features.LocationPattern;
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;
@@ -55,7 +54,7 @@ public class FeaturesProcessorImpl implements FeaturesProcessor {
public static Logger LOG = LoggerFactory.getLogger(FeaturesProcessorImpl.class);
private static final JAXBContext FEATURES_PROCESSING_CONTEXT;
- private FeaturesProcessing processing;
+ private FeaturesProcessing processing = new FeaturesProcessing();
static {
try {
@@ -96,18 +95,6 @@ public class FeaturesProcessorImpl implements FeaturesProcessor {
}
}
- 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);
}
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
index d0e5d48..b82bf08 100644
--- 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
@@ -83,7 +83,7 @@ features, "range" manifest header attribute should be specified in "version" XML
<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="[2,*)" - features with all versions above and including 2.0.0 will be blacklisted
* version="3.0" - feature with version 3.0 only will be blacklisted
]]></xs:documentation>
</xs:annotation>
diff --git a/features/core/src/test/java/org/apache/karaf/features/internal/service/FeaturePatternTest.java b/features/core/src/test/java/org/apache/karaf/features/internal/service/FeaturePatternTest.java
new file mode 100644
index 0000000..c462b37
--- /dev/null
+++ b/features/core/src/test/java/org/apache/karaf/features/internal/service/FeaturePatternTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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 org.apache.karaf.features.FeaturePattern;
+import org.junit.Test;
+import org.osgi.framework.Version;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class FeaturePatternTest {
+
+ @Test
+ public void matchingFeatureIds() {
+ assertTrue(new FeaturePattern("spring").matches("spring", null));
+ assertTrue(new FeaturePattern("spring").matches("spring", "0.0.0"));
+ assertTrue(new FeaturePattern("spring").matches("spring", "1.0.0"));
+ assertFalse(new FeaturePattern("spring").matches("springish", "1.0.0"));
+ assertFalse(new FeaturePattern("commons/1").matches("commons", "0.0.0"));
+ assertFalse(new FeaturePattern("commons/1").matches("commons", null));
+ assertTrue(new FeaturePattern("commons/1").matches("commons", "1"));
+ assertFalse(new FeaturePattern("commons/1").matches("commons", "1.0.0.1"));
+ assertFalse(new FeaturePattern("space/[3,4]").matches("space", "1"));
+ assertTrue(new FeaturePattern("space/[3,4]").matches("space", "3"));
+ assertTrue(new FeaturePattern("space/[3,4]").matches("space", "3.1"));
+ assertFalse(new FeaturePattern("space/[3,4]").matches("x-space", "3.1"));
+ assertTrue(new FeaturePattern("space/[3,4]").matches("space", "4.0.0"));
+ assertFalse(new FeaturePattern("space/[3,4]").matches("space", "4.0.0.0")); // last ".0" is qualifier
+ assertFalse(new FeaturePattern("space/[3,4]").matches("space", "4.0.1"));
+ assertTrue(new FeaturePattern("special;range=1").matches("special", "1"));
+ assertTrue(new FeaturePattern("special;range=1").matches("special", "1.0"));
+ assertTrue(new FeaturePattern("special;range=1").matches("special", "1.0.0"));
+ assertFalse(new FeaturePattern("special;range=1").matches("special2", "1.0.0"));
+ assertFalse(new FeaturePattern("special;range=1").matches("special", "1.0.1"));
+ assertTrue(new FeaturePattern("universal;range=[3,4)").matches("universal", "3"));
+ assertTrue(new FeaturePattern("universal;range=[3,4)").matches("universal", "3.0"));
+ assertTrue(new FeaturePattern("universal;range=[3,4)").matches("universal", "3.0.0"));
+ assertTrue(new FeaturePattern("universal;range=[3,4)").matches("universal", "3.4.2"));
+ assertTrue(new FeaturePattern("universal;range=[3,4)").matches("universal", "3.9.9.GA"));
+ assertFalse(new FeaturePattern("universal;range=[3,4)").matches("universalis", "3.9.9.GA"));
+ assertFalse(new FeaturePattern("universal;range=[3,4)").matches("universal", "4.0.0"));
+ assertTrue(new FeaturePattern("a*").matches("alphabet", null));
+ assertTrue(new FeaturePattern("a*").matches("alphabet", "0"));
+ assertTrue(new FeaturePattern("a*").matches("alphabet", "999"));
+ assertFalse(new FeaturePattern("a*").matches("_alphabet", "999"));
+ assertTrue(new FeaturePattern("*b/[3,4)").matches("b", "3.5"));
+ assertTrue(new FeaturePattern("*b/[3,4)").matches("cb", "3.5"));
+ assertFalse(new FeaturePattern("*b/[3,4)").matches("bc", "3.5"));
+ assertFalse(new FeaturePattern("*b/[3,4)").matches("cb", "4.0.0"));
+ assertFalse(new FeaturePattern("*b/[3,4)").matches("cb", null));
+ assertFalse(new FeaturePattern("*b/[3,4)").matches("cb", "0"));
+ assertFalse(new FeaturePattern("*b/[3,4)").matches("cb", org.apache.karaf.features.internal.model.Feature.DEFAULT_VERSION));
+ assertFalse(new FeaturePattern("*b/[3,4)").matches("cb", Version.emptyVersion.toString()));
+ }
+
+}
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
index c0a7b31..3c0a808 100644
--- 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
@@ -20,6 +20,7 @@ package org.apache.karaf.features.internal.service;
import java.net.MalformedURLException;
+import org.apache.karaf.features.LocationPattern;
import org.junit.Test;
import static org.junit.Assert.assertFalse;
--
To stop receiving notification emails like this one, please contact
"commits@karaf.apache.org" <co...@karaf.apache.org>.