You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by da...@apache.org on 2018/04/27 09:51:20 UTC
[sling-org-apache-sling-feature] branch master created (now f04701f)
This is an automated email from the ASF dual-hosted git repository.
davidb pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-feature.git.
at f04701f [Feature Model] Move constants from support module to feature module
This branch includes the following new commits:
new 9da6695 Move feature model to whiteboard git
new 30803cd Add section about OBR
new a509190 Adjust sections about start levels
new e1a8504 Move readme to parent dir and add apicontroller proposal
new 7a98140 Fix ArtifactId.toMvnId to be symmetrical with fromMvnId
new 062f90b Start changing start level handling to start order handling
new 3d3750f Refactor start order handling
new a6468fc Change from start level to start order
new 5582860 Clarify update handling
new 1587e84 SLING-7512 Order features based on their dependencies.
new 7083fc7 Update to parent pom 33
new 5ec3548 SLING-7521 Order bundles in the generated app based on feature order and start order
new 6c70fe7 Support the slinstart maven plugin by providing a model converter API
new f3dffc7 Support for special feature names when converting to provisioning model.
new f4e132a Support variables in converting from features to provisioning model
new f2a3682 Allow null for attributes or requirements. Throw ISE if namespace is null
new 77d89a3 Move the process package from the feature api module to the support one.
new 4baad43 Move FeatureResource to support package.
new 176fb37 Move Sling specific constants into support module
new f14073d Refactor to use Capabilities and Requirements from Felix utils project
new ec2a23e Move Feature Model Builder into feature module.
new f04701f [Feature Model] Move constants from support module to feature module
The 22 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.
--
To stop receiving notification emails like this one, please contact
davidb@apache.org.
[sling-org-apache-sling-feature] 22/22: [Feature Model] Move
constants from support module to feature module
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
davidb pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-feature.git
commit f04701ff3c67321f4c82e82a345c25bc2bb47e36
Author: David Bosschaert <da...@gmail.com>
AuthorDate: Thu Apr 26 09:50:10 2018 +0100
[Feature Model] Move constants from support module to feature module
EXTENSION_NAME_REPOINIT and EXTENSION_NAME_CONTENT_PACKAGES were moved
from SlingConstants in feature-support to FeatureConstants in feature.
---
.../org/apache/sling/feature/FeatureConstants.java | 25 ++++++++++++++++++++++
1 file changed, 25 insertions(+)
diff --git a/src/main/java/org/apache/sling/feature/FeatureConstants.java b/src/main/java/org/apache/sling/feature/FeatureConstants.java
new file mode 100644
index 0000000..1cbfdf5
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/FeatureConstants.java
@@ -0,0 +1,25 @@
+/*
+ * 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.sling.feature;
+
+public class FeatureConstants {
+ /** Common extension name to specify the repoinit part for Apache Sling. */
+ public static final String EXTENSION_NAME_REPOINIT = "repoinit";
+
+ /** Common extension name to specify the content packages for Apache Sling. */
+ public static final String EXTENSION_NAME_CONTENT_PACKAGES = "content-packages";
+}
--
To stop receiving notification emails like this one, please contact
davidb@apache.org.
[sling-org-apache-sling-feature] 16/22: Allow null for attributes
or requirements. Throw ISE if namespace is null
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
davidb pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-feature.git
commit f2a36820d626b654dd68df113903710c0cb0e796
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Mon Apr 16 16:24:47 2018 +0200
Allow null for attributes or requirements. Throw ISE if namespace is null
---
.../feature/AbstractCapabilityRequirement.java | 56 ++++++++++++++--------
1 file changed, 37 insertions(+), 19 deletions(-)
diff --git a/src/main/java/org/apache/sling/feature/AbstractCapabilityRequirement.java b/src/main/java/org/apache/sling/feature/AbstractCapabilityRequirement.java
index 200cb92..8a4982f 100644
--- a/src/main/java/org/apache/sling/feature/AbstractCapabilityRequirement.java
+++ b/src/main/java/org/apache/sling/feature/AbstractCapabilityRequirement.java
@@ -23,30 +23,57 @@ import java.util.Map;
import org.osgi.resource.Resource;
abstract class AbstractCapabilityRequirement {
- private final Resource resource;
+
+ /** The namespace. Required. */
private final String namespace;
+
+ /** Optional resource. */
+ private final Resource resource;
+
+ /** Optional attributes. Never null. */
private final Map<String, Object> attributes;
+
+ /** Optional attributes. Never null. */
private final Map<String, String> directives;
- AbstractCapabilityRequirement(Resource res, String ns, Map<String, Object> attrs, Map<String, String> dirs) {
+ AbstractCapabilityRequirement(final Resource res, final String ns, final Map<String, Object> attrs, final Map<String, String> dirs) {
+ if ( ns == null ) {
+ throw new IllegalArgumentException("Namespace must not be null.");
+ }
resource = res;
namespace = ns;
- attributes = Collections.unmodifiableMap(new HashMap<>(attrs));
- directives = Collections.unmodifiableMap(new HashMap<>(dirs));
+ attributes = attrs == null ? Collections.emptyMap() : Collections.unmodifiableMap(new HashMap<>(attrs));
+ directives = dirs == null ? Collections.emptyMap() : Collections.unmodifiableMap(new HashMap<>(dirs));
}
+ /**
+ * Return the namespace.
+ * @return The namespace. This is never @{code null}.
+ */
public String getNamespace() {
return namespace;
}
+ /**
+ * Return the attributes.
+ * @return The attributes, might be empty.
+ */
public Map<String, Object> getAttributes() {
return attributes;
}
+ /**
+ * Return the directives.
+ * @return The directives, might be empty.
+ */
public Map<String, String> getDirectives() {
return directives;
}
+ /**
+ * Return the resource.
+ * @return The resource or @{code null}.
+ */
public Resource getResource() {
return resource;
}
@@ -55,9 +82,9 @@ abstract class AbstractCapabilityRequirement {
public int hashCode() {
final int prime = 31;
int result = 1;
- result = prime * result + ((attributes == null) ? 0 : attributes.hashCode());
- result = prime * result + ((directives == null) ? 0 : directives.hashCode());
- result = prime * result + ((namespace == null) ? 0 : namespace.hashCode());
+ result = prime * result + attributes.hashCode();
+ result = prime * result + directives.hashCode();
+ result = prime * result + namespace.hashCode();
return result;
}
@@ -70,20 +97,11 @@ abstract class AbstractCapabilityRequirement {
if (getClass() != obj.getClass())
return false;
AbstractCapabilityRequirement other = (AbstractCapabilityRequirement) obj;
- if (attributes == null) {
- if (other.attributes != null)
- return false;
- } else if (!attributes.equals(other.attributes))
+ if (!namespace.equals(other.namespace))
return false;
- if (directives == null) {
- if (other.directives != null)
- return false;
- } else if (!directives.equals(other.directives))
+ if (!attributes.equals(other.attributes))
return false;
- if (namespace == null) {
- if (other.namespace != null)
- return false;
- } else if (!namespace.equals(other.namespace))
+ if (!directives.equals(other.directives))
return false;
return true;
}
--
To stop receiving notification emails like this one, please contact
davidb@apache.org.
[sling-org-apache-sling-feature] 18/22: Move FeatureResource to
support package.
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
davidb pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-feature.git
commit 4baad43875ef90c0a9462c7799bef923c86ac8b9
Author: David Bosschaert <da...@gmail.com>
AuthorDate: Tue Apr 17 09:45:11 2018 +0300
Move FeatureResource to support package.
---
.../org/apache/sling/feature/FeatureResource.java | 50 ----------------------
1 file changed, 50 deletions(-)
diff --git a/src/main/java/org/apache/sling/feature/FeatureResource.java b/src/main/java/org/apache/sling/feature/FeatureResource.java
deleted file mode 100644
index 38b0b43..0000000
--- a/src/main/java/org/apache/sling/feature/FeatureResource.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with this
- * work for additional information regarding copyright ownership. The ASF
- * licenses this file to You under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package org.apache.sling.feature;
-
-import org.osgi.framework.Version;
-import org.osgi.resource.Resource;
-
-/**
- * A Resource that is associated with an Maven Artifact and belongs to a Feature.
- */
-public interface FeatureResource extends Resource {
- /**
- * Obtain the ID of the resource. If the resource is a bundle then this
- * is the bundle symbolic name.
- * @return The ID of the resource.
- */
- String getId();
-
- /**
- * Obtain the version of the resource.
- * @return The version of the resource.
- */
- Version getVersion();
-
- /**
- * Obtain the associated (Maven) Artifact.
- * @return The artifact for this Resource.
- */
- Artifact getArtifact();
-
- /**
- * Obtain the feature that contains this resource.
- * @return The feature that contains the resource.
- */
- Feature getFeature();
-}
--
To stop receiving notification emails like this one, please contact
davidb@apache.org.
[sling-org-apache-sling-feature] 13/22: Support the slinstart maven
plugin by providing a model converter API
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
davidb pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-feature.git
commit 6c70fe7bca7663dd5a2cf0c523761e9a6c90f577
Author: David Bosschaert <da...@gmail.com>
AuthorDate: Fri Mar 9 10:46:10 2018 +0000
Support the slinstart maven plugin by providing a model converter API
---
.../java/org/apache/sling/feature/KeyValueMap.java | 23 ++++++++++++++++++----
.../sling/feature/process/ApplicationBuilder.java | 21 +++++++++++++-------
2 files changed, 33 insertions(+), 11 deletions(-)
diff --git a/src/main/java/org/apache/sling/feature/KeyValueMap.java b/src/main/java/org/apache/sling/feature/KeyValueMap.java
index ce8c1c8..675ee21 100644
--- a/src/main/java/org/apache/sling/feature/KeyValueMap.java
+++ b/src/main/java/org/apache/sling/feature/KeyValueMap.java
@@ -28,7 +28,7 @@ public class KeyValueMap
implements Iterable<Map.Entry<String, String>> {
/** The map holding the actual key value pairs. */
- private final Map<String, String> properties = new TreeMap<>();
+ private final Map<String, Object> properties = new TreeMap<>();
/**
* Get an item from the map.
@@ -36,6 +36,14 @@ public class KeyValueMap
* @return The item or {@code null}.
*/
public String get(final String key) {
+ Object val = this.properties.get(key);
+ if (val instanceof String) {
+ return (String) val;
+ }
+ return null;
+ }
+
+ public Object getObject(final String key) {
return this.properties.get(key);
}
@@ -44,7 +52,7 @@ public class KeyValueMap
* @param key The key of the item.
* @param value The value
*/
- public void put(final String key, final String value) {
+ public void put(final String key, final Object value) {
this.properties.put(key, value);
}
@@ -53,7 +61,7 @@ public class KeyValueMap
* @param key The key of the item.
* @return The previously stored value for the key or {@code null}.
*/
- public String remove(final String key) {
+ public Object remove(final String key) {
return this.properties.remove(key);
}
@@ -67,7 +75,14 @@ public class KeyValueMap
@Override
public Iterator<Entry<String, String>> iterator() {
- return this.properties.entrySet().iterator();
+ // TODO hack
+ Map<String, String> copied = new TreeMap<>();
+ for (Entry<String, Object> entry : properties.entrySet()) {
+ if (entry.getValue() instanceof String) {
+ copied.put(entry.getKey(), (String) entry.getValue());
+ }
+ }
+ return copied.entrySet().iterator();
}
/**
diff --git a/src/main/java/org/apache/sling/feature/process/ApplicationBuilder.java b/src/main/java/org/apache/sling/feature/process/ApplicationBuilder.java
index 2a9698a..71fc8b0 100644
--- a/src/main/java/org/apache/sling/feature/process/ApplicationBuilder.java
+++ b/src/main/java/org/apache/sling/feature/process/ApplicationBuilder.java
@@ -23,6 +23,7 @@ import org.apache.sling.feature.Feature;
import org.apache.sling.feature.FeatureResource;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
/**
@@ -117,15 +118,21 @@ public class ApplicationBuilder {
}
}
- // order by dependency chain
- final List<FeatureResource> sortedResources = resolver.orderResources(featureList);
+ final List<Feature> sortedFeatures;
+ if (resolver != null) {
+ // order by dependency chain
+ final List<FeatureResource> sortedResources = resolver.orderResources(featureList);
- final List<Feature> sortedFeatures = new ArrayList<>();
- for (final FeatureResource fr : sortedResources) {
- Feature f = fr.getFeature();
- if (!sortedFeatures.contains(f)) {
- sortedFeatures.add(f);
+ sortedFeatures = new ArrayList<>();
+ for (final FeatureResource fr : sortedResources) {
+ Feature f = fr.getFeature();
+ if (!sortedFeatures.contains(f)) {
+ sortedFeatures.add(f);
+ }
}
+ } else {
+ sortedFeatures = featureList;
+ Collections.sort(sortedFeatures);
}
// assemble
--
To stop receiving notification emails like this one, please contact
davidb@apache.org.
[sling-org-apache-sling-feature] 19/22: Move Sling specific
constants into support module
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
davidb pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-feature.git
commit 176fb37b5c8ada4f583fdb990ca33a159ab90b5e
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Tue Apr 17 10:31:04 2018 +0200
Move Sling specific constants into support module
---
src/main/java/org/apache/sling/feature/Extension.java | 6 ------
1 file changed, 6 deletions(-)
diff --git a/src/main/java/org/apache/sling/feature/Extension.java b/src/main/java/org/apache/sling/feature/Extension.java
index a3e8082..df10287 100644
--- a/src/main/java/org/apache/sling/feature/Extension.java
+++ b/src/main/java/org/apache/sling/feature/Extension.java
@@ -31,12 +31,6 @@ import java.util.List;
*/
public class Extension {
- /** Common extension name to specify the repoinit part for Apache Sling. */
- public static final String NAME_REPOINIT = "repoinit";
-
- /** Common extension name to specify the content packages for Apache Sling. */
- public static final String NAME_CONTENT_PACKAGES = "content-packages";
-
/** The extension type */
private final ExtensionType type;
--
To stop receiving notification emails like this one, please contact
davidb@apache.org.
[sling-org-apache-sling-feature] 04/22: Move readme to parent dir
and add apicontroller proposal
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
davidb pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-feature.git
commit e1a8504033e55b284b900c9575f52872a768012c
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Mon Nov 6 14:25:05 2017 +0100
Move readme to parent dir and add apicontroller proposal
---
readme.md | 260 --------------------------------------------------------------
1 file changed, 260 deletions(-)
diff --git a/readme.md b/readme.md
deleted file mode 100644
index 6dbfc07..0000000
--- a/readme.md
+++ /dev/null
@@ -1,260 +0,0 @@
-# Prototype for a new Provisioning / Configuration Model for OSGi based applications
-
-The current model to describe OSGi applications is based on Apache Sling's provisioning model (see https://sling.apache.org/documentation/development/slingstart.html)
-
-## Short description of Sling's provisioning model:
-
-* Text based file format, defining features (several in a single file)
-* A feature can have a name and version (both optional)
-* A feature consists of sections, well defined ones like the run modes and user defined sections
-* A run mode has artifacts (with start levels), configurations and settings (framework properties)
-* Variables can be used throughout a feature
-* Inheritance is supported on a feature base through artifacts
-* Configuration merging is possible
-
-## Advantages of the provisioning model
-
-* Well known by Sling developers, has been introduced some years ago. Some tooling around it
-* Very concise, especially for defining artifacts
-* Extensible, custom sections can be added, e.g used by Sling for repoinit, subsystem definitions, content package definitions
-* Easy diff
-* Special API with semantics related to the prov model (not a general purpose config API)
-
-## Disadvantages of the provisioning model
-
-* Single file can contain more than one feature
-* Custom DSL - no standard format (like JSON)
-* Inheritance and custom artifacts (content packages) are mixed with bundles, which makes processing and understanding more complicated
-* Adding additional info to artifacts looks strange
-* Two formats for configurations and now there is an official JSON based format defined through OSGi R7
-* Strange object relationship between feature and run modes
-* API (object relation) differs from text file (to make the text format easier)
-* Tooling only available as maven plugins, not separate usable
-* Run mode handling is complicating the feature files and processing of those
-* Tightly coupled with the way Sling's launchpad works, therefore no independent OSGi format
-
-# Design Criteria for a model
-
-* A feature is a separate file with a required name and version
-* A feature can include other features
-* No support for run modes in the model - run modes can be modeled through separate features
-* OSGi JSON format for configurations
-* Support for standard OSGi artifacts as well as custom artifacts (like content packages)
-* Support OSGi requirements and capabilities for dependency handling
-
-# Prototype
-
-The prototype uses JSON as a well defined and understood format. This fits nicely with the new OSGi R7 JSON format for configurations.
-
-A model file describes a feature. A feature consists of:
-* A unique id and version (see Feature Identity below)
-* A list of bundles described through maven coordinates
- * Grouped by start level (required)
- * Additional metadata like a hash etc. (optional)
- * Configurations (optional)
-* A set of global configurations
-* A set of framework properties
-* A list of provided capabilities
-* A list of required capabilities
-* A set of includes (of other features) described through maven coordinates
- * Modifications (removals) of the includes (optional)
-* Extensions (optional)
- * A list of repoinit instructions
- * A set of content packages described through maven coordinates
- * Additional metadata like a hash etc. (optional)
- * Configurations (optional)
-
-# Feature Identity
-
-A feature is uniquely identified through maven coordinates, it has a group id, an artifact id and a version. In addition it might have a classifier.
-
-TBD We need to define a common type for such a feature. It could be "osgifeature" (but is this a good type? slingfeature, slingstart are taken, osgifeature might be too general)
-
-# Maven coordinates
-
-Maven coordinates are used to describe artifacts, e.g. bundles, content packages or includes. In these cases either the short notation (as described here: https://maven.apache.org/pom.html#Maven_Coordinates) can be used or the long version as a JSON object with an id property.
-
-# Requirements and Capabilities vs Dependencies
-
-In order to avoid a concept like "Require-Bundle" a feature does not explicitely declare dependencies to other features. These are declared by the required capabilities, either explicit or implicit. The implicit requirements are calculated by inspecting the contained bundles (and potentially other artifacts like content packages ).
-
-Once a feature is processed by tooling, the tooling might create a full list of requirements and capabilities and add this information in a special section to the final feature. This information can be used by tooling to validate an instance (see below) and avoids rescanning the binary artifacts. However this "cached" information is optional and tooling must work without it (which means it needs access to the binaries in that case). TBD the name and format of this information.
-
-# Includes
-
-Includes allow an aggregation of features and a modification of the included feature: each entity listed in the included feature can be removed, e.g a configuration or a bundle. The list of includes must not contain duplicates (not comparing the version of the includes). If there are duplicates, the feature is invalid.
-
-Once a feature is processed, included references are removed and the content of the included features becomes part of the current feature. The following algorithm applies:
-
-* Includes are processed in the order they are defined in the model. The current feature (containing the includes) is used last which means the algorithm starts with the first included feature.
-* Removal instructions for an include are handled first
-* A clash of bundles or content packages is resolved by picking the latest version (not the highest!)
-* Configurations will be merged by default, later ones potentially overriding newer ones:
- * If the same property is declared in more than one feature, the last one wins - in case of an array value, this requires redeclaring all values (if they are meant to be kept)
- * Configurations can be bound to a bundle. When two features are merged, all cases can occur: both might be bound to the same bundle (symbolic name), both might not be bound, they might be bound to different bundles (symbolic name), or one might be bound and the other one might not. As configurations are handled as a set regardless of whether they are bound to a bundle or not, the information of the belonging bundle is handled like a property in the configuration. This means:
- * If the last configuration belongs to a bundle, this relationship is kept
- * If the last configuration does not belong to a bundle and has no property removal instruction, the relationship from the first bundle is used (if there is one)
- * If the last configuration has a property removal instruction for the bundle relationship, the resulting configuration is unbound
-* Later framework properties overwrite newer ones
-* Capabilities and requirements are appended - this might result in duplicates, but that doesn't really hurt in practice.
-* Extensions are handled in an extension specific way:
- * repoinit is just aggregated (appended)
- * artifact extensions are handled like bundles
-
-While includes must not be used for assembling an application, they provide an important concept for manipulating existing features. For example to replace a bundle in an existing feature and deliver this modified feature.
-
-# Extensions
-
-An extension has a unique name and a type which can either be text, JSON or artifacts. Depending on the type, inheritance is performed like this:
-* For type text: simple appended
-* For type JSON: merging of the JSON structure, later arriving properties overriding existing ones
-* For type artifacts: merging of the artifacts, higher version wins
-
-# Handling of Environments
-
-A feature itself has no special support for environments (prod, test, dev). In practice it is very unlikely that a single file exists containing configurations for all environments, especially as the configuration might contain secrets, credentials, urls for production services etc which are not meant to be given out in public (or to the dev department). Instead, a separate feature for an environment can be written and maintained by the different share holders which adds the environment [...]
-
-# Bundles and start levels
-
-Each bundle needs to be explicitly assigned to a start level. There is no default start level as a default start level is not defined in the OSGi spec. In addition, it is a little bit confusing when looking at the model when there is a list of bundles without a start level. Which start level do these have? It is better to be explicit.
-
-However as soon as you have more than one feature and especially if these are authored by different authors, start level handling becomes more tricky. Assigning correct OSGi start levels in such scenarios would require to know all features upfront. Therefore this start level information is interpret as follows: instead of directly mapping it to a start level in the OSGi framework, it defines just the startup order of bundles within a feature. Features are then started in respect of their [...]
-
-# Configurations belonging to Bundles
-
-In most cases, configurations belong to a bundle. The most common use case is a configuration for a (DS) component. Therefore instead of having a separate configurations section, it is more intuitiv to specify configurations as part of a bundle. The benefit of this approach is, that it can easily be decided if a configuration is to be used: if exactly that bundle is used, the configurations are used; otherwise they are not.
-
-However, there might be situations where it is not clear to which bundle a configuration belongs or the configuration might be a cross cutting concern spawning across multiple bundles. Therefore it is still possible to have configurations not related to a particular bundle.
-
-In fact, configurations - whether they are declared as part of a bundle or not - are all managed in a single set for a feature. See above for how includes etc. are handled.
-
-# Example
-
-This is a feature example:
-
- {
- "id" : "org.apache.sling:my.app:feature:optional:1.0",
-
- "includes" : [
- {
- "id" : "org.apache.sling:sling:9",
- "removals" : {
- "configurations" : [
- ],
- "bundles": [
- ],
- "framework-properties" : [
- ]
- }
- }
- ],
- "requirements" : [
- {
- "namespace" : "osgi.contract",
- "directives" : {
- "filter" : "(&(osgi.contract=JavaServlet)(version=3.1))"
- }
- }
- ],
- "capabilities" : [
- {
- "namespace" : "osgi.implementation",
- "attributes" : {
- "osgi.implementation" : "osgi.http",
- "version:Version" : "1.1"
- },
- "directives" : {
- "uses" : "javax.servlet,javax.servlet.http,org.osgi.service.http.context,org.osgi.service.http.whiteboard"
- }
- },
- {
- "namespace" : osgi.service",
- "attributes" : {
- "objectClass:List<String>" : "org.osgi.service.http.runtime.HttpServiceRuntime"
- },
- "directives" {
- "uses" : "org.osgi.service.http.runtime,org.osgi.service.http.runtime.dto"
- }
- }
- ],
- "framework-properties" {
- "foo" : 1,
- "brave" : "something",
- "org.apache.felix.scr.directory" : "launchpad/scr"
- },
- "bundles" : {
- "1" : [
- {
- "id" : "org.apache.sling:security-server:2.2.0",
- "hash" : "4632463464363646436"
- },
- "org.apache.sling:application-bundle:2.0.0",
- "org.apache.sling:another-bundle:2.1.0"
- ],
- "2" : [
- "org.apache.sling:foo-xyz:1.2.3"
- ]
- },
- "configurations" {
- "my.pid" {
- "foo" : 5,
- "bar" : "test",
- "number:Integer" : 7
- },
- "my.factory.pid~name" {
- "a.value" : "yeah"
- }
- }
-
-# Relation to Repository Specification (Chapter 132)
-
-There are two major differences between a repository as described in the Repository Service Description and the feature model. A repository contains a list of more or less unrelated resources whereas a feature describes resources as a unit. For example a feature allows to define a bundle together with OSGi configurations - which ensures that whenever this feature is used, the bundle *together with* the configurations are deployed. A repository can only describe the bundle as a separate r [...]
-
-The second difference is the handling of requirements and capabilities. While a repository is supposed to list all requirements and capabilities of a resource as part of the description, the feature model does not require this. As the feature model refers to the bundle and the bundle has the requirements and capabilities as metadata, there is no need to repeat that information.
-
-By these two differences you can already tell, that a repository contents is usually generated by tools while a feature is usually a human created resource. While it is possible to create a repository index out of a feature, the other way round does not work as the repository has no standard way to define relationships between resources.
-
-# Requirements and Capabilities of Artifacts
-
-The feature model does not allow to explicitly list requirements or capabilities for artifacts. An artifact, for example a bundle, contains this information as part of its metadata. However, to calculate or display these, the tool processing the feature needs to have access to the artifact and needs to extract this. While in general this does not pose a problem by itself, it might happen that the same artifact is processed several times for example during a build process, causing overhead.
-
-To avoid this, a feature might contain an additional section, named "reqscaps" (TODO find a better name). This section is grouped by artifact ids and contains the requirements and capabilities of each artifact. While the requirements and capabilities of a single artifact must be correct and neither leave out or add additional ones, the list of artifacts must not be complete. Tooling will first look into this section to get requirements and capabilities for an artifact. If there are none, [...]
-
- {
- ...
- "reqscaps" : {
- "org.apache.sling:org.apache.sling.scripting.jsp:1.0.0" : {
- "capabilities" : [],
- "requirements" : []
- }
- }
-
-
-# Provisioning Applications
-
-An application jar can contain a set of features (including the listed artifacts).
-
-An optional application configuration further defines the possibilites:
-
- {
- "features" : [
- "org.apache.sling:org.apache.sling.launchpad:10"
- ],
- "options" : [
- "org.apache.sling:org.apache.sling.scripting.jsp:1.0.0",
- {
- "id" : "org.apache.sling:org.apache.sling.scripting.htl:1.0.0",
- "tag": "htl"
- }
- ],
- "defaults" : {
- "auto-add-options": true,
- "tags" : ["htl"]
- },
- "framework" : {
- "id" : "org.apache.felix:org.apache.felix.framework:5.6.4"
- }
- }
-
-Such a configuration is required for an application, at least one feature needs to be listed in either the features or the options section.
-All features listed in the features section will be added to the application, the ones listed in options are optional and depending on the settings and user input will either be added or left out. In addition all available features of an application will be used to make the application runnable (resolvable).
--
To stop receiving notification emails like this one, please contact
davidb@apache.org.
[sling-org-apache-sling-feature] 03/22: Adjust sections about start
levels
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
davidb pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-feature.git
commit a5091906bdf9ec63b159251410b50c6e1b2d26eb
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Mon Nov 6 12:59:33 2017 +0100
Adjust sections about start levels
---
readme.md | 8 +++-----
1 file changed, 3 insertions(+), 5 deletions(-)
diff --git a/readme.md b/readme.md
index facc995..6dbfc07 100644
--- a/readme.md
+++ b/readme.md
@@ -1,4 +1,4 @@
-# Prototype for a new Provisioning Model - Configuration Model for OSGi based applications
+# Prototype for a new Provisioning / Configuration Model for OSGi based applications
The current model to describe OSGi applications is based on Apache Sling's provisioning model (see https://sling.apache.org/documentation/development/slingstart.html)
@@ -116,11 +116,9 @@ A feature itself has no special support for environments (prod, test, dev). In p
# Bundles and start levels
-For bundles there is no default start level - a default start level is not defined in the OSGi spec. And in addition, it is a little bit confusing when looking at the model when there is a list of artifacts without a start level. Which start level do these have? Its better to be explicit.
+Each bundle needs to be explicitly assigned to a start level. There is no default start level as a default start level is not defined in the OSGi spec. In addition, it is a little bit confusing when looking at the model when there is a list of bundles without a start level. Which start level do these have? It is better to be explicit.
-In the current PoC, a bundle needs to be explicitely assigned to a start level. This seems to be only working if you know all the features in advance and how they are structured. On the other hand there needs to be a way to define the start order of bundles within a feature. Therefore we can use the start level information as an ordering information for the bundles within a feature. Bundles within the same start level are started in any order.
-
-Proposal: We use the format as it is today, but interpret the start level value different: instead of directly mapping it to a start level in the OSGi framework, it defines just the startup order of bundles within a feature. Features are then started in respect of their dependency information. Even if a feature has no requirement with respect to start ordering of their bundles, it has to define a start level (to act as a container for the bundles). It can use any positive number, suggest [...]
+However as soon as you have more than one feature and especially if these are authored by different authors, start level handling becomes more tricky. Assigning correct OSGi start levels in such scenarios would require to know all features upfront. Therefore this start level information is interpret as follows: instead of directly mapping it to a start level in the OSGi framework, it defines just the startup order of bundles within a feature. Features are then started in respect of their [...]
# Configurations belonging to Bundles
--
To stop receiving notification emails like this one, please contact
davidb@apache.org.
[sling-org-apache-sling-feature] 17/22: Move the process package
from the feature api module to the support one.
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
davidb pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-feature.git
commit 77d89a31442d2bc72650d8dcfab25422cbacc107
Author: David Bosschaert <da...@gmail.com>
AuthorDate: Wed Apr 11 16:51:50 2018 +0100
Move the process package from the feature api module to the support one.
Also move the associated tests.
---
.../sling/feature/process/ApplicationBuilder.java | 177 ------------
.../sling/feature/process/BuilderContext.java | 67 -----
.../apache/sling/feature/process/BuilderUtil.java | 261 ------------------
.../sling/feature/process/FeatureBuilder.java | 182 -------------
.../feature/process/FeatureExtensionHandler.java | 55 ----
.../sling/feature/process/FeatureProvider.java | 30 --
.../sling/feature/process/FeatureResolver.java | 39 ---
.../apache/sling/feature/process/package-info.java | 23 --
.../sling/feature/process/BuilderUtilTest.java | 153 -----------
.../sling/feature/process/FeatureBuilderTest.java | 302 ---------------------
10 files changed, 1289 deletions(-)
diff --git a/src/main/java/org/apache/sling/feature/process/ApplicationBuilder.java b/src/main/java/org/apache/sling/feature/process/ApplicationBuilder.java
deleted file mode 100644
index 71fc8b0..0000000
--- a/src/main/java/org/apache/sling/feature/process/ApplicationBuilder.java
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sling.feature.process;
-
-import org.apache.sling.feature.Application;
-import org.apache.sling.feature.Artifact;
-import org.apache.sling.feature.ArtifactId;
-import org.apache.sling.feature.Feature;
-import org.apache.sling.feature.FeatureResource;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Build an application based on features.
- */
-public class ApplicationBuilder {
-
- /**
- * Assemble an application based on the provided feature Ids.
- *
- * Upgrade features are only applied if the provided feature list
- * contains the feature to be upgraded. Otherwise the upgrade feature
- * is ignored.
- *
- * @param app The optional application to use as a base.
- * @param context The builder context
- * @param resolver The Feature Resolver to use
- * @param featureIds The feature ids
- * @return The application
- * throws IllegalArgumentException If context or featureIds is {@code null}
- * throws IllegalStateException If the provided ids are invalid, or the feature can't be provided
- */
- public static Application assemble(final Application app,
- final BuilderContext context,
- final FeatureResolver resolver,
- final String... featureIds) {
- if ( featureIds == null || context == null ) {
- throw new IllegalArgumentException("Features and/or context must not be null");
- }
-
- final Feature[] features = new Feature[featureIds.length];
- int index = 0;
- for(final String id : featureIds) {
- features[index] = context.getFeatureProvider().provide(ArtifactId.parse(id));
- if ( features[index] == null ) {
- throw new IllegalStateException("Unable to find included feature " + id);
- }
- index++;
- }
- return assemble(app, context, resolver, features);
- }
-
- /**
- * Assemble an application based on the provided features.
- *
- * If the same feature is included more than once only the feature with
- * the highest version is used. The others are ignored.
- *
- * @param app The optional application to use as a base.
- * @param context The builder context
- * @param resolver The Feature Resolver to use
- * @param features The features
- * @return The application
- * throws IllegalArgumentException If context or featureIds is {@code null}
- * throws IllegalStateException If a feature can't be provided
- */
- public static Application assemble(
- Application app,
- final BuilderContext context,
- final FeatureResolver resolver, final Feature... features) {
- if ( features == null || context == null ) {
- throw new IllegalArgumentException("Features and/or context must not be null");
- }
-
- if ( app == null ) {
- app = new Application();
- }
-
- // Remove duplicate features by selecting the one with the highest version
- final List<Feature> featureList = new ArrayList<>();
- for(final Feature f : features) {
- Feature found = null;
- for(final Feature s : featureList) {
- if ( s.getId().isSame(f.getId()) ) {
- found = s;
- break;
- }
- }
- boolean add = true;
- // feature with different version found
- if ( found != null ) {
- if ( f.getId().getOSGiVersion().compareTo(found.getId().getOSGiVersion()) <= 0 ) {
- // higher version already included
- add = false;
- } else {
- // remove lower version, higher version will be added
- featureList.remove(found);
- }
- }
- if ( add ) {
- featureList.add(f);
- }
- }
-
- final List<Feature> sortedFeatures;
- if (resolver != null) {
- // order by dependency chain
- final List<FeatureResource> sortedResources = resolver.orderResources(featureList);
-
- sortedFeatures = new ArrayList<>();
- for (final FeatureResource fr : sortedResources) {
- Feature f = fr.getFeature();
- if (!sortedFeatures.contains(f)) {
- sortedFeatures.add(f);
- }
- }
- } else {
- sortedFeatures = featureList;
- Collections.sort(sortedFeatures);
- }
-
- // assemble
- int featureStartOrder = 5; // begin with start order a little higher than 0
- for(final Feature f : sortedFeatures) {
- app.getFeatureIds().add(f.getId());
- final Feature assembled = FeatureBuilder.assemble(f, context.clone(new FeatureProvider() {
-
- @Override
- public Feature provide(final ArtifactId id) {
- for(final Feature f : features) {
- if ( f.getId().equals(id) ) {
- return f;
- }
- }
- return context.getFeatureProvider().provide(id);
- }
- }));
-
- int globalStartOrder = featureStartOrder;
- for (Artifact a : assembled.getBundles()) {
- int so = a.getStartOrder() + featureStartOrder;
- if (so > globalStartOrder)
- globalStartOrder = so;
- a.setStartOrder(so);
- }
- // Next feature will have a higher start order than the previous
- featureStartOrder = globalStartOrder + 1;
-
- merge(app, assembled);
- }
-
- return app;
- }
-
- private static void merge(final Application target, final Feature source) {
- BuilderUtil.mergeBundles(target.getBundles(), source.getBundles(), BuilderUtil.ArtifactMerge.HIGHEST);
- BuilderUtil.mergeConfigurations(target.getConfigurations(), source.getConfigurations());
- BuilderUtil.mergeFrameworkProperties(target.getFrameworkProperties(), source.getFrameworkProperties());
- BuilderUtil.mergeExtensions(target, source, BuilderUtil.ArtifactMerge.HIGHEST);
- }
-}
diff --git a/src/main/java/org/apache/sling/feature/process/BuilderContext.java b/src/main/java/org/apache/sling/feature/process/BuilderContext.java
deleted file mode 100644
index 024076d..0000000
--- a/src/main/java/org/apache/sling/feature/process/BuilderContext.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sling.feature.process;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Builder context holds services used by {@link ApplicationBuilder}
- * and {@link FeatureBuilder}.
- */
-public class BuilderContext {
-
- private final FeatureProvider provider;
-
- private final List<FeatureExtensionHandler> featureExtensionHandlers = new ArrayList<>();
-
- /**
- * Assemble the full feature by processing all includes.
- *
- * @param feature The feature to start
- * @param provider A provider providing the included features
- * @param extensionMergers Optional feature mergers
- * @return The assembled feature.
- * @throws IllegalArgumentException If feature or provider is {@code null}
- * @throws IllegalStateException If an included feature can't be provided or merged.
- */
- public BuilderContext(final FeatureProvider provider) {
- if ( provider == null ) {
- throw new IllegalArgumentException("Provider must not be null");
- }
- this.provider = provider;
- }
-
- FeatureProvider getFeatureProvider() {
- return this.provider;
- }
-
- List<FeatureExtensionHandler> getFeatureExtensionHandlers() {
- return this.featureExtensionHandlers;
- }
-
- public BuilderContext add(final FeatureExtensionHandler handler) {
- featureExtensionHandlers.add(handler);
- return this;
- }
-
- BuilderContext clone(final FeatureProvider featureProvider) {
- final BuilderContext ctx = new BuilderContext(featureProvider);
- ctx.featureExtensionHandlers.addAll(featureExtensionHandlers);
- return ctx;
- }
-}
diff --git a/src/main/java/org/apache/sling/feature/process/BuilderUtil.java b/src/main/java/org/apache/sling/feature/process/BuilderUtil.java
deleted file mode 100644
index 90da88e..0000000
--- a/src/main/java/org/apache/sling/feature/process/BuilderUtil.java
+++ /dev/null
@@ -1,261 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sling.feature.process;
-
-import java.io.StringReader;
-import java.util.Enumeration;
-import java.util.List;
-import java.util.Map;
-
-import javax.json.Json;
-import javax.json.JsonArray;
-import javax.json.JsonObject;
-import javax.json.JsonStructure;
-import javax.json.JsonValue;
-import javax.json.JsonValue.ValueType;
-
-import org.apache.sling.feature.Application;
-import org.apache.sling.feature.Artifact;
-import org.apache.sling.feature.Bundles;
-import org.apache.sling.feature.Configuration;
-import org.apache.sling.feature.Configurations;
-import org.apache.sling.feature.Extension;
-import org.apache.sling.feature.Feature;
-import org.apache.sling.feature.KeyValueMap;
-import org.osgi.resource.Capability;
-import org.osgi.resource.Requirement;
-
-/**
- * Utility methods for the builders
- */
-class BuilderUtil {
-
- public enum ArtifactMerge {
- LATEST,
- HIGHEST
- };
-
- // bundles
- static void mergeBundles(final Bundles target,
- final Bundles source,
- final ArtifactMerge artifactMergeAlg) {
- for(final Map.Entry<Integer, List<Artifact>> entry : source.getBundlesByStartOrder().entrySet()) {
- for(final Artifact a : entry.getValue()) {
- // version handling - use provided algorithm
- boolean replace = true;
- if ( artifactMergeAlg == ArtifactMerge.HIGHEST ) {
- final Artifact existing = target.getSame(a.getId());
- if ( existing != null && existing.getId().getOSGiVersion().compareTo(a.getId().getOSGiVersion()) > 0 ) {
- replace = false;
- }
- }
- if ( replace ) {
- target.removeSame(a.getId());
- target.add(a);
- }
- }
- }
- }
-
- // configurations - merge / override
- static void mergeConfigurations(final Configurations target, final Configurations source) {
- for(final Configuration cfg : source) {
- boolean found = false;
- for(final Configuration current : target) {
- if ( current.compareTo(cfg) == 0 ) {
- found = true;
- // merge / override properties
- final Enumeration<String> i = cfg.getProperties().keys();
- while ( i.hasMoreElements() ) {
- final String key = i.nextElement();
- current.getProperties().put(key, cfg.getProperties().get(key));
- }
- break;
- }
- }
- if ( !found ) {
- target.add(cfg);
- }
- }
- }
-
- // framework properties (add/merge)
- static void mergeFrameworkProperties(final KeyValueMap target, final KeyValueMap source) {
- target.putAll(source);
- }
-
- // requirements (add)
- static void mergeRequirements(final List<Requirement> target, final List<Requirement> source) {
- for(final Requirement req : source) {
- if ( !target.contains(req) ) {
- target.add(req);
- }
- }
- }
-
- // capabilities (add)
- static void mergeCapabilities(final List<Capability> target, final List<Capability> source) {
- for(final Capability cap : source) {
- if ( !target.contains(cap) ) {
- target.add(cap);
- }
- }
- }
-
- // default merge for extensions
- static void mergeExtensions(final Extension target,
- final Extension source,
- final ArtifactMerge artifactMergeAlg) {
- switch ( target.getType() ) {
- case TEXT : // simply append
- target.setText(target.getText() + "\n" + source.getText());
- break;
- case JSON : final JsonStructure struct1;
- try ( final StringReader reader = new StringReader(target.getJSON()) ) {
- struct1 = Json.createReader(reader).read();
- }
- final JsonStructure struct2;
- try ( final StringReader reader = new StringReader(source.getJSON()) ) {
- struct2 = Json.createReader(reader).read();
- }
-
- if ( struct1.getValueType() != struct2.getValueType() ) {
- throw new IllegalStateException("Found different JSON types for extension " + target.getName()
- + " : " + struct1.getValueType() + " and " + struct2.getValueType());
- }
- if ( struct1.getValueType() == ValueType.ARRAY ) {
- // array is append
- final JsonArray a1 = (JsonArray)struct1;
- final JsonArray a2 = (JsonArray)struct2;
- for(final JsonValue val : a2) {
- a1.add(val);
- }
- } else {
- // object is merge
- merge((JsonObject)struct1, (JsonObject)struct2);
- }
- break;
-
- case ARTIFACTS : for(final Artifact a : source.getArtifacts()) {
- // use artifactMergeAlg
- boolean add = true;
- for(final Artifact targetArtifact : target.getArtifacts()) {
- if ( targetArtifact.getId().isSame(a.getId()) ) {
- if ( artifactMergeAlg == ArtifactMerge.HIGHEST ) {
- if ( targetArtifact.getId().getOSGiVersion().compareTo(a.getId().getOSGiVersion()) > 0 ) {
- add = false;
- } else {
- target.getArtifacts().remove(targetArtifact);
- }
- } else { // latest
-
- target.getArtifacts().remove(targetArtifact);
- }
- break;
- }
- }
-
- if ( add ) {
- target.getArtifacts().add(a);
- }
-
- }
- break;
- }
- }
-
- // extensions (add/merge)
- static void mergeExtensions(final Feature target,
- final Feature source,
- final ArtifactMerge artifactMergeAlg,
- final BuilderContext context) {
- for(final Extension ext : source.getExtensions()) {
- boolean found = false;
- for(final Extension current : target.getExtensions()) {
- if ( current.getName().equals(ext.getName()) ) {
- found = true;
- if ( current.getType() != ext.getType() ) {
- throw new IllegalStateException("Found different types for extension " + current.getName()
- + " : " + current.getType() + " and " + ext.getType());
- }
- boolean handled = false;
- for(final FeatureExtensionHandler fem : context.getFeatureExtensionHandlers()) {
- if ( fem.canMerge(current.getName()) ) {
- fem.merge(target, source, current.getName());
- handled = true;
- break;
- }
- }
- if ( !handled ) {
- // default merge
- mergeExtensions(current, ext, artifactMergeAlg);
- }
- }
- }
- if ( !found ) {
- target.getExtensions().add(ext);
- }
- }
- }
-
- static void mergeExtensions(final Application target,
- final Feature source,
- final ArtifactMerge artifactMergeAlg) {
- for(final Extension ext : source.getExtensions()) {
- boolean found = false;
- for(final Extension current : target.getExtensions()) {
- if ( current.getName().equals(ext.getName()) ) {
- found = true;
- if ( current.getType() != ext.getType() ) {
- throw new IllegalStateException("Found different types for extension " + current.getName()
- + " : " + current.getType() + " and " + ext.getType());
- }
- // default merge
- mergeExtensions(current, ext, artifactMergeAlg);
- }
- }
- if ( !found ) {
- target.getExtensions().add(ext);
- }
- }
- }
-
- private static void merge(final JsonObject obj1, final JsonObject obj2) {
- for(final Map.Entry<String, JsonValue> entry : obj2.entrySet()) {
- if ( !obj1.containsKey(entry.getKey()) ) {
- obj1.put(entry.getKey(), entry.getValue());
- } else {
- final JsonValue oldValue = obj1.get(entry.getKey());
- if ( oldValue.getValueType() != entry.getValue().getValueType() ) {
- // new type wins
- obj1.put(entry.getKey(), entry.getValue());
- } else if ( oldValue.getValueType() == ValueType.ARRAY ) {
- final JsonArray a1 = (JsonArray)oldValue;
- final JsonArray a2 = (JsonArray)entry.getValue();
- for(final JsonValue val : a2) {
- a1.add(val);
- }
-
- } else if ( oldValue.getValueType() == ValueType.OBJECT ) {
- merge((JsonObject)oldValue, (JsonObject)entry.getValue());
- } else {
- obj1.put(entry.getKey(), entry.getValue());
- }
- }
- }
- }
-}
diff --git a/src/main/java/org/apache/sling/feature/process/FeatureBuilder.java b/src/main/java/org/apache/sling/feature/process/FeatureBuilder.java
deleted file mode 100644
index 6d57dc6..0000000
--- a/src/main/java/org/apache/sling/feature/process/FeatureBuilder.java
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sling.feature.process;
-
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
-import org.apache.sling.feature.Artifact;
-import org.apache.sling.feature.ArtifactId;
-import org.apache.sling.feature.Configuration;
-import org.apache.sling.feature.Extension;
-import org.apache.sling.feature.Feature;
-import org.apache.sling.feature.Include;
-
-public class FeatureBuilder {
-
- /**
- * Assemble the full feature by processing all includes.
- *
- * @param feature The feature to start
- * @param context The builder context
- * @return The assembled feature.
- * @throws IllegalArgumentException If feature or context is {@code null}
- * @throws IllegalStateException If an included feature can't be provided or merged.
- */
- public static Feature assemble(final Feature feature,
- final BuilderContext context) {
- if ( feature == null || context == null ) {
- throw new IllegalArgumentException("Feature and/or context must not be null");
- }
- return internalAssemble(new ArrayList<>(), feature, context);
- }
-
- private static Feature internalAssemble(final List<String> processedFeatures,
- final Feature feature,
- final BuilderContext context) {
- if ( feature.isAssembled() ) {
- return feature;
- }
- if ( processedFeatures.contains(feature.getId().toMvnId()) ) {
- throw new IllegalStateException("Recursive inclusion of " + feature.getId().toMvnId() + " via " + processedFeatures);
- }
- processedFeatures.add(feature.getId().toMvnId());
-
- // we copy the feature as we set the assembled flag on the result
- final Feature result = feature.copy();
-
- if ( !result.getIncludes().isEmpty() ) {
-
- final List<Include> includes = new ArrayList<>(result.getIncludes());
-
- // clear everything in the result, will be added in the process
- result.getBundles().clear();
- result.getFrameworkProperties().clear();
- result.getConfigurations().clear();
- result.getRequirements().clear();
- result.getCapabilities().clear();
- result.getIncludes().clear();
- result.getExtensions().clear();
-
- for(final Include i : includes) {
- final Feature f = context.getFeatureProvider().provide(i.getId());
- if ( f == null ) {
- throw new IllegalStateException("Unable to find included feature " + i.getId());
- }
- final Feature af = internalAssemble(processedFeatures, f, context);
-
- // process include instructions
- include(af, i);
-
- // and now merge
- merge(result, af, context);
- }
- merge(result, feature, context);
- }
- processedFeatures.remove(feature.getId().toMvnId());
-
- result.setAssembled(true);
- return result;
- }
-
- private static void merge(final Feature target,
- final Feature source,
- final BuilderContext context) {
- BuilderUtil.mergeBundles(target.getBundles(), source.getBundles(), BuilderUtil.ArtifactMerge.LATEST);
- BuilderUtil.mergeConfigurations(target.getConfigurations(), source.getConfigurations());
- BuilderUtil.mergeFrameworkProperties(target.getFrameworkProperties(), source.getFrameworkProperties());
- BuilderUtil.mergeRequirements(target.getRequirements(), source.getRequirements());
- BuilderUtil.mergeCapabilities(target.getCapabilities(), source.getCapabilities());
- BuilderUtil.mergeExtensions(target,
- source,
- BuilderUtil.ArtifactMerge.LATEST,
- context);
- }
-
- private static void include(final Feature base, final Include i) {
- // process removals
- // bundles
- for(final ArtifactId a : i.getBundleRemovals()) {
- base.getBundles().removeExact(a);
- final Iterator<Configuration> iter = base.getConfigurations().iterator();
- while ( iter.hasNext() ) {
- final Configuration cfg = iter.next();
- final String bundleId = (String)cfg.getProperties().get(Configuration.PROP_ARTIFACT);
- if ( a.toMvnId().equals(bundleId) ) {
- iter.remove();
- }
- }
- }
- // configurations
- for(final String c : i.getConfigurationRemovals()) {
- final int attrPos = c.indexOf('@');
- final String val = (attrPos == -1 ? c : c.substring(0, attrPos));
- final String attr = (attrPos == -1 ? null : c.substring(attrPos + 1));
-
- final int sepPos = val.indexOf('~');
- Configuration found = null;
- if ( sepPos == -1 ) {
- found = base.getConfigurations().getConfiguration(val);
-
- } else {
- final String factoryPid = val.substring(0, sepPos);
- final String name = val.substring(sepPos + 1);
-
- found = base.getConfigurations().getFactoryConfiguration(factoryPid, name);
- }
- if ( found != null ) {
- if ( attr == null ) {
- base.getConfigurations().remove(found);
- } else {
- found.getProperties().remove(attr);
- }
- }
- }
-
- // framework properties
- for(final String p : i.getFrameworkPropertiesRemovals()) {
- base.getFrameworkProperties().remove(p);
- }
-
- // extensions
- for(final String name : i.getExtensionRemovals()) {
- for(final Extension ext : base.getExtensions()) {
- if ( ext.getName().equals(name) ) {
- base.getExtensions().remove(ext);
- break;
- }
- }
- }
- for(final Map.Entry<String, List<ArtifactId>> entry : i.getArtifactExtensionRemovals().entrySet()) {
- for(final Extension ext : base.getExtensions()) {
- if ( ext.getName().equals(entry.getKey()) ) {
- for(final ArtifactId id : entry.getValue() ) {
- for(final Artifact a : ext.getArtifacts()) {
- if ( a.getId().equals(id) ) {
- ext.getArtifacts().remove(a);
- break;
- }
- }
- }
- break;
- }
- }
- }
- }
-}
diff --git a/src/main/java/org/apache/sling/feature/process/FeatureExtensionHandler.java b/src/main/java/org/apache/sling/feature/process/FeatureExtensionHandler.java
deleted file mode 100644
index c2a9460..0000000
--- a/src/main/java/org/apache/sling/feature/process/FeatureExtensionHandler.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sling.feature.process;
-
-import org.apache.sling.feature.Feature;
-
-/**
- * A feature extension handler can merge a feature of a particular type
- * and also post process the final assembled feature.
- */
-public interface FeatureExtensionHandler {
-
- /**
- * Checks whether this merger can merge extensions with that name
- * @param extensionName The extension name
- * @return {@code true} if merger can handle this
- */
- boolean canMerge(String extensionName);
-
- /**
- * Merge the source extension into the target extension.
- *
- * The caller of this method already ensured that both
- * extensions share the same name and type and that
- * {@link #canMerge(String)} returned {@code true}.
- *
- * @param target The target feature
- * @param source The source feature
- * @param extensionName The extension name
- * @throws IllegalStateException If the extensions can't be merged
- */
- void merge(Feature target, Feature source, String extensionName);
-
- /**
- * Post process the feature with respect to the extension
- * @param feature The feature
- * @param extensionName The extension name
- * @throws IllegalStateException If post processing failed
- */
- void postProcess(Feature feature, String extensionName);
-}
diff --git a/src/main/java/org/apache/sling/feature/process/FeatureProvider.java b/src/main/java/org/apache/sling/feature/process/FeatureProvider.java
deleted file mode 100644
index 8d3c35a..0000000
--- a/src/main/java/org/apache/sling/feature/process/FeatureProvider.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sling.feature.process;
-
-import org.apache.sling.feature.ArtifactId;
-import org.apache.sling.feature.Feature;
-
-public interface FeatureProvider {
-
- /**
- * Provide the feature with the given id.
- * @param id The feature id
- * @return The feature or {@code null}
- */
- Feature provide(ArtifactId id);
-}
diff --git a/src/main/java/org/apache/sling/feature/process/FeatureResolver.java b/src/main/java/org/apache/sling/feature/process/FeatureResolver.java
deleted file mode 100644
index 5fbba7c..0000000
--- a/src/main/java/org/apache/sling/feature/process/FeatureResolver.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with this
- * work for additional information regarding copyright ownership. The ASF
- * licenses this file to You under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package org.apache.sling.feature.process;
-
-import java.util.List;
-
-import org.apache.sling.feature.Feature;
-import org.apache.sling.feature.FeatureResource;
-
-/**
- * A resolver that can perform operations on the feature model.
- */
-public interface FeatureResolver extends AutoCloseable {
- /**
- * Order the resources in list of features by their dependency chain.
- * Each feature and its components are resolved. Then all the resources
- * in the feature are ordered so that each resource is placed before
- * the requiring feature/resources in the result.
- *
- * @param features
- * The features to order.
- * @return The ordered resources from the features.
- */
- List<FeatureResource> orderResources(List<Feature> features);
-}
diff --git a/src/main/java/org/apache/sling/feature/process/package-info.java b/src/main/java/org/apache/sling/feature/process/package-info.java
deleted file mode 100644
index f6d563a..0000000
--- a/src/main/java/org/apache/sling/feature/process/package-info.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-@org.osgi.annotation.versioning.Version("1.0.0")
-package org.apache.sling.feature.process;
-
-
diff --git a/src/test/java/org/apache/sling/feature/process/BuilderUtilTest.java b/src/test/java/org/apache/sling/feature/process/BuilderUtilTest.java
deleted file mode 100644
index 42ce069..0000000
--- a/src/test/java/org/apache/sling/feature/process/BuilderUtilTest.java
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sling.feature.process;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-import org.apache.sling.feature.Artifact;
-import org.apache.sling.feature.ArtifactId;
-import org.apache.sling.feature.Bundles;
-import org.apache.sling.feature.BundlesTest;
-import org.apache.sling.feature.process.BuilderUtil.ArtifactMerge;
-import org.junit.Test;
-
-public class BuilderUtilTest {
-
- private List<Map.Entry<Integer, Artifact>> getBundles(final Bundles f) {
- final List<Map.Entry<Integer, Artifact>> result = new ArrayList<>();
- for(final Map.Entry<Integer, List<Artifact>> entry : f.getBundlesByStartOrder().entrySet()) {
- for(final Artifact artifact : entry.getValue()) {
- result.add(new Map.Entry<Integer, Artifact>() {
-
- @Override
- public Integer getKey() {
- return entry.getKey();
- }
-
- @Override
- public Artifact getValue() {
- return artifact;
- }
-
- @Override
- public Artifact setValue(Artifact value) {
- return null;
- }
- });
- }
- }
-
- return result;
- }
-
- private void assertContains(final List<Map.Entry<Integer, Artifact>> bundles,
- final int level, final ArtifactId id) {
- for(final Map.Entry<Integer, Artifact> entry : bundles) {
- if ( entry.getKey().intValue() == level
- && entry.getValue().getId().equals(id) ) {
- return;
- }
- }
- fail(id.toMvnId());
- }
-
- @Test public void testMergeBundlesWithAlgHighest() {
- final Bundles target = new Bundles();
-
- target.add(BundlesTest.createBundle("g/a/1.0", 1));
- target.add(BundlesTest.createBundle("g/b/2.0", 2));
- target.add(BundlesTest.createBundle("g/c/2.5", 3));
-
- final Bundles source = new Bundles();
- source.add(BundlesTest.createBundle("g/a/1.1", 1));
- source.add(BundlesTest.createBundle("g/b/1.9", 2));
- source.add(BundlesTest.createBundle("g/c/2.5", 3));
-
- BuilderUtil.mergeBundles(target, source, ArtifactMerge.HIGHEST);
-
- final List<Map.Entry<Integer, Artifact>> result = getBundles(target);
- assertEquals(3, result.size());
- assertContains(result, 1, ArtifactId.parse("g/a/1.1"));
- assertContains(result, 2, ArtifactId.parse("g/b/2.0"));
- assertContains(result, 3, ArtifactId.parse("g/c/2.5"));
- }
-
- @Test public void testMergeBundlesWithAlgLatest() {
- final Bundles target = new Bundles();
-
- target.add(BundlesTest.createBundle("g/a/1.0", 1));
- target.add(BundlesTest.createBundle("g/b/2.0", 2));
- target.add(BundlesTest.createBundle("g/c/2.5", 3));
-
- final Bundles source = new Bundles();
- source.add(BundlesTest.createBundle("g/a/1.1", 1));
- source.add(BundlesTest.createBundle("g/b/1.9", 2));
- source.add(BundlesTest.createBundle("g/c/2.5", 3));
-
- BuilderUtil.mergeBundles(target, source, ArtifactMerge.LATEST);
-
- final List<Map.Entry<Integer, Artifact>> result = getBundles(target);
- assertEquals(3, result.size());
- assertContains(result, 1, ArtifactId.parse("g/a/1.1"));
- assertContains(result, 2, ArtifactId.parse("g/b/1.9"));
- assertContains(result, 3, ArtifactId.parse("g/c/2.5"));
- }
-
- @Test public void testMergeBundlesDifferentStartlevel() {
- final Bundles target = new Bundles();
-
- target.add(BundlesTest.createBundle("g/a/1.0", 1));
-
- final Bundles source = new Bundles();
- source.add(BundlesTest.createBundle("g/a/1.1", 2));
-
- BuilderUtil.mergeBundles(target, source, ArtifactMerge.LATEST);
-
- final List<Map.Entry<Integer, Artifact>> result = getBundles(target);
- assertEquals(1, result.size());
- assertContains(result, 2, ArtifactId.parse("g/a/1.1"));
- }
-
- @Test public void testMergeBundles() {
- final Bundles target = new Bundles();
-
- target.add(BundlesTest.createBundle("g/a/1.0", 1));
- target.add(BundlesTest.createBundle("g/b/2.0", 2));
- target.add(BundlesTest.createBundle("g/c/2.5", 3));
-
- final Bundles source = new Bundles();
- source.add(BundlesTest.createBundle("g/d/1.1", 1));
- source.add(BundlesTest.createBundle("g/e/1.9", 2));
- source.add(BundlesTest.createBundle("g/f/2.5", 3));
-
- BuilderUtil.mergeBundles(target, source, ArtifactMerge.LATEST);
-
- final List<Map.Entry<Integer, Artifact>> result = getBundles(target);
- assertEquals(6, result.size());
- assertContains(result, 1, ArtifactId.parse("g/a/1.0"));
- assertContains(result, 2, ArtifactId.parse("g/b/2.0"));
- assertContains(result, 3, ArtifactId.parse("g/c/2.5"));
- assertContains(result, 1, ArtifactId.parse("g/d/1.1"));
- assertContains(result, 2, ArtifactId.parse("g/e/1.9"));
- assertContains(result, 3, ArtifactId.parse("g/f/2.5"));
- }
-}
diff --git a/src/test/java/org/apache/sling/feature/process/FeatureBuilderTest.java b/src/test/java/org/apache/sling/feature/process/FeatureBuilderTest.java
deleted file mode 100644
index ca27cf5..0000000
--- a/src/test/java/org/apache/sling/feature/process/FeatureBuilderTest.java
+++ /dev/null
@@ -1,302 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sling.feature.process;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import org.apache.sling.feature.Artifact;
-import org.apache.sling.feature.ArtifactId;
-import org.apache.sling.feature.BundlesTest;
-import org.apache.sling.feature.Configuration;
-import org.apache.sling.feature.Extension;
-import org.apache.sling.feature.Feature;
-import org.apache.sling.feature.Include;
-import org.apache.sling.feature.OSGiCapability;
-import org.apache.sling.feature.OSGiRequirement;
-import org.junit.Test;
-import org.osgi.resource.Capability;
-import org.osgi.resource.Requirement;
-
-public class FeatureBuilderTest {
-
- private static final Map<String, Feature> FEATURES = new HashMap<>();
-
- static {
- final Feature f1 = new Feature(ArtifactId.parse("g/a/1"));
-
- f1.getFrameworkProperties().put("foo", "2");
- f1.getFrameworkProperties().put("bar", "X");
-
- f1.getBundles().add(BundlesTest.createBundle("org.apache.sling/foo-bar/4.5.6", 3));
- f1.getBundles().add(BundlesTest.createBundle("group/testnewversion_low/2", 5));
- f1.getBundles().add(BundlesTest.createBundle("group/testnewversion_high/2", 5));
- f1.getBundles().add(BundlesTest.createBundle("group/testnewstartlevel/1", 5));
- f1.getBundles().add(BundlesTest.createBundle("group/testnewstartlevelandversion/1", 5));
-
- final Configuration c1 = new Configuration("org.apache.sling.foo");
- c1.getProperties().put("prop", "value");
- f1.getConfigurations().add(c1);
-
- FEATURES.put(f1.getId().toMvnId(), f1);
- }
-
- private final FeatureProvider provider = new FeatureProvider() {
-
- @Override
- public Feature provide(final ArtifactId id) {
- return FEATURES.get(id.getGroupId() + ":" + id.getArtifactId() + ":" + id.getVersion());
- }
- };
-
- private List<Map.Entry<Integer, Artifact>> getBundles(final Feature f) {
- final List<Map.Entry<Integer, Artifact>> result = new ArrayList<>();
- for(final Map.Entry<Integer, List<Artifact>> entry : f.getBundles().getBundlesByStartOrder().entrySet()) {
- for(final Artifact artifact : entry.getValue()) {
- result.add(new Map.Entry<Integer, Artifact>() {
-
- @Override
- public Integer getKey() {
- return entry.getKey();
- }
-
- @Override
- public Artifact getValue() {
- return artifact;
- }
-
- @Override
- public Artifact setValue(Artifact value) {
- return null;
- }
- });
- }
- }
-
- return result;
- }
-
- private void equals(final Feature expected, final Feature actuals) {
- assertFalse(expected.isAssembled());
- assertTrue(actuals.isAssembled());
-
- assertEquals(expected.getId(), actuals.getId());
- assertEquals(expected.getTitle(), actuals.getTitle());
- assertEquals(expected.getDescription(), actuals.getDescription());
- assertEquals(expected.getVendor(), actuals.getVendor());
- assertEquals(expected.getLicense(), actuals.getLicense());
-
- // bundles
- final List<Map.Entry<Integer, Artifact>> expectedBundles = getBundles(expected);
- final List<Map.Entry<Integer, Artifact>> actualsBundles = getBundles(actuals);
- assertEquals(expectedBundles.size(), actualsBundles.size());
- for(final Map.Entry<Integer, Artifact> entry : expectedBundles) {
- boolean found = false;
- for(final Map.Entry<Integer, Artifact> inner : actualsBundles) {
- if ( inner.getValue().getId().equals(entry.getValue().getId()) ) {
- found = true;
- assertEquals("Startlevel of bundle " + entry.getValue(), entry.getKey(), inner.getKey());
- assertEquals("Metadata of bundle " + entry.getValue(), entry.getValue().getMetadata(), inner.getValue().getMetadata());
- break;
- }
- }
- assertTrue("Bundle " + entry.getValue() + " in level " + entry.getKey(), found);
- }
-
- // configurations
- assertEquals(expected.getConfigurations().size(), actuals.getConfigurations().size());
- for(final Configuration cfg : expected.getConfigurations()) {
- final Configuration found = (cfg.isFactoryConfiguration() ? actuals.getConfigurations().getFactoryConfiguration(cfg.getFactoryPid(), cfg.getName())
- : actuals.getConfigurations().getConfiguration(cfg.getPid()));
- assertNotNull("Configuration " + cfg, found);
- assertEquals("Configuration " + cfg, cfg.getProperties(), found.getProperties());
- }
-
- // frameworkProperties
- assertEquals(expected.getFrameworkProperties(), actuals.getFrameworkProperties());
-
- // requirements
- assertEquals(expected.getRequirements().size(), actuals.getRequirements().size());
- for(final Requirement r : expected.getRequirements()) {
- boolean found = false;
- for(final Requirement i : actuals.getRequirements()) {
- if ( r.equals(i) ) {
- found = true;
- break;
- }
- }
- assertTrue(found);
- }
-
- // capabilities
- assertEquals(expected.getCapabilities().size(), actuals.getCapabilities().size());
- for(final Capability r : expected.getCapabilities()) {
- boolean found = false;
- for(final Capability i : actuals.getCapabilities()) {
- if ( r.equals(i) ) {
- found = true;
- break;
- }
- }
- assertTrue(found);
- }
-
- // extensions
- assertEquals(expected.getExtensions().size(), actuals.getExtensions().size());
- for(final Extension ext : expected.getExtensions()) {
- final Extension inner = actuals.getExtensions().getByName(ext.getName());
- assertNotNull(inner);
- assertEquals(ext.getType(), inner.getType());
- switch ( ext.getType()) {
- case JSON : assertEquals(ext.getJSON(), inner.getJSON());
- break;
- case TEXT : assertEquals(ext.getText(), inner.getText());
- break;
- case ARTIFACTS : assertEquals(ext.getArtifacts().size(), inner.getArtifacts().size());
- for(final Artifact art : ext.getArtifacts()) {
- boolean found = false;
- for(final Artifact i : inner.getArtifacts()) {
- if ( art.getId().equals(i.getId()) ) {
- found = true;
- assertEquals(art.getMetadata(), i.getMetadata());
- break;
- }
- }
- assertTrue(found);
- }
- }
- }
-
- // includes should always be empty
- assertTrue(actuals.getIncludes().isEmpty());
- }
-
- @Test public void testNoIncludesNoUpgrade() throws Exception {
- final Feature base = new Feature(ArtifactId.parse("org.apache.sling/test-feature/1.1"));
-
- final Requirement r1 = new OSGiRequirement("osgi.contract",
- Collections.emptyMap(), Collections.singletonMap("filter", "(&(osgi.contract=JavaServlet)(version=3.1))"));
- base.getRequirements().add(r1);
-
- Map<String, Object> attrs = new HashMap<>();
- attrs.put("osgi.implementation", "osgi.http");
- attrs.put("version:Version", "1.1");
- final Capability c1 = new OSGiCapability("osgi.implementation", attrs,
- Collections.singletonMap("uses", "javax.servlet,javax.servlet.http,org.osgi.service.http.context,org.osgi.service.http.whiteboard"));
- base.getCapabilities().add(c1);
- final Capability c2 = new OSGiCapability("osgi.service",
- Collections.singletonMap("objectClass:List<String>", "org.osgi.service.http.runtime.HttpServiceRuntime"),
- Collections.singletonMap("uses", "org.osgi.service.http.runtime,org.osgi.service.http.runtime.dto"));
- base.getCapabilities().add(c2);
-
- base.getFrameworkProperties().put("foo", "1");
- base.getFrameworkProperties().put("brave", "something");
- base.getFrameworkProperties().put("org.apache.felix.scr.directory", "launchpad/scr");
-
- final Artifact a1 = new Artifact(ArtifactId.parse("org.apache.sling/oak-server/1.0.0"));
- a1.getMetadata().put(Artifact.KEY_START_ORDER, "1");
- a1.getMetadata().put("hash", "4632463464363646436");
- base.getBundles().add(a1);
- base.getBundles().add(BundlesTest.createBundle("org.apache.sling/application-bundle/2.0.0", 1));
- base.getBundles().add(BundlesTest.createBundle("org.apache.sling/another-bundle/2.1.0", 1));
- base.getBundles().add(BundlesTest.createBundle("org.apache.sling/foo-xyz/1.2.3", 2));
-
- final Configuration co1 = new Configuration("my.pid");
- co1.getProperties().put("foo", 5L);
- co1.getProperties().put("bar", "test");
- co1.getProperties().put("number", 7);
- base.getConfigurations().add(co1);
-
- final Configuration co2 = new Configuration("my.factory.pid", "name");
- co2.getProperties().put("a.value", "yeah");
- base.getConfigurations().add(co2);
-
- assertFalse(base.isAssembled());
-
- final Feature assembled = FeatureBuilder.assemble(base, new BuilderContext(provider));
-
- equals(base, assembled);
- }
-
- @Test public void testSingleInclude() throws Exception {
- final Feature base = new Feature(ArtifactId.parse("org.apache.sling/test-feature/1.1"));
- final Include i1 = new Include(ArtifactId.parse("g/a/1"));
- base.getIncludes().add(i1);
-
- final Requirement r1 = new OSGiRequirement("osgi.contract",
- Collections.emptyMap(), Collections.singletonMap("filter", "(&(osgi.contract=JavaServlet)(version=3.1))"));
- base.getRequirements().add(r1);
-
- Map<String, Object> attrs = new HashMap<>();
- attrs.put("osgi.implementation", "osgi.http");
- attrs.put("version:Version", "1.1");
- final Capability c1 = new OSGiCapability("osgi.implementation", attrs,
- Collections.singletonMap("uses", "javax.servlet,javax.servlet.http,org.osgi.service.http.context,org.osgi.service.http.whiteboard"));
- base.getCapabilities().add(c1);
-
- base.getFrameworkProperties().put("foo", "1");
- base.getFrameworkProperties().put("brave", "something");
- base.getFrameworkProperties().put("org.apache.felix.scr.directory", "launchpad/scr");
-
- final Artifact a1 = new Artifact(ArtifactId.parse("org.apache.sling/oak-server/1.0.0"));
- a1.getMetadata().put(Artifact.KEY_START_ORDER, "1");
- a1.getMetadata().put("hash", "4632463464363646436");
- base.getBundles().add(a1);
- base.getBundles().add(BundlesTest.createBundle("org.apache.sling/application-bundle/2.0.0", 1));
- base.getBundles().add(BundlesTest.createBundle("org.apache.sling/another-bundle/2.1.0", 1));
- base.getBundles().add(BundlesTest.createBundle("org.apache.sling/foo-xyz/1.2.3", 2));
- base.getBundles().add(BundlesTest.createBundle("group/testnewversion_low/1", 5));
- base.getBundles().add(BundlesTest.createBundle("group/testnewversion_high/5", 5));
- base.getBundles().add(BundlesTest.createBundle("group/testnewstartlevel/1", 10));
- base.getBundles().add(BundlesTest.createBundle("group/testnewstartlevelandversion/2", 10));
-
- final Configuration co1 = new Configuration("my.pid");
- co1.getProperties().put("foo", 5L);
- co1.getProperties().put("bar", "test");
- co1.getProperties().put("number", 7);
- base.getConfigurations().add(co1);
-
- final Configuration co2 = new Configuration("my.factory.pid", "name");
- co2.getProperties().put("a.value", "yeah");
- base.getConfigurations().add(co2);
-
- assertFalse(base.isAssembled());
-
- // create the expected result
- final Feature result = base.copy();
- result.getIncludes().remove(0);
- result.getFrameworkProperties().put("bar", "X");
- result.getBundles().add(BundlesTest.createBundle("org.apache.sling/foo-bar/4.5.6", 3));
- final Configuration co3 = new Configuration("org.apache.sling.foo");
- co3.getProperties().put("prop", "value");
- result.getConfigurations().add(co3);
-
- // assemble
- final Feature assembled = FeatureBuilder.assemble(base, new BuilderContext(provider));
-
- // and test
- equals(result, assembled);
- }
-}
--
To stop receiving notification emails like this one, please contact
davidb@apache.org.
[sling-org-apache-sling-feature] 01/22: Move feature model to
whiteboard git
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
davidb pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-feature.git
commit 9da66950b8f99f3e57b854c98b7cd4e0812998f4
Author: Carsten Ziegeler <cz...@adobe.com>
AuthorDate: Fri Nov 3 15:06:50 2017 +0100
Move feature model to whiteboard git
---
pom.xml | 86 +++++
readme.md | 242 ++++++++++++
.../java/org/apache/sling/feature/Application.java | 117 ++++++
.../java/org/apache/sling/feature/Artifact.java | 89 +++++
.../java/org/apache/sling/feature/ArtifactId.java | 414 +++++++++++++++++++++
.../java/org/apache/sling/feature/Bundles.java | 240 ++++++++++++
.../java/org/apache/sling/feature/Capability.java | 104 ++++++
.../org/apache/sling/feature/Configuration.java | 154 ++++++++
.../org/apache/sling/feature/Configurations.java | 58 +++
.../java/org/apache/sling/feature/Extension.java | 190 ++++++++++
.../org/apache/sling/feature/ExtensionType.java | 27 ++
.../java/org/apache/sling/feature/Extensions.java | 41 ++
.../java/org/apache/sling/feature/Feature.java | 408 ++++++++++++++++++++
.../java/org/apache/sling/feature/Include.java | 115 ++++++
.../java/org/apache/sling/feature/KeyValueMap.java | 125 +++++++
.../java/org/apache/sling/feature/Requirement.java | 113 ++++++
.../org/apache/sling/feature/package-info.java | 23 ++
.../sling/feature/process/ApplicationBuilder.java | 161 ++++++++
.../sling/feature/process/BuilderContext.java | 67 ++++
.../apache/sling/feature/process/BuilderUtil.java | 261 +++++++++++++
.../sling/feature/process/FeatureBuilder.java | 291 +++++++++++++++
.../feature/process/FeatureExtensionHandler.java | 55 +++
.../sling/feature/process/FeatureProvider.java | 30 ++
.../apache/sling/feature/process/package-info.java | 23 ++
.../org/apache/sling/feature/ArtifactIdTest.java | 144 +++++++
.../java/org/apache/sling/feature/BundlesTest.java | 45 +++
.../sling/feature/process/BuilderUtilTest.java | 133 +++++++
.../sling/feature/process/FeatureBuilderTest.java | 277 ++++++++++++++
28 files changed, 4033 insertions(+)
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..5cb1614
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+ <!--
+ 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.
+ -->
+<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/maven-v4_0_0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>sling</artifactId>
+ <version>32</version>
+ <relativePath />
+ </parent>
+
+ <artifactId>org.apache.sling.feature</artifactId>
+ <version>0.0.1-SNAPSHOT</version>
+ <packaging>bundle</packaging>
+
+ <name>Apache Sling Feature</name>
+ <description>
+ A feature describes an OSGi system
+ </description>
+
+ <properties>
+ <sling.java.version>8</sling.java.version>
+ </properties>
+
+ <scm>
+ <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/tooling/support/feature</connection>
+ <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/trunk/tooling/support/feature</developerConnection>
+ <url>http://svn.apache.org/viewvc/sling/trunk/tooling/support/feature</url>
+ </scm>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.rat</groupId>
+ <artifactId>apache-rat-plugin</artifactId>
+ <configuration>
+ <excludes>
+ <exclude>readme.md</exclude>
+ </excludes>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.geronimo.specs</groupId>
+ <artifactId>geronimo-json_1.0_spec</artifactId>
+ <version>1.0-alpha-1</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>osgi.core</artifactId>
+ </dependency>
+
+ <!-- Testing -->
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.johnzon</groupId>
+ <artifactId>johnzon-core</artifactId>
+ <version>1.0.0</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/readme.md b/readme.md
new file mode 100644
index 0000000..2fbf1f4
--- /dev/null
+++ b/readme.md
@@ -0,0 +1,242 @@
+# Prototype for a new Provisioning Model - Configuration Model for OSGi based applications
+
+The current model to describe OSGi applications is based on Apache Sling's provisioning model (see https://sling.apache.org/documentation/development/slingstart.html)
+
+## Short description of Sling's provisioning model:
+
+* Text based file format, defining features (several in a single file)
+* A feature can have a name and version (both optional)
+* A feature consists of sections, well defined ones like the run modes and user defined sections
+* A run mode has artifacts (with start levels), configurations and settings (framework properties)
+* Variables can be used throughout a feature
+* Inheritance is supported on a feature base through artifacts
+* Configuration merging is possible
+
+## Advantages of the provisioning model
+
+* Well known by Sling developers, has been introduced some years ago. Some tooling around it
+* Very concise, especially for defining artifacts
+* Extensible, custom sections can be added, e.g used by Sling for repoinit, subsystem definitions, content package definitions
+* Easy diff
+* Special API with semantics related to the prov model (not a general purpose config API)
+
+## Disadvantages of the provisioning model
+
+* Single file can contain more than one feature
+* Custom DSL - no standard format (like JSON)
+* Inheritance and custom artifacts (content packages) are mixed with bundles, which makes processing and understanding more complicated
+* Adding additional info to artifacts looks strange
+* Two formats for configurations and now there is an official JSON based format defined through OSGi R7
+* Strange object relationship between feature and run modes
+* API (object relation) differs from text file (to make the text format easier)
+* Tooling only available as maven plugins, not separate usable
+* Run mode handling is complicating the feature files and processing of those
+* Tightly coupled with the way Sling's launchpad works, therefore no independent OSGi format
+
+# Design Criteria for a model
+
+* A feature is a separate file with a required name and version
+* A feature can include other features
+* No support for run modes in the model - run modes can be modeled through separate features
+* OSGi JSON format for configurations
+* Support for standard OSGi artifacts as well as custom artifacts (like content packages)
+* Support OSGi requirements and capabilities for dependency handling
+
+# Prototype
+
+The prototype uses JSON as a well defined and understood format. This fits nicely with the new OSGi R7 JSON format for configurations.
+
+A model file describes a feature. A feature consists of:
+* A unique id and version (see Feature Identity below)
+* A list of bundles described through maven coordinates
+ * Grouped by start level (required)
+ * Additional metadata like a hash etc. (optional)
+ * Configurations (optional)
+* A set of global configurations
+* A set of framework properties
+* A list of provided capabilities
+* A list of required capabilities
+* A set of includes (of other features) described through maven coordinates
+ * Modifications (removals) of the includes (optional)
+* Extensions (optional)
+ * A list of repoinit instructions
+ * A set of content packages described through maven coordinates
+ * Additional metadata like a hash etc. (optional)
+ * Configurations (optional)
+
+# Feature Identity
+
+A feature is uniquely identified through maven coordinates, it has a group id, an artifact id and a version. In addition it might have a classifier.
+
+TBD We need to define a common type for such a feature. It could be "osgifeature" (but is this a good type? slingfeature, slingstart are taken, osgifeature might be too general)
+
+# Maven coordinates
+
+Maven coordinates are used to describe artifacts, e.g. bundles, content packages or includes. In these cases either the short notation (as described here: https://maven.apache.org/pom.html#Maven_Coordinates) can be used or the long version as a JSON object with an id property.
+
+# Requirements and Capabilities vs Dependencies
+
+In order to avoid a concept like "Require-Bundle" a feature does not explicitely declare dependencies to other features. These are declared by the required capabilities, either explicit or implicit. The implicit requirements are calculated by inspecting the contained bundles (and potentially other artifacts like content packages ).
+
+Once a feature is processed by tooling, the tooling might create a full list of requirements and capabilities and add this information in a special section to the final feature. This information can be used by tooling to validate an instance (see below) and avoids rescanning the binary artifacts. However this "cached" information is optional and tooling must work without it (which means it needs access to the binaries in that case). TBD the name and format of this information.
+
+# Includes
+
+Includes allow an aggregation of features and a modification of the included feature: each entity listed in the included feature can be removed, e.g a configuration or a bundle. The list of includes must not contain duplicates (not comparing the version of the includes). If there are duplicates, the feature is invalid.
+
+Once a feature is processed, included references are removed and the content of the included features becomes part of the current feature. The following algorithm applies:
+
+* Includes are processed in the order they are defined in the model. The current feature (containing the includes) is used last which means the algorithm starts with the first included feature.
+* Removal instructions for an include are handled first
+* A clash of bundles or content packages is resolved by picking the latest version (not the highest!)
+* Configurations will be merged by default, later ones potentially overriding newer ones:
+ * If the same property is declared in more than one feature, the last one wins - in case of an array value, this requires redeclaring all values (if they are meant to be kept)
+ * Configurations can be bound to a bundle. When two features are merged, all cases can occur: both might be bound to the same bundle (symbolic name), both might not be bound, they might be bound to different bundles (symbolic name), or one might be bound and the other one might not. As configurations are handled as a set regardless of whether they are bound to a bundle or not, the information of the belonging bundle is handled like a property in the configuration. This means:
+ * If the last configuration belongs to a bundle, this relationship is kept
+ * If the last configuration does not belong to a bundle and has no property removal instruction, the relationship from the first bundle is used (if there is one)
+ * If the last configuration has a property removal instruction for the bundle relationship, the resulting configuration is unbound
+* Later framework properties overwrite newer ones
+* Capabilities and requirements are appended - this might result in duplicates, but that doesn't really hurt in practice.
+* Extensions are handled in an extension specific way:
+ * repoinit is just aggregated (appended)
+ * artifact extensions are handled like bundles
+
+While includes must not be used for assembling an application, they provide an important concept for manipulating existing features. For example to replace a bundle in an existing feature and deliver this modified feature.
+
+# Extensions
+
+An extension has a unique name and a type which can either be text, JSON or artifacts. Depending on the type, inheritance is performed like this:
+* For type text: simple appended
+* For type JSON: merging of the JSON structure, later arriving properties overriding existing ones
+* For type artifacts: merging of the artifacts, higher version wins
+
+# Handling of Environments
+
+A feature itself has no special support for environments (prod, test, dev). In practice it is very unlikely that a single file exists containing configurations for all environments, especially as the configuration might contain secrets, credentials, urls for production services etc which are not meant to be given out in public (or to the dev department). Instead, a separate feature for an environment can be written and maintained by the different share holders which adds the environment [...]
+
+# Bundles and start levels
+
+For bundles there is no default start level - a default start level is not defined in the OSGi spec. And in addition, it is a little bit confusing when looking at the model when there is a list of artifacts without a start level. Which start level do these have? Its better to be explicit.
+
+In the current PoC, a bundle needs to be explicitely assigned to a start level. This seems to be only working if you know all the features in advance and how they are structured. On the other hand there needs to be a way to define the start order of bundles within a feature. Therefore we can use the start level information as an ordering information for the bundles within a feature. Bundles within the same start level are started in any order.
+
+Proposal: We use the format as it is today, but interpret the start level value different: instead of directly mapping it to a start level in the OSGi framework, it defines just the startup order of bundles within a feature. Features are then started in respect of their dependency information. Even if a feature has no requirement with respect to start ordering of their bundles, it has to define a start level (to act as a container for the bundles). It can use any positive number, suggest [...]
+
+# Configurations belonging to Bundles
+
+In most cases, configurations belong to a bundle. The most common use case is a configuration for a (DS) component. Therefore instead of having a separate configurations section, it is more intuitiv to specify configurations as part of a bundle. The benefit of this approach is, that it can easily be decided if a configuration is to be used: if exactly that bundle is used, the configurations are used; otherwise they are not.
+
+However, there might be situations where it is not clear to which bundle a configuration belongs or the configuration might be a cross cutting concern spawning across multiple bundles. Therefore it is still possible to have configurations not related to a particular bundle.
+
+In fact, configurations - whether they are declared as part of a bundle or not - are all managed in a single set for a feature. See above for how includes etc. are handled.
+
+# Example
+
+This is a feature example:
+
+ {
+ "id" : "org.apache.sling:my.app:feature:optional:1.0",
+
+ "includes" : [
+ {
+ "id" : "org.apache.sling:sling:9",
+ "removals" : {
+ "configurations" : [
+ ],
+ "bundles": [
+ ],
+ "framework-properties" : [
+ ]
+ }
+ }
+ ],
+ "requirements" : [
+ {
+ "namespace" : "osgi.contract",
+ "directives" : {
+ "filter" : "(&(osgi.contract=JavaServlet)(version=3.1))"
+ }
+ }
+ ],
+ "capabilities" : [
+ {
+ "namespace" : "osgi.implementation",
+ "attributes" : {
+ "osgi.implementation" : "osgi.http",
+ "version:Version" : "1.1"
+ },
+ "directives" : {
+ "uses" : "javax.servlet,javax.servlet.http,org.osgi.service.http.context,org.osgi.service.http.whiteboard"
+ }
+ },
+ {
+ "namespace" : osgi.service",
+ "attributes" : {
+ "objectClass:List<String>" : "org.osgi.service.http.runtime.HttpServiceRuntime"
+ },
+ "directives" {
+ "uses" : "org.osgi.service.http.runtime,org.osgi.service.http.runtime.dto"
+ }
+ }
+ ],
+ "framework-properties" {
+ "foo" : 1,
+ "brave" : "something",
+ "org.apache.felix.scr.directory" : "launchpad/scr"
+ },
+ "bundles" : {
+ "1" : [
+ {
+ "id" : "org.apache.sling:security-server:2.2.0",
+ "hash" : "4632463464363646436"
+ },
+ "org.apache.sling:application-bundle:2.0.0",
+ "org.apache.sling:another-bundle:2.1.0"
+ ],
+ "2" : [
+ "org.apache.sling:foo-xyz:1.2.3"
+ ]
+ },
+ "configurations" {
+ "my.pid" {
+ "foo" : 5,
+ "bar" : "test",
+ "number:Integer" : 7
+ },
+ "my.factory.pid~name" {
+ "a.value" : "yeah"
+ }
+ }
+
+# Relation to OBR
+
+TODO
+
+# Provisioning Applications
+
+An application jar can contain a set of features (including the listed artifacts).
+
+An optional application configuration further defines the possibilites:
+
+ {
+ "features" : [
+ "org.apache.sling:org.apache.sling.launchpad:10"
+ ],
+ "options" : [
+ "org.apache.sling:org.apache.sling.scripting.jsp:1.0.0",
+ {
+ "id" : "org.apache.sling:org.apache.sling.scripting.htl:1.0.0",
+ "tag": "htl"
+ }
+ ],
+ "defaults" : {
+ "auto-add-options": true,
+ "tags" : ["htl"]
+ },
+ "framework" : {
+ "id" : "org.apache.felix:org.apache.felix.framework:5.6.4"
+ }
+ }
+
+Such a configuration is required for an application, at least one feature needs to be listed in either the features or the options section.
+All features listed in the features section will be added to the application, the ones listed in options are optional and depending on the settings and user input will either be added or left out. In addition all available features of an application will be used to make the application runnable (resolvable).
diff --git a/src/main/java/org/apache/sling/feature/Application.java b/src/main/java/org/apache/sling/feature/Application.java
new file mode 100644
index 0000000..7ca58e4
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/Application.java
@@ -0,0 +1,117 @@
+/*
+ * 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.sling.feature;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * An application consists of
+ * <ul>
+ * <li>Framework
+ * <li>Bundles
+ * <li>Configurations
+ * <li>Framework properties
+ * <li>Extensions
+ * <li>Feature ids (of the features making up this application)
+ * </ul>
+ */
+public class Application {
+
+ /** Container for bundles. */
+ private final Bundles bundles = new Bundles();
+
+ /** List of configurations. */
+ private final Configurations configurations = new Configurations();
+
+ /** Map of framework properties. */
+ private final KeyValueMap frameworkProperties = new KeyValueMap();
+
+ /** List of extensions. */
+ private final Extensions extensions = new Extensions();
+
+ /** List of features. */
+ private final List<ArtifactId> features = new ArrayList<>();
+
+ /** The framework id. */
+ private ArtifactId framework;
+
+ /**
+ * Get the bundles
+ * @return The bundles object.
+ */
+ public Bundles getBundles() {
+ return this.bundles;
+ }
+
+ /**
+ * Get the configurations
+ * The list is modifiable.
+ * @return The list of configurations
+ */
+ public Configurations getConfigurations() {
+ return this.configurations;
+ }
+
+ /**
+ * Get the framework properties
+ * The map is modifiable
+ * @return The map of properties
+ */
+ public KeyValueMap getFrameworkProperties() {
+ return this.frameworkProperties;
+ }
+
+ /**
+ * Get the list of extensions
+ * The list is modifiable
+ * @return The list of extension
+ */
+ public Extensions getExtensions() {
+ return this.extensions;
+ }
+
+ /**
+ * Get the list of used features to build this application
+ * @return The list of features
+ */
+ public List<ArtifactId> getFeatureIds() {
+ return this.features;
+ }
+
+ /**
+ * Get the framework id
+ * @return The framework id or {@code null}
+ */
+ public ArtifactId getFramework() {
+ return framework;
+ }
+
+ /**
+ * Set the framework id
+ * @param framework The framework id
+ */
+ public void setFramework(final ArtifactId framework) {
+ this.framework = framework;
+ }
+
+ @Override
+ public String toString() {
+ return "Application [features=" + this.features
+ + "]";
+ }
+}
diff --git a/src/main/java/org/apache/sling/feature/Artifact.java b/src/main/java/org/apache/sling/feature/Artifact.java
new file mode 100644
index 0000000..4fada27
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/Artifact.java
@@ -0,0 +1,89 @@
+/*
+ * 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.sling.feature;
+
+/**
+ * An artifact consists of
+ * <ul>
+ * <li>An id
+ * <li>metadata
+ * </ul>
+ */
+public class Artifact implements Comparable<Artifact> {
+
+ /** The artifact id. */
+ private final ArtifactId id;
+
+ /** Artifact metadata. */
+ private final KeyValueMap metadata = new KeyValueMap();
+
+ /**
+ * Construct a new artifact
+ * @param id The id of the artifact.
+ * @throws IllegalArgumentException If id is {@code null}.
+ */
+ public Artifact(final ArtifactId id) {
+ if ( id == null ) {
+ throw new IllegalArgumentException("id must not be null.");
+ }
+ this.id = id;
+ }
+
+ /**
+ * Get the id of the artifact.
+ * @return The id.
+ */
+ public ArtifactId getId() {
+ return this.id;
+ }
+
+ /**
+ * Get the metadata of the artifact.
+ * The metadata can be modified.
+ * @return The metadata.
+ */
+ public KeyValueMap getMetadata() {
+ return this.metadata;
+ }
+
+ @Override
+ public int compareTo(final Artifact o) {
+ return this.id.compareTo(o.id);
+ }
+
+ @Override
+ public int hashCode() {
+ return this.id.hashCode();
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ return this.id.equals(((Artifact)obj).id);
+ }
+
+ @Override
+ public String toString() {
+ return "Artifact [id=" + id.toMvnId()
+ + "]";
+ }
+}
diff --git a/src/main/java/org/apache/sling/feature/ArtifactId.java b/src/main/java/org/apache/sling/feature/ArtifactId.java
new file mode 100644
index 0000000..a7231c8
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/ArtifactId.java
@@ -0,0 +1,414 @@
+/*
+ * 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.sling.feature;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.osgi.framework.Version;
+
+/**
+ * An artifact identifier.
+ *
+ * An artifact is described by it's Apache Maven coordinates consisting of group id,
+ * artifact id, and version. In addition, the classifier and type can be specified.
+ * If no type is specified, {@code jar} is assumed.
+ */
+public class ArtifactId implements Comparable<ArtifactId> {
+
+ /** The required group id. */
+ private final String groupId;
+
+ /** The required artifact id. */
+ private final String artifactId;
+
+ /** The required version. */
+ private final String version;
+
+ /** The optional classifier. */
+ private final String classifier;
+
+ /** The required type. Defaults to jar. */
+ private final String type;
+
+ /**
+ * Create a new artifact object
+ * @param groupId The group id (required)
+ * @param artifactId The artifact id (required)
+ * @param version The version (required)
+ * @param classifier The classifier (optional)
+ * @param type The type/extension (optional, defaults to jar)
+ * @throws IllegalArgumentException If group id, artifact id or version are {@code null} or if
+ * the version is not a valid version.
+ */
+ public ArtifactId(final String groupId,
+ final String artifactId,
+ final String version,
+ final String classifier,
+ final String type) {
+ if ( groupId == null || artifactId == null || version == null ) {
+ throw new IllegalArgumentException("Argument must not be null");
+ }
+ this.groupId = groupId;
+ this.artifactId = artifactId;
+ this.version = version;
+ this.getOSGiVersion();
+ if ( "bundle".equals(type) || type == null || type.isEmpty() ) {
+ this.type = "jar";
+ } else {
+ this.type = type;
+ }
+ if ( classifier != null && classifier.isEmpty() ) {
+ this.classifier = null;
+ } else {
+ this.classifier = classifier;
+ }
+ }
+
+ /**
+ * Create a new artifact id from a string, the string must either be a
+ * mvn url or a mvn id (= coordinates)
+ * @param s The string to parse
+ * @return The artifact id
+ * @throws IllegalArgumentException if the string can't be parsed to a valid artifact id.
+ */
+ public static ArtifactId parse(final String s) {
+ if ( s.contains(":") ) {
+ return ArtifactId.fromMvnId(s);
+ } else if ( s.contains("/") ) {
+ return ArtifactId.fromMvnUrl(s);
+ }
+ throw new IllegalArgumentException("Unable to parse mvn coordinates/url: " + s);
+ }
+
+ /**
+ * Create a new artifact id from a maven url,
+ * 'mvn:' group-id '/' artifact-id [ '/' [version] [ '/' [type] [ '/' classifier ] ] ] ]
+ * @param url The url
+ * @return A new artifact id
+ * @throws IllegalArgumentException If the url is not valid
+ */
+ public static ArtifactId fromMvnUrl(final String url) {
+ if ( url == null || (url.indexOf(':') != -1 && !url.startsWith("mvn:")) ) {
+ throw new IllegalArgumentException("Invalid mvn url: " + url);
+ }
+ // throw if repository url is included
+ if ( url.indexOf('!') != -1 ) {
+ throw new IllegalArgumentException("Repository url is not supported for Maven artifacts at the moment.");
+ }
+ final String coordinates = url.startsWith("mvn:") ? url.substring(4) : url;
+ String gId = null;
+ String aId = null;
+ String version = null;
+ String type = null;
+ String classifier = null;
+ int part = 0;
+ String value = coordinates;
+ while ( value != null ) {
+ final int pos = value.indexOf('/');
+ final String current;
+ if ( pos == -1 ) {
+ current = value;
+ value = null;
+ } else {
+ if ( pos == 0 ) {
+ current = null;
+ } else {
+ current = value.substring(0, pos);
+ }
+ value = value.substring(pos + 1);
+ }
+ if ( current != null ) {
+ if ( part == 0 ) {
+ gId = current;
+ } else if ( part == 1 ) {
+ aId = current;
+ } else if ( part == 2 ) {
+ version = current;
+ } else if ( part == 3 ) {
+ type = current;
+ } else if ( part == 4 ) {
+ classifier = current;
+ }
+ }
+ part++;
+ }
+ return new ArtifactId(gId, aId, version, classifier, type);
+ }
+
+ /**
+ * Create a new artifact id from maven coordinates/id
+ * groupId:artifactId[:packaging[:classifier]]:version
+ * @param coordinates The coordinates as outlined above
+ * @return A new artifact id
+ * @throws IllegalArgumentException If the id is not valid
+ */
+ public static ArtifactId fromMvnId(final String coordinates) {
+ final String[] parts = coordinates.split(":");
+ if ( parts.length < 3 || parts.length > 5) {
+ throw new IllegalArgumentException("Invalid mvn coordinates: " + coordinates);
+ }
+ final String gId = parts[0];
+ final String aId = parts[1];
+ final String version = parts[parts.length - 1];
+ final String type = parts.length > 3 ? parts[2] : null;
+ final String classifier = parts.length > 4 ? parts[3] : null;
+
+ return new ArtifactId(gId, aId, version, classifier, type);
+ }
+
+ /**
+ * Return a mvn url
+ * @return A mvn url
+ * @see #fromMvnUrl(String)
+ */
+ public String toMvnUrl() {
+ final StringBuilder sb = new StringBuilder("mvn:");
+ sb.append(this.groupId);
+ sb.append('/');
+ sb.append(this.artifactId);
+ sb.append('/');
+ sb.append(version);
+ if ( this.classifier != null || !"jar".equals(this.type)) {
+ sb.append('/');
+ sb.append(this.type);
+ if ( this.classifier != null ) {
+ sb.append('/');
+ sb.append(this.classifier);
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Return a mvn id
+ * @return The mvn id
+ * #see {@link #fromMvnId(String)}
+ */
+ public String toMvnId() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append(this.groupId);
+ sb.append(':');
+ sb.append(this.artifactId);
+ sb.append(':');
+ sb.append(version);
+ if ( this.classifier != null || !"jar".equals(this.type)) {
+ sb.append(':');
+ sb.append(this.type);
+ if ( this.classifier != null ) {
+ sb.append(':');
+ sb.append(this.classifier);
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Return the group id.
+ * @return The group id.
+ */
+ public String getGroupId() {
+ return groupId;
+ }
+
+ /**
+ * Return the artifact id.
+ * @return The artifact id.
+ */
+ public String getArtifactId() {
+ return artifactId;
+ }
+
+ /**
+ * Return the optional classifier.
+ * @return The classifier or {@code null}.
+ */
+ public String getClassifier() {
+ return classifier;
+ }
+
+ /**
+ * Return the type.
+ * @return The type.
+ */
+ public String getType() {
+ return type;
+ }
+
+ /**
+ * Return the version.
+ * @return The version.
+ */
+ public String getVersion() {
+ return version;
+ }
+
+ /**
+ * Test whether the artifact id is pointing to the same artifact but potentially a different version
+ * @param id The artifact id
+ * @return {@code true} if group id, artifact id, type and classifier equal
+ */
+ public boolean isSame(final ArtifactId id) {
+ if ( this.groupId.equals(id.groupId)
+ && this.artifactId.equals(id.artifactId)
+ && this.type.equals(id.type) ) {
+ if (this.classifier == null && id.classifier == null ) {
+ return true;
+ }
+ if ( this.classifier != null ) {
+ return this.classifier.equals(id.classifier);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Return the OSGi version
+ * @return The OSGi version
+ */
+ public Version getOSGiVersion() {
+ String parts[] = version.split("\\.");
+
+ if ( parts.length < 4) {
+
+ int pos = parts[parts.length - 1].indexOf('-');
+ if ( pos != -1 ) {
+ final String[] newParts = new String[4];
+ newParts[0] = parts.length > 1 ? parts[0] : parts[0].substring(0, pos);
+ newParts[1] = parts.length > 2 ? parts[1] : (parts.length > 1 ? parts[1].substring(0, pos) : "0");
+ newParts[2] = parts.length > 3 ? parts[2] : (parts.length > 2 ? parts[2].substring(0, pos) : "0");
+ newParts[3] = parts[parts.length - 1].substring(pos + 1);
+ parts = newParts;
+ }
+ else {
+ // special case for strange versions like NUMBER_NUMBER
+ for (int i = 0; i < parts.length; i++) {
+ for (pos = parts[i].indexOf('_'); pos != -1 && pos < parts[i].length() - 1; pos = parts[i].indexOf('_')) {
+ List<String> newParts = new ArrayList<>(Arrays.asList(parts));
+ newParts.remove(i);
+ newParts.add(i, parts[i].substring(0, pos));
+ newParts.add(i + 1, parts[i].substring(pos + 1));
+ parts = newParts.toArray(new String[0]);
+ }
+ }
+ }
+ }
+ if ( parts.length >= 4 ) {
+ final int pos = parts[2].indexOf('-');
+ if ( pos != -1 ) {
+ parts[3] = parts[2].substring(pos + 1) + "." + parts[3];
+ parts[2] = parts[2].substring(0, pos);
+ }
+ }
+ if ( parts.length > 4 ) {
+ final StringBuilder sb = new StringBuilder(parts[3]);
+ for(int i=4; i<parts.length;i++) {
+ sb.append('.');
+ sb.append(parts[i]);
+ }
+ parts[3] = sb.toString();
+ }
+ if ( parts.length > 3 && parts[3] != null ) {
+ final StringBuilder sb = new StringBuilder();
+ for ( int i = 0; i < parts[3].length(); i++ )
+ {
+ final char c = parts[3].charAt( i );
+ if ( ( c >= '0' && c <= '9' ) || ( c >= 'a' && c <= 'z' ) || ( c >= 'A' && c <= 'Z' ) || c == '_'
+ || c == '-' ) {
+ sb.append( c );
+ } else {
+ sb.append( '_' );
+ }
+ }
+ parts[3] = sb.toString();
+ }
+ final int majorVersion = parseInt(parts[0], version);
+ final int minorVersion;
+ final int microVersion;
+ if ( parts.length > 1 ) {
+ minorVersion = parseInt(parts[1], version);
+ } else {
+ minorVersion = 0;
+ }
+ if ( parts.length > 2 ) {
+ microVersion = parseInt(parts[2], version);
+ } else {
+ microVersion = 0;
+ }
+ final String qualifier = (parts.length > 3 ? parts[3] : "");
+ return new Version(majorVersion, minorVersion, microVersion, qualifier);
+ }
+
+ /**
+ * Create a Maven like relative repository path.
+ * @return A relative repository path. The path does not start with a slash.
+ */
+ public String toMvnPath() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append(groupId.replace('.', '/'));
+ sb.append('/');
+ sb.append(artifactId);
+ sb.append('/');
+ sb.append(version);
+ sb.append('/');
+ sb.append(artifactId);
+ sb.append('-');
+ sb.append(version);
+ if ( classifier != null ) {
+ sb.append('-');
+ sb.append(classifier);
+ }
+ sb.append('.');
+ sb.append(type);
+ return sb.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return toMvnUrl().hashCode();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if(o == null) return false;
+ if(!(o instanceof ArtifactId)) return false;
+ return toMvnUrl().equals(((ArtifactId)o).toMvnUrl());
+ }
+
+ @Override
+ public int compareTo(final ArtifactId o) {
+ if(o == null) return 1;
+ return toMvnUrl().compareTo(o.toMvnUrl());
+ }
+
+ @Override
+ public String toString() {
+ return toMvnId();
+ }
+
+ /**
+ * Parse an integer.
+ */
+ private static int parseInt(final String value, final String version) {
+ try {
+ return Integer.parseInt(value);
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("Invalid version " + version);
+ }
+ }
+}
diff --git a/src/main/java/org/apache/sling/feature/Bundles.java b/src/main/java/org/apache/sling/feature/Bundles.java
new file mode 100644
index 0000000..a359575
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/Bundles.java
@@ -0,0 +1,240 @@
+/*
+ * 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.sling.feature;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.NoSuchElementException;
+import java.util.TreeMap;
+
+/**
+ * Bundles groups bundle {@code Artifact}s by start level.
+ */
+public class Bundles implements Iterable<Map.Entry<Integer, Artifact>> {
+
+ /** Map of bundles grouped by start level */
+ private final Map<Integer, List<Artifact>> startLevelMap = new TreeMap<>();
+
+ /**
+ * Get the map of all bundles sorted by start level. The map is sorted
+ * and iterating over the keys is done in start level order.
+ * @return The map of bundles. The map is unmodifiable.
+ */
+ public Map<Integer, List<Artifact>> getBundlesByStartLevel() {
+ return Collections.unmodifiableMap(this.startLevelMap);
+ }
+
+ /**
+ * Add an artifact in the given start level.
+ * @param startLevel The start level
+ * @param bundle The bundle
+ */
+ public void add(final int startLevel, final Artifact bundle) {
+ List<Artifact> list = this.startLevelMap.get(startLevel);
+ if ( list == null ) {
+ list = new ArrayList<>();
+ this.startLevelMap.put(startLevel, list);
+ }
+ list.add(bundle);
+ }
+
+ /**
+ * Remove the exact artifact.
+ * All start levels are searched for such an artifact. The first one found is removed.
+ * @param id The artifact id
+ * @return {@code true} if the artifact has been removed
+ */
+ public boolean removeExact(final ArtifactId id) {
+ for(final Map.Entry<Integer, List<Artifact>> entry : this.startLevelMap.entrySet()) {
+ for(final Artifact artifact : entry.getValue()) {
+ if ( artifact.getId().equals(id)) {
+ entry.getValue().remove(artifact);
+ if ( entry.getValue().isEmpty() ) {
+ this.startLevelMap.remove(entry.getKey());
+ }
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Remove the same artifact, neglecting the version.
+ * All start levels are searched for such an artifact. The first one found is removed.
+ * @param id The artifact id
+ * @return {@code true} if the artifact has been removed
+ */
+ public boolean removeSame(final ArtifactId id) {
+ for(final Map.Entry<Integer, List<Artifact>> entry : this.startLevelMap.entrySet()) {
+ for(final Artifact artifact : entry.getValue()) {
+ if ( artifact.getId().isSame(id)) {
+ entry.getValue().remove(artifact);
+ if ( entry.getValue().isEmpty() ) {
+ this.startLevelMap.remove(entry.getKey());
+ }
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Clear the bundles map.
+ */
+ public void clear() {
+ this.startLevelMap.clear();
+ }
+
+ /**
+ * Get start level and artifact for the given id, neglecting the version
+ * @param id The artifact id
+ * @return A map entry with start level and artifact, {@code null} otherwise
+ */
+ public Map.Entry<Integer, Artifact> getSame(final ArtifactId id) {
+ for(final Map.Entry<Integer, Artifact> entry : this) {
+ if ( entry.getValue().getId().isSame(id)) {
+ return new Map.Entry<Integer, Artifact>() {
+
+ @Override
+ public Integer getKey() {
+ return entry.getKey();
+ }
+
+ @Override
+ public Artifact getValue() {
+ return entry.getValue();
+ }
+
+ @Override
+ public Artifact setValue(final Artifact value) {
+ throw new IllegalStateException();
+ }
+ };
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Checks whether the exact artifact is available
+ * @param id The artifact id.
+ * @return {@code true} if the artifact exists
+ */
+ public boolean containsExact(final ArtifactId id) {
+ for(final Map.Entry<Integer, Artifact> entry : this) {
+ if ( entry.getValue().getId().equals(id)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Checks whether the same artifact is available, neglecting the version
+ * @param id The artifact id.
+ * @return {@code true} if the artifact exists
+ */
+ public boolean containsSame(final ArtifactId id) {
+ for(final Map.Entry<Integer, Artifact> entry : this) {
+ if ( entry.getValue().getId().isSame(id)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Iterate over all bundles
+ */
+ @Override
+ public Iterator<Map.Entry<Integer, Artifact>> iterator() {
+ final Iterator<Map.Entry<Integer, List<Artifact>>> mainIter = this.startLevelMap.entrySet().iterator();
+ return new Iterator<Map.Entry<Integer,Artifact>>() {
+
+ private Map.Entry<Integer, Artifact> next = seek();
+
+ private Integer level;
+
+ private Iterator<Artifact> innerIter;
+
+ private Map.Entry<Integer, Artifact> seek() {
+ Map.Entry<Integer, Artifact> entry = null;
+ while ( this.innerIter != null || mainIter.hasNext() ) {
+ if ( innerIter != null ) {
+ if ( innerIter.hasNext() ) {
+ final Artifact a = innerIter.next();
+ final Integer l = this.level;
+ entry = new Map.Entry<Integer, Artifact>() {
+
+ @Override
+ public Integer getKey() {
+ return l;
+ }
+
+ @Override
+ public Artifact getValue() {
+ return a;
+ }
+
+ @Override
+ public Artifact setValue(Artifact value) {
+ throw new UnsupportedOperationException();
+ }
+ };
+ break;
+ } else {
+ innerIter = null;
+ }
+ } else {
+ final Map.Entry<Integer, List<Artifact>> e = mainIter.next();
+ this.level = e.getKey();
+ this.innerIter = e.getValue().iterator();
+ }
+ }
+ return entry;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return this.next != null;
+ }
+
+ @Override
+ public Entry<Integer, Artifact> next() {
+ final Entry<Integer, Artifact> result = next;
+ if ( result == null ) {
+ throw new NoSuchElementException();
+ }
+ this.next = seek();
+ return result;
+ }
+
+ };
+ }
+
+ @Override
+ public String toString() {
+ return "Bundles [" + this.startLevelMap
+ + "]";
+ }
+}
diff --git a/src/main/java/org/apache/sling/feature/Capability.java b/src/main/java/org/apache/sling/feature/Capability.java
new file mode 100644
index 0000000..53c9c3b
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/Capability.java
@@ -0,0 +1,104 @@
+/*
+ * 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.sling.feature;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * A capability of a feature.
+ * The capability is modeled after an OSGi capability: it
+ * belongs to a namespace and might have attributes and / or
+ * directives.
+ */
+public class Capability {
+
+ /** The namspace. */
+ private final String namespace;
+
+ /** Map of attributes. */
+ private final Map<String, Object> attributes = new ConcurrentHashMap<>();
+
+ /** Map of directives. */
+ private final Map<String, Object> directives = new ConcurrentHashMap<>();
+
+ /**
+ * Create a new Capability.
+ * @param namespace The namespace
+ * @throws IllegalArgumentException If namespace is {@code null}.
+ */
+ public Capability(final String namespace) {
+ if ( namespace == null ) {
+ throw new IllegalArgumentException("namespace must not be null.");
+ }
+ this.namespace = namespace;
+ }
+
+ /**
+ * The namespace
+ * @return The namespace
+ */
+ public String getNamespace() {
+ return namespace;
+ }
+
+ /**
+ * Get the map of attributes.
+ * The map is modifiable.
+ * @return The map of attributes.
+ */
+ public Map<String, Object> getAttributes() {
+ return attributes;
+ }
+
+ /**
+ * Get the map of directives.
+ * The map is modifiable.
+ * @return The map of directives.
+ */
+ public Map<String, Object> getDirectives() {
+ return directives;
+ }
+
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + attributes.hashCode();
+ result = prime * result + directives.hashCode();
+ result = prime * result + namespace.hashCode();
+ return result;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ final Capability other = (Capability) obj;
+ if (!attributes.equals(other.attributes)
+ || !directives.equals(other.directives)
+ || !namespace.equals(other.namespace)) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/src/main/java/org/apache/sling/feature/Configuration.java b/src/main/java/org/apache/sling/feature/Configuration.java
new file mode 100644
index 0000000..721badf
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/Configuration.java
@@ -0,0 +1,154 @@
+/*
+ * 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.sling.feature;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+
+/**
+ * A configuration has either
+ * <ul>
+ * <li>a pid
+ * <li>or a factory pid and a name
+ * </ul>
+ * and properties.
+ */
+public class Configuration
+ implements Comparable<Configuration> {
+
+ public static final String PROP_ARTIFACT = "service.bundleLocation";
+
+ /** The pid or name for factory pids. */
+ private final String pid;
+
+ /** The factory pid. */
+ private final String factoryPid;
+
+ /** The properties. */
+ private final Dictionary<String, Object> properties = new Hashtable<>();
+
+ /**
+ * Create a new configuration
+ * @param pid The pid
+ * @throws IllegalArgumentException If pid is {@code null}
+ */
+ public Configuration(final String pid) {
+ if ( pid == null ) {
+ throw new IllegalArgumentException("pid must not be null");
+ }
+ this.pid = pid;
+ this.factoryPid = null;
+ }
+
+ /**
+ * Create a new factor configuration
+ * @param factoryPid The factory pid
+ * @param name The name of the factory pid
+ * @throws IllegalArgumentException If factoryPid or name is {@code null}
+ */
+ public Configuration(final String factoryPid, final String name) {
+ if ( factoryPid == null || name == null ) {
+ throw new IllegalArgumentException("factoryPid and/or name must not be null");
+ }
+ this.pid = name;
+ this.factoryPid = factoryPid;
+ }
+
+ private int compareString(final String a, final String b) {
+ if ( a == null ) {
+ if ( b == null ) {
+ return 0;
+ }
+ return -1;
+ }
+ if ( b == null ) {
+ return 1;
+ }
+ return a.compareTo(b);
+ }
+
+ @Override
+ public int compareTo(final Configuration o) {
+ int result = compareString(this.factoryPid, o.factoryPid);
+ if ( result == 0 ) {
+ result = compareString(this.pid, o.pid);
+ }
+ return result;
+ }
+
+
+ /**
+ * Get the pid.
+ * If this is a factory configuration, it returns {@code null}
+ * @return The pid or {@code null}
+ */
+ public String getPid() {
+ if ( this.isFactoryConfiguration() ) {
+ return null;
+ }
+ return this.pid;
+ }
+
+ /**
+ * Return the factory pid
+ * @return The factory pid or {@code null}.
+ */
+ public String getFactoryPid() {
+ return this.factoryPid;
+ }
+
+ /**
+ * Return the name for a factory configuration.
+ * @return The name or {@code null}.
+ */
+ public String getName() {
+ if ( this.isFactoryConfiguration() ) {
+ return this.pid;
+ }
+ return null;
+ }
+
+ /**
+ * Check whether this is a factory configuration
+ * @return {@code true} if it is a factory configuration
+ */
+ public boolean isFactoryConfiguration() {
+ return this.factoryPid != null;
+ }
+
+ /**
+ * Get all properties of the configuration.
+ * @return The properties
+ */
+ public Dictionary<String, Object> getProperties() {
+ return this.properties;
+ }
+
+ @Override
+ public String toString() {
+ if ( this.isFactoryConfiguration() ) {
+ return "Factory Configuration [factoryPid=" + factoryPid
+ + ", name=" + pid
+ + ", properties=" + properties
+ + "]";
+ }
+ return "Configuration [pid=" + pid
+ + ", properties=" + properties
+ + "]";
+ }
+}
diff --git a/src/main/java/org/apache/sling/feature/Configurations.java b/src/main/java/org/apache/sling/feature/Configurations.java
new file mode 100644
index 0000000..0a78964
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/Configurations.java
@@ -0,0 +1,58 @@
+/*
+ * 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.sling.feature;
+
+import java.util.ArrayList;
+
+/**
+ * A container for configurations.
+ */
+public class Configurations extends ArrayList<Configuration> {
+
+ private static final long serialVersionUID = -7243822886707856704L;
+
+ /**
+ * Get the configuration
+ * @param pid The pid of the configuration
+ * @return The configuration or {@code null}
+ */
+ public Configuration getConfiguration(final String pid) {
+ for(final Configuration cfg : this) {
+ if ( !cfg.isFactoryConfiguration() && pid.equals(cfg.getPid())) {
+ return cfg;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Get the factory configuration
+ * @param factoryPid The factoryPid of the configuration
+ * @param name The name of the configuration
+ * @return The factory configuration or {@code null}
+ */
+ public Configuration getFactoryConfiguration(final String factoryPid, final String name) {
+ for(final Configuration cfg : this) {
+ if ( cfg.isFactoryConfiguration()
+ && factoryPid.equals(cfg.getFactoryPid())
+ && name.equals(cfg.getName())) {
+ return cfg;
+ }
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/org/apache/sling/feature/Extension.java b/src/main/java/org/apache/sling/feature/Extension.java
new file mode 100644
index 0000000..a3e8082
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/Extension.java
@@ -0,0 +1,190 @@
+/*
+ * 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.sling.feature;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * An Extension can either be of type
+ * <ul>
+ * <li>Artifacts : it contains a list of artifacts
+ * <li>Text : it contains text
+ * <li>JSON : it contains a blob of JSON
+ * </ul>
+ *
+ * @see ExtensionType
+ */
+public class Extension {
+
+ /** Common extension name to specify the repoinit part for Apache Sling. */
+ public static final String NAME_REPOINIT = "repoinit";
+
+ /** Common extension name to specify the content packages for Apache Sling. */
+ public static final String NAME_CONTENT_PACKAGES = "content-packages";
+
+ /** The extension type */
+ private final ExtensionType type;
+
+ /** The extension name. */
+ private final String name;
+
+ /** The list of artifacts (if type artifacts) */
+ private final List<Artifact> artifacts;
+
+ /** The text or json (if corresponding type) */
+ private String text;
+
+ /** Whether the artifact is required. */
+ private final boolean required;
+
+ /**
+ * Create a new extension
+ * @param t The type of the extension
+ * @param name The name of the extension
+ * @param required Whether the extension is required or optional
+ * @throws IllegalArgumentException If name or t are {@code null}
+ */
+ public Extension(final ExtensionType t,
+ final String name,
+ final boolean required) {
+ if ( t == null || name == null ) {
+ throw new IllegalArgumentException("Argument must not be null");
+ }
+ this.type = t;
+ this.name = name;
+ this.required = required;
+ if ( t == ExtensionType.ARTIFACTS ) {
+ this.artifacts = new ArrayList<>();
+ } else {
+ this.artifacts = null;
+ }
+ }
+
+ /**
+ * Get the extension type
+ * @return The type
+ */
+ public ExtensionType getType() {
+ return this.type;
+ }
+
+ /**
+ * Get the extension name
+ * @return The name
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Return whether the extension is required or optional
+ * @return Return {@code true} if the extension is required.
+ */
+ public boolean isRequired() {
+ return this.required;
+ }
+
+ /**
+ * Return whether the extension is required or optional
+ * @return Return {@code true} if the extension is optional.
+ */
+ public boolean isOptional() {
+ return !this.isRequired();
+ }
+
+ /**
+ * Get the text of the extension
+ * @return The text
+ * @throws IllegalStateException if the type is not {@code ExtensionType#TEXT}
+ */
+ public String getText() {
+ if ( type != ExtensionType.TEXT ) {
+ throw new IllegalStateException();
+ }
+ return text;
+ }
+
+ /**
+ * Set the text of the extension
+ * @param text The text
+ * @throws IllegalStateException if the type is not {@code ExtensionType#TEXT}
+ */
+ public void setText(final String text) {
+ if ( type != ExtensionType.TEXT ) {
+ throw new IllegalStateException();
+ }
+ this.text = text;
+ }
+
+ /**
+ * Get the JSON of the extension
+ * @return The JSON
+ * @throws IllegalStateException if the type is not {@code ExtensionType#JSON}
+ */
+ public String getJSON() {
+ if ( type != ExtensionType.JSON ) {
+ throw new IllegalStateException();
+ }
+ return text;
+ }
+
+ /**
+ * Set the JSON of the extension
+ * @param text The JSON
+ * @throws IllegalStateException if the type is not {@code ExtensionType#JSON}
+ */
+ public void setJSON(String text) {
+ if ( type != ExtensionType.JSON ) {
+ throw new IllegalStateException();
+ }
+ this.text = text;
+ }
+
+ /**
+ * Get the artifacts of the extension
+ * @return The artifacts
+ * @throws IllegalStateException if the type is not {@code ExtensionType#ARTIFACTS}
+ */
+ public List<Artifact> getArtifacts() {
+ if ( type != ExtensionType.ARTIFACTS ) {
+ throw new IllegalStateException();
+ }
+ return artifacts;
+ }
+
+ @Override
+ public int hashCode() {
+ return name.hashCode();
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ return name.equals(((Extension)obj).name);
+ }
+
+ @Override
+ public String toString() {
+ return "Extension [type=" + type + ", name=" + name + "]";
+ }
+}
diff --git a/src/main/java/org/apache/sling/feature/ExtensionType.java b/src/main/java/org/apache/sling/feature/ExtensionType.java
new file mode 100644
index 0000000..2b8b221
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/ExtensionType.java
@@ -0,0 +1,27 @@
+/*
+ * 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.sling.feature;
+
+/**
+ * Enumeration for {@link Extension} types.
+ */
+public enum ExtensionType {
+
+ ARTIFACTS,
+ TEXT,
+ JSON
+}
diff --git a/src/main/java/org/apache/sling/feature/Extensions.java b/src/main/java/org/apache/sling/feature/Extensions.java
new file mode 100644
index 0000000..703eac2
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/Extensions.java
@@ -0,0 +1,41 @@
+/*
+ * 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.sling.feature;
+
+import java.util.ArrayList;
+
+/**
+ * A container for extensions
+ */
+public class Extensions extends ArrayList<Extension> {
+
+ private static final long serialVersionUID = -3850006820840607498L;
+
+ /**
+ * Get an extension by name
+ * @param name The name
+ * @return The {@link Extension} or {@code null}
+ */
+ public Extension getByName(final String name) {
+ for(final Extension ext : this) {
+ if ( ext.getName().equals(name) ) {
+ return ext;
+ }
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/org/apache/sling/feature/Feature.java b/src/main/java/org/apache/sling/feature/Feature.java
new file mode 100644
index 0000000..3665689
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/Feature.java
@@ -0,0 +1,408 @@
+/*
+ * 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.sling.feature;
+
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A feature consists of
+ * <ul>
+ * <li>A unique id {@link ArtifactId}
+ * <li>Bundles
+ * <li>Configurations
+ * <li>Framework properties
+ * <li>Requirements and capabilities
+ * <li>Includes
+ * <li>Extensions
+ * </ul>
+ */
+public class Feature implements Comparable<Feature> {
+
+ private final ArtifactId id;
+
+ private final Bundles bundles = new Bundles();
+
+ private final Configurations configurations = new Configurations();
+
+ private final KeyValueMap frameworkProperties = new KeyValueMap();
+
+ private final List<Requirement> requirements = new ArrayList<>();
+
+ private final List<Capability> capabilities = new ArrayList<>();
+
+ private final List<Include> includes = new ArrayList<>();
+
+ private final Extensions extensions = new Extensions();
+
+ /** The optional location. */
+ private volatile String location;
+
+ /** The optional title. */
+ private volatile String title;
+
+ /** The optional description. */
+ private volatile String description;
+
+ /** The optional vendor. */
+ private volatile String vendor;
+
+ /** The optional license. */
+ private volatile String license;
+
+ /** Is this an upgrade of another feature? */
+ private volatile ArtifactId upgradeOf;
+
+ /** Flag indicating whether this is an assembled feature */
+ private volatile boolean assembled = false;
+
+ /** Contained upgrades (this is usually only set for assembled features*/
+ private final List<ArtifactId> upgrades = new ArrayList<>();
+
+ /**
+ * Construct a new feature.
+ * @param id The id of the feature.
+ * @throws IllegalArgumentException If id is {@code null}.
+ */
+ public Feature(final ArtifactId id) {
+ if ( id == null ) {
+ throw new IllegalArgumentException("id must not be null.");
+ }
+ this.id = id;
+ }
+
+ /**
+ * Get the id of the artifact.
+ * @return The id.
+ */
+ public ArtifactId getId() {
+ return this.id;
+ }
+
+ /**
+ * Get the location.
+ * The location might be the location of the feature file or any other
+ * means identifying where the object is defined.
+ * @return The location or {@code null}.
+ */
+ public String getLocation() {
+ return this.location;
+ }
+
+ /**
+ * Set the location.
+ * @param value The new location.
+ */
+ public void setLocation(final String value) {
+ this.location = value;
+ }
+
+ /**
+ * Get the bundles.
+ * @return The bundles object.
+ */
+ public Bundles getBundles() {
+ return this.bundles;
+ }
+
+ /**
+ * Get the configurations.
+ * The returned object is modifiable.
+ * @return The configurations
+ */
+ public Configurations getConfigurations() {
+ return this.configurations;
+ }
+
+ /**
+ * Get the framework properties
+ * The returned object is modifiable.
+ * @return The framework properties
+ */
+ public KeyValueMap getFrameworkProperties() {
+ return this.frameworkProperties;
+ }
+
+ /**
+ * Get the list of requirements.
+ * The returned object is modifiable.
+ * @return The list of requirements
+ */
+ public List<Requirement> getRequirements() {
+ return requirements;
+ }
+
+ /**
+ * Get the list of capabilities.
+ * The returned object is modifiable.
+ * @return The list of capabilities
+ */
+ public List<Capability> getCapabilities() {
+ return capabilities;
+ }
+
+ /**
+ * Get the list of includes.
+ * The returned object is modifiable.
+ * @return The list of includes
+ */
+ public List<Include> getIncludes() {
+ return includes;
+ }
+
+ /**
+ * Get the list of extensions.
+ * The returned object is modifiable.
+ * @return The list of extensions
+ */
+ public Extensions getExtensions() {
+ return this.extensions;
+ }
+
+ /**
+ * Get the title
+ * @return The title or {@code null}
+ */
+ public String getTitle() {
+ return title;
+ }
+
+ /**
+ * Set the title
+ * @param title The title
+ */
+ public void setTitle(final String title) {
+ this.title = title;
+ }
+
+ /**
+ * Get the description
+ * @return The description or {@code null}
+ */
+ public String getDescription() {
+ return description;
+ }
+
+ /**
+ * Set the description
+ * @param description The description
+ */
+ public void setDescription(final String description) {
+ this.description = description;
+ }
+
+ /**
+ * Get the vendor
+ * @return The vendor or {@code null}
+ */
+ public String getVendor() {
+ return vendor;
+ }
+
+ /**
+ * Set the vendor
+ * @param vendor The vendor
+ */
+ public void setVendor(final String vendor) {
+ this.vendor = vendor;
+ }
+
+ /**
+ * Get the license
+ * @return The license or {@code null}
+ */
+ public String getLicense() {
+ return license;
+ }
+
+ /**
+ * Set the vendor
+ * @param license The license
+ */
+ public void setLicense(final String license) {
+ this.license = license;
+ }
+
+ /**
+ * Set the upgrade of information
+ * @param id The artifact id
+ */
+ public void setUpgradeOf(final ArtifactId id) {
+ this.upgradeOf = id;
+ }
+
+ /**
+ * Get the artifact id of the upgrade of information
+ * @return The artifact id or {@code null}
+ */
+ public ArtifactId getUpgradeOf() {
+ return this.upgradeOf;
+ }
+
+ /**
+ * Get the list of upgrades applied to this feature
+ * The returned object is modifiable.
+ * @return The list of upgrades
+ */
+ public List<ArtifactId> getUpgrades() {
+ return this.upgrades;
+ }
+
+ /**
+ * Check whether the feature is already assembled
+ * @return {@code true} if it is assembled, {@code false} if it needs to be assembled
+ */
+ public boolean isAssembled() {
+ return assembled;
+ }
+
+ /**
+ * Set the assembled flag
+ * @param flag The flag
+ */
+ public void setAssembled(final boolean flag) {
+ this.assembled = flag;
+ }
+
+ /**
+ * Create a copy of the feature
+ * @return A copy of the feature
+ */
+ public Feature copy() {
+ return copy(this.getId());
+ }
+
+ /**
+ * Create a copy of the feature with a different id
+ * @param id The new id
+ * @return The copy of the feature with the new id
+ */
+ public Feature copy(final ArtifactId id) {
+ final Feature result = new Feature(id);
+
+ // metadata
+ result.setLocation(this.getLocation());
+ result.setTitle(this.getTitle());
+ result.setDescription(this.getDescription());
+ result.setVendor(this.getVendor());
+ result.setLicense(this.getLicense());
+ result.setAssembled(this.isAssembled());
+
+ // bundles
+ for(final Map.Entry<Integer, Artifact> entry : this.getBundles()) {
+ final Artifact c = new Artifact(entry.getValue().getId());
+ c.getMetadata().putAll(entry.getValue().getMetadata());
+
+ result.getBundles().add(entry.getKey(), c);
+ }
+
+ // configurations
+ for(final Configuration cfg : this.getConfigurations()) {
+ final Configuration c = cfg.isFactoryConfiguration() ? new Configuration(cfg.getFactoryPid(), cfg.getName()) : new Configuration(cfg.getPid());
+ final Enumeration<String> keyEnum = cfg.getProperties().keys();
+ while ( keyEnum.hasMoreElements() ) {
+ final String key = keyEnum.nextElement();
+ c.getProperties().put(key, cfg.getProperties().get(key));
+ }
+ result.getConfigurations().add(c);
+ }
+
+ // framework properties
+ result.getFrameworkProperties().putAll(this.getFrameworkProperties());
+
+ // requirements
+ for(final Requirement r : this.getRequirements()) {
+ final Requirement c = new Requirement(r.getNamespace());
+ c.getAttributes().putAll(r.getAttributes());
+ c.getDirectives().putAll(r.getDirectives());
+ result.getRequirements().add(c);
+ }
+
+ // capabilities
+ for(final Capability r : this.getCapabilities()) {
+ final Capability c = new Capability(r.getNamespace());
+ c.getAttributes().putAll(r.getAttributes());
+ c.getDirectives().putAll(r.getDirectives());
+ result.getCapabilities().add(c);
+ }
+
+ // includes
+ for(final Include i : this.getIncludes()) {
+ final Include c = new Include(i.getId());
+
+ c.getBundleRemovals().addAll(i.getBundleRemovals());
+ c.getConfigurationRemovals().addAll(i.getConfigurationRemovals());
+ c.getExtensionRemovals().addAll(i.getExtensionRemovals());
+ c.getFrameworkPropertiesRemovals().addAll(i.getFrameworkPropertiesRemovals());
+ c.getArtifactExtensionRemovals().putAll(c.getArtifactExtensionRemovals());
+
+ result.getIncludes().add(c);
+ }
+
+ // extensions
+ for(final Extension e : this.getExtensions()) {
+ final Extension c = new Extension(e.getType(), e.getName(), e.isRequired());
+ switch ( c.getType() ) {
+ case ARTIFACTS : for(final Artifact a : e.getArtifacts()) {
+ final Artifact x = new Artifact(a.getId());
+ x.getMetadata().putAll(a.getMetadata());
+ c.getArtifacts().add(x);
+ }
+ break;
+ case JSON : c.setJSON(e.getJSON());
+ break;
+ case TEXT : c.setText(e.getText());
+ break;
+ }
+ result.getExtensions().add(c);
+ }
+
+ return result;
+ }
+
+ @Override
+ public int compareTo(final Feature o) {
+ return this.id.compareTo(o.id);
+ }
+
+ @Override
+ public int hashCode() {
+ return this.id.hashCode();
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ return this.id.equals(((Feature)obj).id);
+ }
+
+ @Override
+ public String toString() {
+ return (this.isAssembled() ? "Assembled Feature" : "Feature") +
+ " [id=" + this.getId().toMvnId()
+ + ( this.getLocation() != null ? ", location=" + this.getLocation() : "")
+ + "]";
+ }
+
+}
diff --git a/src/main/java/org/apache/sling/feature/Include.java b/src/main/java/org/apache/sling/feature/Include.java
new file mode 100644
index 0000000..eb28293
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/Include.java
@@ -0,0 +1,115 @@
+/*
+ * 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.sling.feature;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A include is an inclusion of a feature with optional removals of
+ * <ul>
+ * <li>Configurations / configuration properties
+ * <li>Bundles
+ * <li>Framework properties
+ * <li>Extensions or artifacts from extensions
+ * </ul>
+ *
+ * TODO - requirement, capabilities
+ */
+public class Include implements Comparable<Include> {
+
+ private final ArtifactId id;
+
+ private final List<String> configurationRemovals = new ArrayList<>();
+
+ private final List<ArtifactId> bundleRemovals = new ArrayList<>();
+
+ private final List<String> frameworkPropertiesRemovals = new ArrayList<>();
+
+ private final List<String> extensionRemovals = new ArrayList<>();
+
+ private final Map<String, List<ArtifactId>> artifactExtensionRemovals = new HashMap<>();
+
+ /**
+ * Construct a new Include.
+ * @param id The id of the feature.
+ * @throws IllegalArgumentException If id is {@code null}.
+ */
+ public Include(final ArtifactId id) {
+ if ( id == null ) {
+ throw new IllegalArgumentException("id must not be null.");
+ }
+ this.id = id;
+ }
+
+ /**
+ * Get the id of the artifact.
+ * @return The id.
+ */
+ public ArtifactId getId() {
+ return this.id;
+ }
+
+ public List<String> getConfigurationRemovals() {
+ return configurationRemovals;
+ }
+
+ public List<ArtifactId> getBundleRemovals() {
+ return bundleRemovals;
+ }
+
+ public List<String> getFrameworkPropertiesRemovals() {
+ return frameworkPropertiesRemovals;
+ }
+
+ public List<String> getExtensionRemovals() {
+ return extensionRemovals;
+ }
+
+ public Map<String, List<ArtifactId>> getArtifactExtensionRemovals() {
+ return artifactExtensionRemovals;
+ }
+
+ @Override
+ public int compareTo(final Include o) {
+ return this.id.compareTo(o.id);
+ }
+
+ @Override
+ public int hashCode() {
+ return this.id.hashCode();
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ return this.id.equals(((Include)obj).id);
+ }
+
+ @Override
+ public String toString() {
+ return "Include [id=" + id.toMvnId()
+ + "]";
+ }
+}
diff --git a/src/main/java/org/apache/sling/feature/KeyValueMap.java b/src/main/java/org/apache/sling/feature/KeyValueMap.java
new file mode 100644
index 0000000..ce8c1c8
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/KeyValueMap.java
@@ -0,0 +1,125 @@
+/*
+ * 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.sling.feature;
+
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.TreeMap;
+
+/**
+ * Helper class to hold key value pairs.
+ */
+public class KeyValueMap
+ implements Iterable<Map.Entry<String, String>> {
+
+ /** The map holding the actual key value pairs. */
+ private final Map<String, String> properties = new TreeMap<>();
+
+ /**
+ * Get an item from the map.
+ * @param key The key of the item.
+ * @return The item or {@code null}.
+ */
+ public String get(final String key) {
+ return this.properties.get(key);
+ }
+
+ /**
+ * Put an item in the map
+ * @param key The key of the item.
+ * @param value The value
+ */
+ public void put(final String key, final String value) {
+ this.properties.put(key, value);
+ }
+
+ /**
+ * Remove an item from the map
+ * @param key The key of the item.
+ * @return The previously stored value for the key or {@code null}.
+ */
+ public String remove(final String key) {
+ return this.properties.remove(key);
+ }
+
+ /**
+ * Put all items from the other map in this map
+ * @param map The other map
+ */
+ public void putAll(final KeyValueMap map) {
+ this.properties.putAll(map.properties);
+ }
+
+ @Override
+ public Iterator<Entry<String, String>> iterator() {
+ return this.properties.entrySet().iterator();
+ }
+
+ /**
+ * Check whether this map is empty.
+ * @return {@code true} if the map is empty.
+ */
+ public boolean isEmpty() {
+ return this.properties.isEmpty();
+ }
+
+ @Override
+ public String toString() {
+ return properties.toString();
+ }
+
+ /**
+ * Get the size of the map.
+ * @return The size of the map.
+ */
+ public int size() {
+ return this.properties.size();
+ }
+
+ /**
+ * Clear the map
+ */
+ public void clear() {
+ this.properties.clear();
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((properties == null) ? 0 : properties.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ KeyValueMap other = (KeyValueMap) obj;
+ if (properties == null) {
+ if (other.properties != null)
+ return false;
+ } else if (!properties.equals(other.properties))
+ return false;
+ return true;
+ }
+}
diff --git a/src/main/java/org/apache/sling/feature/Requirement.java b/src/main/java/org/apache/sling/feature/Requirement.java
new file mode 100644
index 0000000..3409e51
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/Requirement.java
@@ -0,0 +1,113 @@
+/*
+ * 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.sling.feature;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * A requirement for a feature.
+ * The requirement is modeled after an OSGi requirement: it
+ * belongs to a namespace and might have attributes and / or
+ * directives.
+ */
+public class Requirement {
+
+ /*public static final String RESOLUTION_OPTIONAL = "optional";
+
+ public static final String RESOLUTION_DIRECTIVE = "resolution";*/
+
+ /** The namspace. */
+ private final String namespace;
+
+ /** Map of attributes. */
+ private final Map<String, Object> attributes = new ConcurrentHashMap<>();
+
+ /** Map of directives. */
+ private final Map<String, Object> directives = new ConcurrentHashMap<>();
+
+ /**
+ * Create a new Requirement.
+ * @param namespace The namespace
+ * @throws IllegalArgumentException If namespace is {@code null}.
+ */
+ public Requirement(final String namespace) {
+ if ( namespace == null ) {
+ throw new IllegalArgumentException("namespace must not be null.");
+ }
+ this.namespace = namespace;
+ }
+
+ /**
+ * The namespace
+ * @return The namespace
+ */
+ public String getNamespace() {
+ return namespace;
+ }
+
+ /**
+ * Get the map of attributes.
+ * The map is modifiable.
+ * @return The map of attributes.
+ */
+ public Map<String,Object> getAttributes() {
+ return attributes;
+ }
+
+ /**
+ * Get the map of directives.
+ * The map is modifiable.
+ * @return The map of directives.
+ */
+ public Map<String,Object> getDirectives() {
+ return directives;
+ }
+
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + attributes.hashCode();
+ result = prime * result + directives.hashCode();
+ result = prime * result + namespace.hashCode();
+ return result;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ final Requirement other = (Requirement) obj;
+ if (!attributes.equals(other.attributes)
+ || !directives.equals(other.directives)
+ || !namespace.equals(other.namespace)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "[Requirement namespace='" + namespace + "' attributes=" + attributes + " directives=" + directives + "]";
+ }
+}
diff --git a/src/main/java/org/apache/sling/feature/package-info.java b/src/main/java/org/apache/sling/feature/package-info.java
new file mode 100644
index 0000000..0ec26d7
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/package-info.java
@@ -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.
+ */
+
+@org.osgi.annotation.versioning.Version("1.0.0")
+package org.apache.sling.feature;
+
+
diff --git a/src/main/java/org/apache/sling/feature/process/ApplicationBuilder.java b/src/main/java/org/apache/sling/feature/process/ApplicationBuilder.java
new file mode 100644
index 0000000..cc0f235
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/process/ApplicationBuilder.java
@@ -0,0 +1,161 @@
+/*
+ * 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.sling.feature.process;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.sling.feature.Application;
+import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.Feature;
+
+/**
+ * Build an application based on features.
+ */
+public class ApplicationBuilder {
+
+ /**
+ * Assemble an application based on the provided feature Ids.
+ *
+ * Upgrade features are only applied if the provided feature list
+ * contains the feature to be upgraded. Otherwise the upgrade feature
+ * is ignored.
+ *
+ * @param app The optional application to use as a base.
+ * @param context The builder context
+ * @param featureIds The feature ids
+ * @return The application
+ * throws IllegalArgumentException If context or featureIds is {@code null}
+ * throws IllegalStateException If the provided ids are invalid, or the feature can't be provided
+ */
+ public static Application assemble(final Application app,
+ final BuilderContext context,
+ final String... featureIds) {
+ if ( featureIds == null || context == null ) {
+ throw new IllegalArgumentException("Features and/or context must not be null");
+ }
+
+ final Feature[] features = new Feature[featureIds.length];
+ int index = 0;
+ for(final String id : featureIds) {
+ features[index] = context.getFeatureProvider().provide(ArtifactId.parse(id));
+ if ( features[index] == null ) {
+ throw new IllegalStateException("Unable to find included feature " + id);
+ }
+ index++;
+ }
+ return assemble(app, context, features);
+ }
+
+ /**
+ * Assemble an application based on the provided features.
+ *
+ * Upgrade features are only applied if the provided feature list
+ * contains the feature to be upgraded. Otherwise the upgrade feature
+ * is ignored.
+ *
+ * @param app The optional application to use as a base.
+ * @param context The builder context
+ * @param features The features
+ * @return The application
+ * throws IllegalArgumentException If context or featureIds is {@code null}
+ * throws IllegalStateException If a feature can't be provided
+ */
+ public static Application assemble(
+ Application app,
+ final BuilderContext context,
+ final Feature... features) {
+ if ( features == null || context == null ) {
+ throw new IllegalArgumentException("Features and/or context must not be null");
+ }
+
+ if ( app == null ) {
+ app = new Application();
+ }
+
+ // detect upgrades and created sorted feature list
+ final Map<Feature, List<Feature>> upgrades = new HashMap<>();
+ final List<Feature> sortedFeatureList = new ArrayList<>();
+ for(final Feature f : features) {
+ if ( f.getUpgradeOf() != null ) {
+ for(final Feature i : features) {
+ if ( i.getId().equals(f.getUpgradeOf()) ) {
+ List<Feature> u = upgrades.get(i);
+ if ( u == null ) {
+ u = new ArrayList<>();
+ upgrades.put(i, u);
+ }
+ u.add(f);
+ app.getFeatureIds().add(f.getId());
+ break;
+ }
+ }
+ } else {
+ app.getFeatureIds().add(f.getId());
+ sortedFeatureList.add(f);
+ }
+ }
+
+ // process upgrades first
+ for(final Map.Entry<Feature, List<Feature>> entry : upgrades.entrySet()) {
+ final Feature assembled = FeatureBuilder.assemble(entry.getKey(),
+ entry.getValue(),
+ context);
+ // update feature to assembled feature
+ sortedFeatureList.remove(entry.getKey());
+ sortedFeatureList.add(assembled);
+ }
+
+ // sort
+ Collections.sort(sortedFeatureList);
+
+ // assemble
+ for(final Feature f : sortedFeatureList) {
+ final Feature assembled = FeatureBuilder.assemble(f, context.clone(new FeatureProvider() {
+
+ @Override
+ public Feature provide(final ArtifactId id) {
+ for(final Feature f : upgrades.keySet()) {
+ if ( f.getId().equals(id) ) {
+ return f;
+ }
+ }
+ for(final Feature f : features) {
+ if ( f.getId().equals(id) ) {
+ return f;
+ }
+ }
+ return context.getFeatureProvider().provide(id);
+ }
+ }));
+
+ merge(app, assembled);
+ }
+
+ return app;
+ }
+
+ private static void merge(final Application target, final Feature source) {
+ BuilderUtil.mergeBundles(target.getBundles(), source.getBundles(), BuilderUtil.ArtifactMerge.HIGHEST);
+ BuilderUtil.mergeConfigurations(target.getConfigurations(), source.getConfigurations());
+ BuilderUtil.mergeFrameworkProperties(target.getFrameworkProperties(), source.getFrameworkProperties());
+ BuilderUtil.mergeExtensions(target, source, BuilderUtil.ArtifactMerge.HIGHEST);
+ }
+}
diff --git a/src/main/java/org/apache/sling/feature/process/BuilderContext.java b/src/main/java/org/apache/sling/feature/process/BuilderContext.java
new file mode 100644
index 0000000..024076d
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/process/BuilderContext.java
@@ -0,0 +1,67 @@
+/*
+ * 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.sling.feature.process;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Builder context holds services used by {@link ApplicationBuilder}
+ * and {@link FeatureBuilder}.
+ */
+public class BuilderContext {
+
+ private final FeatureProvider provider;
+
+ private final List<FeatureExtensionHandler> featureExtensionHandlers = new ArrayList<>();
+
+ /**
+ * Assemble the full feature by processing all includes.
+ *
+ * @param feature The feature to start
+ * @param provider A provider providing the included features
+ * @param extensionMergers Optional feature mergers
+ * @return The assembled feature.
+ * @throws IllegalArgumentException If feature or provider is {@code null}
+ * @throws IllegalStateException If an included feature can't be provided or merged.
+ */
+ public BuilderContext(final FeatureProvider provider) {
+ if ( provider == null ) {
+ throw new IllegalArgumentException("Provider must not be null");
+ }
+ this.provider = provider;
+ }
+
+ FeatureProvider getFeatureProvider() {
+ return this.provider;
+ }
+
+ List<FeatureExtensionHandler> getFeatureExtensionHandlers() {
+ return this.featureExtensionHandlers;
+ }
+
+ public BuilderContext add(final FeatureExtensionHandler handler) {
+ featureExtensionHandlers.add(handler);
+ return this;
+ }
+
+ BuilderContext clone(final FeatureProvider featureProvider) {
+ final BuilderContext ctx = new BuilderContext(featureProvider);
+ ctx.featureExtensionHandlers.addAll(featureExtensionHandlers);
+ return ctx;
+ }
+}
diff --git a/src/main/java/org/apache/sling/feature/process/BuilderUtil.java b/src/main/java/org/apache/sling/feature/process/BuilderUtil.java
new file mode 100644
index 0000000..19c606c
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/process/BuilderUtil.java
@@ -0,0 +1,261 @@
+/*
+ * 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.sling.feature.process;
+
+import java.io.StringReader;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+
+import javax.json.Json;
+import javax.json.JsonArray;
+import javax.json.JsonObject;
+import javax.json.JsonStructure;
+import javax.json.JsonValue;
+import javax.json.JsonValue.ValueType;
+
+import org.apache.sling.feature.Application;
+import org.apache.sling.feature.Artifact;
+import org.apache.sling.feature.Bundles;
+import org.apache.sling.feature.Capability;
+import org.apache.sling.feature.Configuration;
+import org.apache.sling.feature.Configurations;
+import org.apache.sling.feature.Extension;
+import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.KeyValueMap;
+import org.apache.sling.feature.Requirement;
+
+/**
+ * Utility methods for the builders
+ */
+class BuilderUtil {
+
+ public enum ArtifactMerge {
+ LATEST,
+ HIGHEST
+ };
+
+ // bundles
+ static void mergeBundles(final Bundles target,
+ final Bundles source,
+ final ArtifactMerge artifactMergeAlg) {
+ for(final Map.Entry<Integer, List<Artifact>> entry : source.getBundlesByStartLevel().entrySet()) {
+ for(final Artifact a : entry.getValue()) {
+ // version handling - use provided algorithm
+ boolean replace = true;
+ if ( artifactMergeAlg == ArtifactMerge.HIGHEST ) {
+ final Map.Entry<Integer, Artifact> existing = target.getSame(a.getId());
+ if ( existing != null && existing.getValue().getId().getOSGiVersion().compareTo(a.getId().getOSGiVersion()) > 0 ) {
+ replace = false;
+ }
+ }
+ if ( replace ) {
+ target.removeSame(a.getId());
+ target.add(entry.getKey(), a);
+ }
+ }
+ }
+ }
+
+ // configurations - merge / override
+ static void mergeConfigurations(final Configurations target, final Configurations source) {
+ for(final Configuration cfg : source) {
+ boolean found = false;
+ for(final Configuration current : target) {
+ if ( current.compareTo(cfg) == 0 ) {
+ found = true;
+ // merge / override properties
+ final Enumeration<String> i = cfg.getProperties().keys();
+ while ( i.hasMoreElements() ) {
+ final String key = i.nextElement();
+ current.getProperties().put(key, cfg.getProperties().get(key));
+ }
+ break;
+ }
+ }
+ if ( !found ) {
+ target.add(cfg);
+ }
+ }
+ }
+
+ // framework properties (add/merge)
+ static void mergeFrameworkProperties(final KeyValueMap target, final KeyValueMap source) {
+ target.putAll(source);
+ }
+
+ // requirements (add)
+ static void mergeRequirements(final List<Requirement> target, final List<Requirement> source) {
+ for(final Requirement req : source) {
+ if ( !target.contains(req) ) {
+ target.add(req);
+ }
+ }
+ }
+
+ // capabilities (add)
+ static void mergeCapabilities(final List<Capability> target, final List<Capability> source) {
+ for(final Capability cap : source) {
+ if ( !target.contains(cap) ) {
+ target.add(cap);
+ }
+ }
+ }
+
+ // default merge for extensions
+ static void mergeExtensions(final Extension target,
+ final Extension source,
+ final ArtifactMerge artifactMergeAlg) {
+ switch ( target.getType() ) {
+ case TEXT : // simply append
+ target.setText(target.getText() + "\n" + source.getText());
+ break;
+ case JSON : final JsonStructure struct1;
+ try ( final StringReader reader = new StringReader(target.getJSON()) ) {
+ struct1 = Json.createReader(reader).read();
+ }
+ final JsonStructure struct2;
+ try ( final StringReader reader = new StringReader(source.getJSON()) ) {
+ struct2 = Json.createReader(reader).read();
+ }
+
+ if ( struct1.getValueType() != struct2.getValueType() ) {
+ throw new IllegalStateException("Found different JSON types for extension " + target.getName()
+ + " : " + struct1.getValueType() + " and " + struct2.getValueType());
+ }
+ if ( struct1.getValueType() == ValueType.ARRAY ) {
+ // array is append
+ final JsonArray a1 = (JsonArray)struct1;
+ final JsonArray a2 = (JsonArray)struct2;
+ for(final JsonValue val : a2) {
+ a1.add(val);
+ }
+ } else {
+ // object is merge
+ merge((JsonObject)struct1, (JsonObject)struct2);
+ }
+ break;
+
+ case ARTIFACTS : for(final Artifact a : source.getArtifacts()) {
+ // use artifactMergeAlg
+ boolean add = true;
+ for(final Artifact targetArtifact : target.getArtifacts()) {
+ if ( targetArtifact.getId().isSame(a.getId()) ) {
+ if ( artifactMergeAlg == ArtifactMerge.HIGHEST ) {
+ if ( targetArtifact.getId().getOSGiVersion().compareTo(a.getId().getOSGiVersion()) > 0 ) {
+ add = false;
+ } else {
+ target.getArtifacts().remove(targetArtifact);
+ }
+ } else { // latest
+
+ target.getArtifacts().remove(targetArtifact);
+ }
+ break;
+ }
+ }
+
+ if ( add ) {
+ target.getArtifacts().add(a);
+ }
+
+ }
+ break;
+ }
+ }
+
+ // extensions (add/merge)
+ static void mergeExtensions(final Feature target,
+ final Feature source,
+ final ArtifactMerge artifactMergeAlg,
+ final BuilderContext context) {
+ for(final Extension ext : source.getExtensions()) {
+ boolean found = false;
+ for(final Extension current : target.getExtensions()) {
+ if ( current.getName().equals(ext.getName()) ) {
+ found = true;
+ if ( current.getType() != ext.getType() ) {
+ throw new IllegalStateException("Found different types for extension " + current.getName()
+ + " : " + current.getType() + " and " + ext.getType());
+ }
+ boolean handled = false;
+ for(final FeatureExtensionHandler fem : context.getFeatureExtensionHandlers()) {
+ if ( fem.canMerge(current.getName()) ) {
+ fem.merge(target, source, current.getName());
+ handled = true;
+ break;
+ }
+ }
+ if ( !handled ) {
+ // default merge
+ mergeExtensions(current, ext, artifactMergeAlg);
+ }
+ }
+ }
+ if ( !found ) {
+ target.getExtensions().add(ext);
+ }
+ }
+ }
+
+ static void mergeExtensions(final Application target,
+ final Feature source,
+ final ArtifactMerge artifactMergeAlg) {
+ for(final Extension ext : source.getExtensions()) {
+ boolean found = false;
+ for(final Extension current : target.getExtensions()) {
+ if ( current.getName().equals(ext.getName()) ) {
+ found = true;
+ if ( current.getType() != ext.getType() ) {
+ throw new IllegalStateException("Found different types for extension " + current.getName()
+ + " : " + current.getType() + " and " + ext.getType());
+ }
+ // default merge
+ mergeExtensions(current, ext, artifactMergeAlg);
+ }
+ }
+ if ( !found ) {
+ target.getExtensions().add(ext);
+ }
+ }
+ }
+
+ private static void merge(final JsonObject obj1, final JsonObject obj2) {
+ for(final Map.Entry<String, JsonValue> entry : obj2.entrySet()) {
+ if ( !obj1.containsKey(entry.getKey()) ) {
+ obj1.put(entry.getKey(), entry.getValue());
+ } else {
+ final JsonValue oldValue = obj1.get(entry.getKey());
+ if ( oldValue.getValueType() != entry.getValue().getValueType() ) {
+ // new type wins
+ obj1.put(entry.getKey(), entry.getValue());
+ } else if ( oldValue.getValueType() == ValueType.ARRAY ) {
+ final JsonArray a1 = (JsonArray)oldValue;
+ final JsonArray a2 = (JsonArray)entry.getValue();
+ for(final JsonValue val : a2) {
+ a1.add(val);
+ }
+
+ } else if ( oldValue.getValueType() == ValueType.OBJECT ) {
+ merge((JsonObject)oldValue, (JsonObject)entry.getValue());
+ } else {
+ obj1.put(entry.getKey(), entry.getValue());
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/org/apache/sling/feature/process/FeatureBuilder.java b/src/main/java/org/apache/sling/feature/process/FeatureBuilder.java
new file mode 100644
index 0000000..7208f00
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/process/FeatureBuilder.java
@@ -0,0 +1,291 @@
+/*
+ * 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.sling.feature.process;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.sling.feature.Artifact;
+import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.Configuration;
+import org.apache.sling.feature.Extension;
+import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.Include;
+
+public class FeatureBuilder {
+
+ /**
+ * Assemble the full feature by processing all includes.
+ *
+ * @param feature The feature to start
+ * @param context The builder context
+ * @return The assembled feature.
+ * @throws IllegalArgumentException If feature or context is {@code null}
+ * @throws IllegalStateException If an included feature can't be provided or merged.
+ */
+ public static Feature assemble(final Feature feature,
+ final BuilderContext context) {
+ if ( feature == null || context == null ) {
+ throw new IllegalArgumentException("Feature and/or context must not be null");
+ }
+ return internalAssemble(new ArrayList<>(), feature, context);
+ }
+
+ /**
+ * Assemble the final feature and apply upgrades
+ *
+ * If the list of upgrades contains upgrade features not intended for the
+ * provided feature, this is not considered an error situation. But the
+ * provided upgrade is ignored.
+ *
+ * @param feature The feature to start
+ * @param upgrades The list of upgrades. If this is {@code null} or empty, this method
+ * behaves like {@link #assemble(Feature, FeatureProvider)}.
+ * @param context The builder context
+ * @return The assembled feature.
+ * @throws IllegalArgumentException If feature or context is {@code null}
+ * @throws IllegalStateException If an included feature can't be provided
+ */
+ public static Feature assemble(final Feature feature,
+ final List<Feature> upgrades,
+ final BuilderContext context) {
+ if ( feature == null || context == null ) {
+ throw new IllegalArgumentException("Feature and/or context must not be null");
+ }
+
+ // check upgrades
+ List<Feature> useUpdates = null;
+ if ( upgrades != null && !upgrades.isEmpty() ) {
+ useUpdates = new ArrayList<>();
+ for(final Feature uf : upgrades) {
+ if ( !feature.getId().equals(uf.getUpgradeOf()) ) {
+ continue;
+ }
+ boolean found = false;
+ for(final Feature i : useUpdates) {
+ if ( i.getId().isSame(uf.getId()) ) {
+ if ( uf.getId().getOSGiVersion().compareTo(i.getId().getOSGiVersion()) > 0 ) {
+ useUpdates.remove(i);
+ } else {
+ found = true;
+ }
+ break;
+ }
+ }
+ if ( !found ) {
+ // we add a copy as we manipulate the upgrade below
+ useUpdates.add(uf.copy());
+ }
+ }
+ Collections.sort(useUpdates);
+ if ( useUpdates.isEmpty() ) {
+ useUpdates = null;
+ }
+ }
+
+ // assemble feature without upgrades
+ final Feature assembledFeature = internalAssemble(new ArrayList<>(), feature, context);
+
+ // handle upgrades
+ if ( useUpdates != null ) {
+ for(final Feature uf : useUpdates) {
+ Include found = null;
+ for(final Include inc : uf.getIncludes() ) {
+ if ( inc.getId().equals(assembledFeature.getId()) ) {
+ found = inc;
+ break;
+ }
+ }
+ if ( found != null ) {
+ uf.getIncludes().remove(found);
+
+ // process include instructions
+ include(assembledFeature, found);
+ }
+
+ // now assemble upgrade, but without considering the base
+ uf.setUpgradeOf(null);
+ assembledFeature.getUpgrades().add(uf.getId());
+ final Feature auf = assemble(uf, context);
+
+ // merge
+ merge(assembledFeature, auf, context);
+ }
+ }
+
+ return assembledFeature;
+ }
+
+ private static Feature internalAssemble(final List<String> processedFeatures,
+ final Feature feature,
+ final BuilderContext context) {
+ if ( feature.isAssembled() ) {
+ return feature;
+ }
+ if ( processedFeatures.contains(feature.getId().toMvnId()) ) {
+ throw new IllegalStateException("Recursive inclusion of " + feature.getId().toMvnId() + " via " + processedFeatures);
+ }
+ processedFeatures.add(feature.getId().toMvnId());
+
+ // we copy the feature as we set the assembled flag on the result
+ final Feature result;
+
+ if ( feature.getUpgradeOf() != null ) {
+ Include found = null;
+ for(final Include inc : feature.getIncludes()) {
+ if ( inc.getId().equals(feature.getUpgradeOf()) ) {
+ found = inc;
+ break;
+ }
+ }
+
+ result = feature.copy(feature.getUpgradeOf());
+
+ // add base as the first include
+ if ( found == null ) {
+ result.getIncludes().add(0, new Include(feature.getUpgradeOf()));
+ } else {
+ result.getIncludes().remove(found);
+ result.getIncludes().add(0, found);
+ }
+ result.getUpgrades().add(feature.getId());
+ } else {
+ result = feature.copy();
+ }
+
+ if ( !result.getIncludes().isEmpty() ) {
+
+ final List<Include> includes = new ArrayList<>(result.getIncludes());
+
+ // clear everything in the result, will be added in the process
+ result.getBundles().clear();
+ result.getFrameworkProperties().clear();
+ result.getConfigurations().clear();
+ result.getRequirements().clear();
+ result.getCapabilities().clear();
+ result.getIncludes().clear();
+ result.getExtensions().clear();
+
+ for(final Include i : includes) {
+ final Feature f = context.getFeatureProvider().provide(i.getId());
+ if ( f == null ) {
+ throw new IllegalStateException("Unable to find included feature " + i.getId());
+ }
+ final Feature af = internalAssemble(processedFeatures, f, context);
+
+ // process include instructions
+ include(af, i);
+
+ // and now merge
+ merge(result, af, context);
+ }
+ merge(result, feature, context);
+ }
+ processedFeatures.remove(feature.getId().toMvnId());
+
+ result.setAssembled(true);
+ return result;
+ }
+
+ private static void merge(final Feature target,
+ final Feature source,
+ final BuilderContext context) {
+ BuilderUtil.mergeBundles(target.getBundles(), source.getBundles(), BuilderUtil.ArtifactMerge.LATEST);
+ BuilderUtil.mergeConfigurations(target.getConfigurations(), source.getConfigurations());
+ BuilderUtil.mergeFrameworkProperties(target.getFrameworkProperties(), source.getFrameworkProperties());
+ BuilderUtil.mergeRequirements(target.getRequirements(), source.getRequirements());
+ BuilderUtil.mergeCapabilities(target.getCapabilities(), source.getCapabilities());
+ BuilderUtil.mergeExtensions(target,
+ source,
+ BuilderUtil.ArtifactMerge.LATEST,
+ context);
+ }
+
+ private static void include(final Feature base, final Include i) {
+ // process removals
+ // bundles
+ for(final ArtifactId a : i.getBundleRemovals()) {
+ base.getBundles().removeExact(a);
+ final Iterator<Configuration> iter = base.getConfigurations().iterator();
+ while ( iter.hasNext() ) {
+ final Configuration cfg = iter.next();
+ final String bundleId = (String)cfg.getProperties().get(Configuration.PROP_ARTIFACT);
+ if ( a.toMvnId().equals(bundleId) ) {
+ iter.remove();
+ }
+ }
+ }
+ // configurations
+ for(final String c : i.getConfigurationRemovals()) {
+ final int attrPos = c.indexOf('@');
+ final String val = (attrPos == -1 ? c : c.substring(0, attrPos));
+ final String attr = (attrPos == -1 ? null : c.substring(attrPos + 1));
+
+ final int sepPos = val.indexOf('~');
+ Configuration found = null;
+ if ( sepPos == -1 ) {
+ found = base.getConfigurations().getConfiguration(val);
+
+ } else {
+ final String factoryPid = val.substring(0, sepPos);
+ final String name = val.substring(sepPos + 1);
+
+ found = base.getConfigurations().getFactoryConfiguration(factoryPid, name);
+ }
+ if ( found != null ) {
+ if ( attr == null ) {
+ base.getConfigurations().remove(found);
+ } else {
+ found.getProperties().remove(attr);
+ }
+ }
+ }
+
+ // framework properties
+ for(final String p : i.getFrameworkPropertiesRemovals()) {
+ base.getFrameworkProperties().remove(p);
+ }
+
+ // extensions
+ for(final String name : i.getExtensionRemovals()) {
+ for(final Extension ext : base.getExtensions()) {
+ if ( ext.getName().equals(name) ) {
+ base.getExtensions().remove(ext);
+ break;
+ }
+ }
+ }
+ for(final Map.Entry<String, List<ArtifactId>> entry : i.getArtifactExtensionRemovals().entrySet()) {
+ for(final Extension ext : base.getExtensions()) {
+ if ( ext.getName().equals(entry.getKey()) ) {
+ for(final ArtifactId id : entry.getValue() ) {
+ for(final Artifact a : ext.getArtifacts()) {
+ if ( a.getId().equals(id) ) {
+ ext.getArtifacts().remove(a);
+ break;
+ }
+ }
+ }
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/org/apache/sling/feature/process/FeatureExtensionHandler.java b/src/main/java/org/apache/sling/feature/process/FeatureExtensionHandler.java
new file mode 100644
index 0000000..c2a9460
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/process/FeatureExtensionHandler.java
@@ -0,0 +1,55 @@
+/*
+ * 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.sling.feature.process;
+
+import org.apache.sling.feature.Feature;
+
+/**
+ * A feature extension handler can merge a feature of a particular type
+ * and also post process the final assembled feature.
+ */
+public interface FeatureExtensionHandler {
+
+ /**
+ * Checks whether this merger can merge extensions with that name
+ * @param extensionName The extension name
+ * @return {@code true} if merger can handle this
+ */
+ boolean canMerge(String extensionName);
+
+ /**
+ * Merge the source extension into the target extension.
+ *
+ * The caller of this method already ensured that both
+ * extensions share the same name and type and that
+ * {@link #canMerge(String)} returned {@code true}.
+ *
+ * @param target The target feature
+ * @param source The source feature
+ * @param extensionName The extension name
+ * @throws IllegalStateException If the extensions can't be merged
+ */
+ void merge(Feature target, Feature source, String extensionName);
+
+ /**
+ * Post process the feature with respect to the extension
+ * @param feature The feature
+ * @param extensionName The extension name
+ * @throws IllegalStateException If post processing failed
+ */
+ void postProcess(Feature feature, String extensionName);
+}
diff --git a/src/main/java/org/apache/sling/feature/process/FeatureProvider.java b/src/main/java/org/apache/sling/feature/process/FeatureProvider.java
new file mode 100644
index 0000000..8d3c35a
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/process/FeatureProvider.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.sling.feature.process;
+
+import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.Feature;
+
+public interface FeatureProvider {
+
+ /**
+ * Provide the feature with the given id.
+ * @param id The feature id
+ * @return The feature or {@code null}
+ */
+ Feature provide(ArtifactId id);
+}
diff --git a/src/main/java/org/apache/sling/feature/process/package-info.java b/src/main/java/org/apache/sling/feature/process/package-info.java
new file mode 100644
index 0000000..f6d563a
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/process/package-info.java
@@ -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.
+ */
+
+@org.osgi.annotation.versioning.Version("1.0.0")
+package org.apache.sling.feature.process;
+
+
diff --git a/src/test/java/org/apache/sling/feature/ArtifactIdTest.java b/src/test/java/org/apache/sling/feature/ArtifactIdTest.java
new file mode 100644
index 0000000..958e117
--- /dev/null
+++ b/src/test/java/org/apache/sling/feature/ArtifactIdTest.java
@@ -0,0 +1,144 @@
+/*
+ * 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.sling.feature;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+import org.osgi.framework.Version;
+
+public class ArtifactIdTest {
+
+ private static final String G = "g";
+ private static final String A = "a";
+
+ @Test
+ public void testSameVersion() {
+ final String v1 = "1";
+ final String v10 = "1.0";
+ final String v100 = "1.0.0";
+
+ final Version ve1 = new ArtifactId(G, A, v1, null, null).getOSGiVersion();
+ final Version ve10 = new ArtifactId(G, A, v10, null, null).getOSGiVersion();
+ final Version ve100 = new ArtifactId(G, A, v100, null, null).getOSGiVersion();
+
+ assertEquals(0, ve1.compareTo(ve10));
+ assertEquals(0, ve10.compareTo(ve100));
+ assertEquals(0, ve1.compareTo(ve100));
+ assertEquals(0, ve10.compareTo(ve1));
+ assertEquals(0, ve100.compareTo(ve10));
+ assertEquals(0, ve100.compareTo(ve1));
+ }
+
+ @Test
+ public void testVersions() {
+ final String v1 = "1";
+ final String v20 = "2.0";
+ final String v150 = "1.5.0";
+
+ final Version ve1 = new ArtifactId(G, A, v1, null, null).getOSGiVersion();
+ final Version ve20 = new ArtifactId(G, A, v20, null, null).getOSGiVersion();
+ final Version ve150 = new ArtifactId(G, A, v150, null, null).getOSGiVersion();
+
+ assertTrue(ve1.compareTo(ve20) < 0);
+ assertTrue(ve20.compareTo(ve150) > 0);
+ assertTrue(ve1.compareTo(ve150) < 0);
+ assertTrue(ve20.compareTo(ve1) > 0);
+ assertTrue(ve150.compareTo(ve20) < 0);
+ assertTrue(ve150.compareTo(ve1) > 0);
+ }
+
+ @Test
+ public void testSnapshotQualifier() {
+ final Version v1 = new ArtifactId(G, A, "1", null, null).getOSGiVersion();
+ final Version v1snapshot = new ArtifactId(G, A, "1-SNAPSHOT", null, null).getOSGiVersion();
+ final Version v1a = new ArtifactId(G, A, "1-A", null, null).getOSGiVersion();
+
+ // snapshot in OSGi is higher than the corresponding version
+ assertTrue(v1.compareTo(v1snapshot) < 0);
+ assertTrue(v1snapshot.compareTo(v1) > 0);
+
+ // qualifier is higher than the version
+ assertTrue(v1a.compareTo(v1) > 0);
+ assertTrue(v1.compareTo(v1a) < 0);
+
+ // qualifier in OSGi is lower than snapshot (A is lower than SNAPSHOT)
+ assertTrue(v1a.compareTo(v1snapshot) < 0);
+ assertTrue(v1snapshot.compareTo(v1a) > 0);
+ }
+
+ @Test
+ public void testQualifiers() {
+ final Version va = new ArtifactId(G, A, "1-A", null, null).getOSGiVersion();
+ final Version vb = new ArtifactId(G, A, "1-B", null, null).getOSGiVersion();
+ assertTrue(va.compareTo(vb) < 0);
+ assertTrue(vb.compareTo(va) > 0);
+
+ final Version vc = new ArtifactId(G, A, "0.11.14.1.0010", null, null).getOSGiVersion();
+ assertEquals(0, vc.getMajor());
+ assertEquals(11, vc.getMinor());
+ assertEquals(14, vc.getMicro());
+ assertEquals("1_0010", vc.getQualifier());
+ }
+
+ @Test
+ public void testOSGiVersion() {
+ final Version v = new ArtifactId(G, A, "1.5.2.SNAPSHOT", null, null).getOSGiVersion();
+ assertEquals(1, v.getMajor());
+ assertEquals(5, v.getMinor());
+ assertEquals(2, v.getMicro());
+ assertEquals("SNAPSHOT", v.getQualifier());
+ }
+
+ @Test
+ public void testStrangeVersions() {
+ final Version v = new ArtifactId(G, A, "3.0.3-20170712.062549-4", null, null).getOSGiVersion();
+ assertEquals(3, v.getMajor());
+ assertEquals(0, v.getMinor());
+ assertEquals(3, v.getMicro());
+ assertEquals("20170712_062549-4", v.getQualifier());
+ }
+
+ @Test public void testCoordinatesGAV() {
+ final ArtifactId id = ArtifactId.fromMvnId("group.a:artifact.b:1.0");
+ assertEquals("group.a", id.getGroupId());
+ assertEquals("artifact.b", id.getArtifactId());
+ assertEquals("1.0", id.getVersion());
+ assertEquals("jar", id.getType());
+ assertNull(id.getClassifier());
+ }
+
+ @Test public void testCoordinatesGAVP() {
+ final ArtifactId id = ArtifactId.fromMvnId("group.a:artifact.b:zip:1.0");
+ assertEquals("group.a", id.getGroupId());
+ assertEquals("artifact.b", id.getArtifactId());
+ assertEquals("1.0", id.getVersion());
+ assertEquals("zip", id.getType());
+ assertNull(id.getClassifier());
+ }
+
+ @Test public void testCoordinatesGAVPC() {
+ final ArtifactId id = ArtifactId.fromMvnId("group.a:artifact.b:zip:foo:1.0");
+ assertEquals("group.a", id.getGroupId());
+ assertEquals("artifact.b", id.getArtifactId());
+ assertEquals("1.0", id.getVersion());
+ assertEquals("zip", id.getType());
+ assertEquals("foo", id.getClassifier());
+ }
+}
diff --git a/src/test/java/org/apache/sling/feature/BundlesTest.java b/src/test/java/org/apache/sling/feature/BundlesTest.java
new file mode 100644
index 0000000..3346f19
--- /dev/null
+++ b/src/test/java/org/apache/sling/feature/BundlesTest.java
@@ -0,0 +1,45 @@
+/*
+ * 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.sling.feature;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.Map;
+
+import org.junit.Test;
+
+public class BundlesTest {
+
+ @Test
+ public void testIterator() {
+ final Bundles bundles = new Bundles();
+ bundles.add(1, new Artifact(ArtifactId.parse("1/a/1")));
+ bundles.add(5, new Artifact(ArtifactId.parse("5/a/5")));
+ bundles.add(5, new Artifact(ArtifactId.parse("5/b/6")));
+ bundles.add(2, new Artifact(ArtifactId.parse("2/b/2")));
+ bundles.add(2, new Artifact(ArtifactId.parse("2/a/3")));
+ bundles.add(4, new Artifact(ArtifactId.parse("4/x/4")));
+
+ int index = 1;
+ for(final Map.Entry<Integer, Artifact> entry : bundles) {
+ assertEquals(entry.getKey().toString(), entry.getValue().getId().getGroupId());
+ assertEquals(index, entry.getValue().getId().getOSGiVersion().getMajor());
+ index++;
+ }
+ assertEquals(7, index);
+ }
+}
diff --git a/src/test/java/org/apache/sling/feature/process/BuilderUtilTest.java b/src/test/java/org/apache/sling/feature/process/BuilderUtilTest.java
new file mode 100644
index 0000000..a33ef95
--- /dev/null
+++ b/src/test/java/org/apache/sling/feature/process/BuilderUtilTest.java
@@ -0,0 +1,133 @@
+/*
+ * 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.sling.feature.process;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.sling.feature.Artifact;
+import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.Bundles;
+import org.apache.sling.feature.process.BuilderUtil.ArtifactMerge;
+import org.junit.Test;
+
+public class BuilderUtilTest {
+
+ private List<Map.Entry<Integer, Artifact>> getBundles(final Bundles f) {
+ final List<Map.Entry<Integer, Artifact>> result = new ArrayList<>();
+ for(final Map.Entry<Integer, Artifact> entry : f) {
+ result.add(entry);
+ }
+ return result;
+ }
+
+ private void assertContains(final List<Map.Entry<Integer, Artifact>> bundles,
+ final int level, final ArtifactId id) {
+ for(final Map.Entry<Integer, Artifact> entry : bundles) {
+ if ( entry.getKey().intValue() == level
+ && entry.getValue().getId().equals(id) ) {
+ return;
+ }
+ }
+ fail(id.toMvnId());
+ }
+
+ @Test public void testMergeBundlesWithAlgHighest() {
+ final Bundles target = new Bundles();
+
+ target.add(1, new Artifact(ArtifactId.parse("g/a/1.0")));
+ target.add(2, new Artifact(ArtifactId.parse("g/b/2.0")));
+ target.add(3, new Artifact(ArtifactId.parse("g/c/2.5")));
+
+ final Bundles source = new Bundles();
+ source.add(1, new Artifact(ArtifactId.parse("g/a/1.1")));
+ source.add(2, new Artifact(ArtifactId.parse("g/b/1.9")));
+ source.add(3, new Artifact(ArtifactId.parse("g/c/2.5")));
+
+ BuilderUtil.mergeBundles(target, source, ArtifactMerge.HIGHEST);
+
+ final List<Map.Entry<Integer, Artifact>> result = getBundles(target);
+ assertEquals(3, result.size());
+ assertContains(result, 1, ArtifactId.parse("g/a/1.1"));
+ assertContains(result, 2, ArtifactId.parse("g/b/2.0"));
+ assertContains(result, 3, ArtifactId.parse("g/c/2.5"));
+ }
+
+ @Test public void testMergeBundlesWithAlgLatest() {
+ final Bundles target = new Bundles();
+
+ target.add(1, new Artifact(ArtifactId.parse("g/a/1.0")));
+ target.add(2, new Artifact(ArtifactId.parse("g/b/2.0")));
+ target.add(3, new Artifact(ArtifactId.parse("g/c/2.5")));
+
+ final Bundles source = new Bundles();
+ source.add(1, new Artifact(ArtifactId.parse("g/a/1.1")));
+ source.add(2, new Artifact(ArtifactId.parse("g/b/1.9")));
+ source.add(3, new Artifact(ArtifactId.parse("g/c/2.5")));
+
+ BuilderUtil.mergeBundles(target, source, ArtifactMerge.LATEST);
+
+ final List<Map.Entry<Integer, Artifact>> result = getBundles(target);
+ assertEquals(3, result.size());
+ assertContains(result, 1, ArtifactId.parse("g/a/1.1"));
+ assertContains(result, 2, ArtifactId.parse("g/b/1.9"));
+ assertContains(result, 3, ArtifactId.parse("g/c/2.5"));
+ }
+
+ @Test public void testMergeBundlesDifferentStartlevel() {
+ final Bundles target = new Bundles();
+
+ target.add(1, new Artifact(ArtifactId.parse("g/a/1.0")));
+
+ final Bundles source = new Bundles();
+ source.add(2, new Artifact(ArtifactId.parse("g/a/1.1")));
+
+ BuilderUtil.mergeBundles(target, source, ArtifactMerge.LATEST);
+
+ final List<Map.Entry<Integer, Artifact>> result = getBundles(target);
+ assertEquals(1, result.size());
+ assertContains(result, 2, ArtifactId.parse("g/a/1.1"));
+ }
+
+ @Test public void testMergeBundles() {
+ final Bundles target = new Bundles();
+
+ target.add(1, new Artifact(ArtifactId.parse("g/a/1.0")));
+ target.add(2, new Artifact(ArtifactId.parse("g/b/2.0")));
+ target.add(3, new Artifact(ArtifactId.parse("g/c/2.5")));
+
+ final Bundles source = new Bundles();
+ source.add(1, new Artifact(ArtifactId.parse("g/d/1.1")));
+ source.add(2, new Artifact(ArtifactId.parse("g/e/1.9")));
+ source.add(3, new Artifact(ArtifactId.parse("g/f/2.5")));
+
+ BuilderUtil.mergeBundles(target, source, ArtifactMerge.LATEST);
+
+ final List<Map.Entry<Integer, Artifact>> result = getBundles(target);
+ assertEquals(6, result.size());
+ assertContains(result, 1, ArtifactId.parse("g/a/1.0"));
+ assertContains(result, 2, ArtifactId.parse("g/b/2.0"));
+ assertContains(result, 3, ArtifactId.parse("g/c/2.5"));
+ assertContains(result, 1, ArtifactId.parse("g/d/1.1"));
+ assertContains(result, 2, ArtifactId.parse("g/e/1.9"));
+ assertContains(result, 3, ArtifactId.parse("g/f/2.5"));
+ }
+}
diff --git a/src/test/java/org/apache/sling/feature/process/FeatureBuilderTest.java b/src/test/java/org/apache/sling/feature/process/FeatureBuilderTest.java
new file mode 100644
index 0000000..5ef1a62
--- /dev/null
+++ b/src/test/java/org/apache/sling/feature/process/FeatureBuilderTest.java
@@ -0,0 +1,277 @@
+/*
+ * 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.sling.feature.process;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.sling.feature.Artifact;
+import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.Capability;
+import org.apache.sling.feature.Configuration;
+import org.apache.sling.feature.Extension;
+import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.Include;
+import org.apache.sling.feature.Requirement;
+import org.junit.Test;
+
+public class FeatureBuilderTest {
+
+ private static final Map<String, Feature> FEATURES = new HashMap<>();
+
+ static {
+ final Feature f1 = new Feature(ArtifactId.parse("g/a/1"));
+
+ f1.getFrameworkProperties().put("foo", "2");
+ f1.getFrameworkProperties().put("bar", "X");
+
+ f1.getBundles().add(3, new Artifact(ArtifactId.parse("org.apache.sling/foo-bar/4.5.6")));
+ f1.getBundles().add(5, new Artifact(ArtifactId.parse("group/testnewversion_low/2")));
+ f1.getBundles().add(5, new Artifact(ArtifactId.parse("group/testnewversion_high/2")));
+ f1.getBundles().add(5, new Artifact(ArtifactId.parse("group/testnewstartlevel/1")));
+ f1.getBundles().add(5, new Artifact(ArtifactId.parse("group/testnewstartlevelandversion/1")));
+
+ final Configuration c1 = new Configuration("org.apache.sling.foo");
+ c1.getProperties().put("prop", "value");
+ f1.getConfigurations().add(c1);
+
+ FEATURES.put(f1.getId().toMvnId(), f1);
+ }
+
+ private final FeatureProvider provider = new FeatureProvider() {
+
+ @Override
+ public Feature provide(final ArtifactId id) {
+ return FEATURES.get(id.getGroupId() + ":" + id.getArtifactId() + ":" + id.getVersion());
+ }
+ };
+
+ private List<Map.Entry<Integer, Artifact>> getBundles(final Feature f) {
+ final List<Map.Entry<Integer, Artifact>> result = new ArrayList<>();
+ for(final Map.Entry<Integer, Artifact> entry : f.getBundles()) {
+ result.add(entry);
+ }
+ return result;
+ }
+
+ private void equals(final Feature expected, final Feature actuals) {
+ assertFalse(expected.isAssembled());
+ assertTrue(actuals.isAssembled());
+
+ assertEquals(expected.getId(), actuals.getId());
+ assertEquals(expected.getTitle(), actuals.getTitle());
+ assertEquals(expected.getDescription(), actuals.getDescription());
+ assertEquals(expected.getVendor(), actuals.getVendor());
+ assertEquals(expected.getLicense(), actuals.getLicense());
+ assertEquals(expected.getUpgradeOf(), actuals.getUpgradeOf());
+ assertEquals(expected.getUpgrades(), actuals.getUpgrades());
+
+ // bundles
+ final List<Map.Entry<Integer, Artifact>> expectedBundles = getBundles(expected);
+ final List<Map.Entry<Integer, Artifact>> actualsBundles = getBundles(actuals);
+ assertEquals(expectedBundles.size(), actualsBundles.size());
+ for(final Map.Entry<Integer, Artifact> entry : expectedBundles) {
+ boolean found = false;
+ for(final Map.Entry<Integer, Artifact> inner : actualsBundles) {
+ if ( inner.getValue().getId().equals(entry.getValue().getId()) ) {
+ found = true;
+ assertEquals("Startlevel of bundle " + entry.getValue(), entry.getKey(), inner.getKey());
+ assertEquals("Metadata of bundle " + entry.getValue(), entry.getValue().getMetadata(), inner.getValue().getMetadata());
+ break;
+ }
+ }
+ assertTrue("Bundle " + entry.getValue() + " in level " + entry.getKey(), found);
+ }
+
+ // configurations
+ assertEquals(expected.getConfigurations().size(), actuals.getConfigurations().size());
+ for(final Configuration cfg : expected.getConfigurations()) {
+ final Configuration found = (cfg.isFactoryConfiguration() ? actuals.getConfigurations().getFactoryConfiguration(cfg.getFactoryPid(), cfg.getName())
+ : actuals.getConfigurations().getConfiguration(cfg.getPid()));
+ assertNotNull("Configuration " + cfg, found);
+ assertEquals("Configuration " + cfg, cfg.getProperties(), found.getProperties());
+ }
+
+ // frameworkProperties
+ assertEquals(expected.getFrameworkProperties(), actuals.getFrameworkProperties());
+
+ // requirements
+ assertEquals(expected.getRequirements().size(), actuals.getRequirements().size());
+ for(final Requirement r : expected.getRequirements()) {
+ boolean found = false;
+ for(final Requirement i : actuals.getRequirements()) {
+ if ( r.equals(i) ) {
+ found = true;
+ break;
+ }
+ }
+ assertTrue(found);
+ }
+
+ // capabilities
+ assertEquals(expected.getCapabilities().size(), actuals.getCapabilities().size());
+ for(final Capability r : expected.getCapabilities()) {
+ boolean found = false;
+ for(final Capability i : actuals.getCapabilities()) {
+ if ( r.equals(i) ) {
+ found = true;
+ break;
+ }
+ }
+ assertTrue(found);
+ }
+
+ // extensions
+ assertEquals(expected.getExtensions().size(), actuals.getExtensions().size());
+ for(final Extension ext : expected.getExtensions()) {
+ final Extension inner = actuals.getExtensions().getByName(ext.getName());
+ assertNotNull(inner);
+ assertEquals(ext.getType(), inner.getType());
+ switch ( ext.getType()) {
+ case JSON : assertEquals(ext.getJSON(), inner.getJSON());
+ break;
+ case TEXT : assertEquals(ext.getText(), inner.getText());
+ break;
+ case ARTIFACTS : assertEquals(ext.getArtifacts().size(), inner.getArtifacts().size());
+ for(final Artifact art : ext.getArtifacts()) {
+ boolean found = false;
+ for(final Artifact i : inner.getArtifacts()) {
+ if ( art.getId().equals(i.getId()) ) {
+ found = true;
+ assertEquals(art.getMetadata(), i.getMetadata());
+ break;
+ }
+ }
+ assertTrue(found);
+ }
+ }
+ }
+
+ // includes should always be empty
+ assertTrue(actuals.getIncludes().isEmpty());
+ }
+
+ @Test public void testNoIncludesNoUpgrade() throws Exception {
+ final Feature base = new Feature(ArtifactId.parse("org.apache.sling/test-feature/1.1"));
+
+ final Requirement r1 = new Requirement("osgi.contract");
+ r1.getDirectives().put("filter", "(&(osgi.contract=JavaServlet)(version=3.1))");
+ base.getRequirements().add(r1);
+
+ final Capability c1 = new Capability("osgi.implementation");
+ c1.getAttributes().put("osgi.implementation", "osgi.http");
+ c1.getAttributes().put("version:Version", "1.1");
+ c1.getDirectives().put("uses", "javax.servlet,javax.servlet.http,org.osgi.service.http.context,org.osgi.service.http.whiteboard");
+ base.getCapabilities().add(c1);
+ final Capability c2 = new Capability("osgi.service");
+ c2.getAttributes().put("objectClass:List<String>", "org.osgi.service.http.runtime.HttpServiceRuntime");
+ c2.getDirectives().put("uses", "org.osgi.service.http.runtime,org.osgi.service.http.runtime.dto");
+ base.getCapabilities().add(c2);
+
+ base.getFrameworkProperties().put("foo", "1");
+ base.getFrameworkProperties().put("brave", "something");
+ base.getFrameworkProperties().put("org.apache.felix.scr.directory", "launchpad/scr");
+
+ final Artifact a1 = new Artifact(ArtifactId.parse("org.apache.sling/oak-server/1.0.0"));
+ a1.getMetadata().put("hash", "4632463464363646436");
+ base.getBundles().add(1, a1);
+ base.getBundles().add(1, new Artifact(ArtifactId.parse("org.apache.sling/application-bundle/2.0.0")));
+ base.getBundles().add(1, new Artifact(ArtifactId.parse("org.apache.sling/another-bundle/2.1.0")));
+ base.getBundles().add(2, new Artifact(ArtifactId.parse("org.apache.sling/foo-xyz/1.2.3")));
+
+ final Configuration co1 = new Configuration("my.pid");
+ co1.getProperties().put("foo", 5L);
+ co1.getProperties().put("bar", "test");
+ co1.getProperties().put("number", 7);
+ base.getConfigurations().add(co1);
+
+ final Configuration co2 = new Configuration("my.factory.pid", "name");
+ co2.getProperties().put("a.value", "yeah");
+ base.getConfigurations().add(co2);
+
+ assertFalse(base.isAssembled());
+
+ final Feature assembled = FeatureBuilder.assemble(base, new BuilderContext(provider));
+
+ equals(base, assembled);
+ }
+
+ @Test public void testSingleInclude() throws Exception {
+ final Feature base = new Feature(ArtifactId.parse("org.apache.sling/test-feature/1.1"));
+ final Include i1 = new Include(ArtifactId.parse("g/a/1"));
+ base.getIncludes().add(i1);
+
+ final Requirement r1 = new Requirement("osgi.contract");
+ r1.getDirectives().put("filter", "(&(osgi.contract=JavaServlet)(version=3.1))");
+ base.getRequirements().add(r1);
+
+ final Capability c1 = new Capability("osgi.implementation");
+ c1.getAttributes().put("osgi.implementation", "osgi.http");
+ c1.getAttributes().put("version:Version", "1.1");
+ c1.getDirectives().put("uses", "javax.servlet,javax.servlet.http,org.osgi.service.http.context,org.osgi.service.http.whiteboard");
+ base.getCapabilities().add(c1);
+
+ base.getFrameworkProperties().put("foo", "1");
+ base.getFrameworkProperties().put("brave", "something");
+ base.getFrameworkProperties().put("org.apache.felix.scr.directory", "launchpad/scr");
+
+ final Artifact a1 = new Artifact(ArtifactId.parse("org.apache.sling/oak-server/1.0.0"));
+ a1.getMetadata().put("hash", "4632463464363646436");
+ base.getBundles().add(1, a1);
+ base.getBundles().add(1, new Artifact(ArtifactId.parse("org.apache.sling/application-bundle/2.0.0")));
+ base.getBundles().add(1, new Artifact(ArtifactId.parse("org.apache.sling/another-bundle/2.1.0")));
+ base.getBundles().add(2, new Artifact(ArtifactId.parse("org.apache.sling/foo-xyz/1.2.3")));
+ base.getBundles().add(5, new Artifact(ArtifactId.parse("group/testnewversion_low/1")));
+ base.getBundles().add(5, new Artifact(ArtifactId.parse("group/testnewversion_high/5")));
+ base.getBundles().add(10, new Artifact(ArtifactId.parse("group/testnewstartlevel/1")));
+ base.getBundles().add(10, new Artifact(ArtifactId.parse("group/testnewstartlevelandversion/2")));
+
+ final Configuration co1 = new Configuration("my.pid");
+ co1.getProperties().put("foo", 5L);
+ co1.getProperties().put("bar", "test");
+ co1.getProperties().put("number", 7);
+ base.getConfigurations().add(co1);
+
+ final Configuration co2 = new Configuration("my.factory.pid", "name");
+ co2.getProperties().put("a.value", "yeah");
+ base.getConfigurations().add(co2);
+
+ assertFalse(base.isAssembled());
+
+ // create the expected result
+ final Feature result = base.copy();
+ result.getIncludes().remove(0);
+ result.getFrameworkProperties().put("bar", "X");
+ result.getBundles().add(3, new Artifact(ArtifactId.parse("org.apache.sling/foo-bar/4.5.6")));
+ final Configuration co3 = new Configuration("org.apache.sling.foo");
+ co3.getProperties().put("prop", "value");
+ result.getConfigurations().add(co3);
+
+ // assemble
+ final Feature assembled = FeatureBuilder.assemble(base, new BuilderContext(provider));
+
+ // and test
+ equals(result, assembled);
+ }
+}
--
To stop receiving notification emails like this one, please contact
davidb@apache.org.
[sling-org-apache-sling-feature] 20/22: Refactor to use
Capabilities and Requirements from Felix utils project
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
davidb pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-feature.git
commit f14073db5efd3e4f8a80e4c2dc0076af3fdc6718
Author: David Bosschaert <da...@gmail.com>
AuthorDate: Sun Apr 22 21:19:43 2018 +0100
Refactor to use Capabilities and Requirements from Felix utils project
---
pom.xml | 9 +-
.../feature/AbstractCapabilityRequirement.java | 114 ---------------------
.../java/org/apache/sling/feature/Feature.java | 6 +-
.../org/apache/sling/feature/OSGiCapability.java | 59 -----------
.../org/apache/sling/feature/OSGiRequirement.java | 59 -----------
.../sling/feature/CapabilityRequirementTest.java | 68 ------------
6 files changed, 12 insertions(+), 303 deletions(-)
diff --git a/pom.xml b/pom.xml
index 46f2116..35078c5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -77,11 +77,18 @@
<version>1.0-alpha-1</version>
<scope>provided</scope>
</dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.utils</artifactId>
+ <version>1.11.0-SNAPSHOT</version>
+ <scope>provided</scope>
+ </dependency>
- <!-- Testing -->
+ <!-- Testing -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
+ <scope>test</scope>
</dependency>
<dependency>
diff --git a/src/main/java/org/apache/sling/feature/AbstractCapabilityRequirement.java b/src/main/java/org/apache/sling/feature/AbstractCapabilityRequirement.java
deleted file mode 100644
index 8a4982f..0000000
--- a/src/main/java/org/apache/sling/feature/AbstractCapabilityRequirement.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with this
- * work for additional information regarding copyright ownership. The ASF
- * licenses this file to You under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package org.apache.sling.feature;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-
-import org.osgi.resource.Resource;
-
-abstract class AbstractCapabilityRequirement {
-
- /** The namespace. Required. */
- private final String namespace;
-
- /** Optional resource. */
- private final Resource resource;
-
- /** Optional attributes. Never null. */
- private final Map<String, Object> attributes;
-
- /** Optional attributes. Never null. */
- private final Map<String, String> directives;
-
- AbstractCapabilityRequirement(final Resource res, final String ns, final Map<String, Object> attrs, final Map<String, String> dirs) {
- if ( ns == null ) {
- throw new IllegalArgumentException("Namespace must not be null.");
- }
- resource = res;
- namespace = ns;
- attributes = attrs == null ? Collections.emptyMap() : Collections.unmodifiableMap(new HashMap<>(attrs));
- directives = dirs == null ? Collections.emptyMap() : Collections.unmodifiableMap(new HashMap<>(dirs));
- }
-
- /**
- * Return the namespace.
- * @return The namespace. This is never @{code null}.
- */
- public String getNamespace() {
- return namespace;
- }
-
- /**
- * Return the attributes.
- * @return The attributes, might be empty.
- */
- public Map<String, Object> getAttributes() {
- return attributes;
- }
-
- /**
- * Return the directives.
- * @return The directives, might be empty.
- */
- public Map<String, String> getDirectives() {
- return directives;
- }
-
- /**
- * Return the resource.
- * @return The resource or @{code null}.
- */
- public Resource getResource() {
- return resource;
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + attributes.hashCode();
- result = prime * result + directives.hashCode();
- result = prime * result + namespace.hashCode();
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj)
- return true;
- if (obj == null)
- return false;
- if (getClass() != obj.getClass())
- return false;
- AbstractCapabilityRequirement other = (AbstractCapabilityRequirement) obj;
- if (!namespace.equals(other.namespace))
- return false;
- if (!attributes.equals(other.attributes))
- return false;
- if (!directives.equals(other.directives))
- return false;
- return true;
- }
-
- @Override
- public String toString() {
- return getClass().getSimpleName() + " [resource=" + resource + ", namespace=" + namespace + ", attributes=" + attributes
- + ", directives=" + directives + "]";
- }
-}
diff --git a/src/main/java/org/apache/sling/feature/Feature.java b/src/main/java/org/apache/sling/feature/Feature.java
index b1ad406..98f1e0b 100644
--- a/src/main/java/org/apache/sling/feature/Feature.java
+++ b/src/main/java/org/apache/sling/feature/Feature.java
@@ -16,6 +16,8 @@
*/
package org.apache.sling.feature;
+import org.apache.felix.utils.resource.CapabilityImpl;
+import org.apache.felix.utils.resource.RequirementImpl;
import org.osgi.resource.Capability;
import org.osgi.resource.Requirement;
@@ -312,13 +314,13 @@ public class Feature implements Comparable<Feature> {
// requirements
for(final Requirement r : this.getRequirements()) {
- final Requirement c = new OSGiRequirement(r.getNamespace(), r.getAttributes(), r.getDirectives());
+ final Requirement c = new RequirementImpl(null, r.getNamespace(), r.getDirectives(), r.getAttributes());
result.getRequirements().add(c);
}
// capabilities
for(final Capability r : this.getCapabilities()) {
- final Capability c = new OSGiCapability(r.getNamespace(), r.getAttributes(), r.getDirectives());
+ final Capability c = new CapabilityImpl(null, r.getNamespace(), r.getDirectives(), r.getAttributes());
result.getCapabilities().add(c);
}
diff --git a/src/main/java/org/apache/sling/feature/OSGiCapability.java b/src/main/java/org/apache/sling/feature/OSGiCapability.java
deleted file mode 100644
index 5c2000d..0000000
--- a/src/main/java/org/apache/sling/feature/OSGiCapability.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with this
- * work for additional information regarding copyright ownership. The ASF
- * licenses this file to You under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package org.apache.sling.feature;
-
-import java.util.Map;
-
-import org.osgi.resource.Capability;
-import org.osgi.resource.Resource;
-
-/**
- * Implementation of the OSGi Capability interface.
- */
-public class OSGiCapability extends AbstractCapabilityRequirement implements Capability {
- /**
- * Create a capability that is not associated with a resource.
- * @param res The resource associated with the capability. May be null.
- * @param ns The namespace of the capability.
- * @param attrs The attributes of the capability.
- * @param dirs The directives of the capability.
- */
- public OSGiCapability(String ns, Map<String, Object> attrs, Map<String, String> dirs) {
- this(null, ns, attrs, dirs);
- }
-
- /**
- * Create a capability.
- * @param res The resource associated with the capability. May be null.
- * @param ns The namespace of the capability.
- * @param attrs The attributes of the capability.
- * @param dirs The directives of the capability.
- */
- public OSGiCapability(Resource res, String ns, Map<String, Object> attrs, Map<String, String> dirs) {
- super(res, ns, attrs, dirs);
- }
-
- /**
- * Create a capability based on an existing capability, providing the resource.
- * The namespace, attributes and directives are copied from the provided capability.
- * @param resource The resource to be associated with the capability
- * @param capability The capability to base the new requirement on.
- */
- public OSGiCapability(Resource resource, Capability capability) {
- this(resource, capability.getNamespace(), capability.getAttributes(), capability.getDirectives());
- }
-}
diff --git a/src/main/java/org/apache/sling/feature/OSGiRequirement.java b/src/main/java/org/apache/sling/feature/OSGiRequirement.java
deleted file mode 100644
index 2727379..0000000
--- a/src/main/java/org/apache/sling/feature/OSGiRequirement.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with this
- * work for additional information regarding copyright ownership. The ASF
- * licenses this file to You under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package org.apache.sling.feature;
-
-import java.util.Map;
-
-import org.osgi.resource.Requirement;
-import org.osgi.resource.Resource;
-
-/**
- * Implementation of the OSGi Requirement interface.
- */
-public class OSGiRequirement extends AbstractCapabilityRequirement implements Requirement {
- /**
- * Create a requirement that is not associated with a resource.
- * @param res The resource associated with the requirement.
- * @param ns The namespace of the requirement.
- * @param attrs The attributes of the requirement.
- * @param dirs The directives of the requirement.
- */
- public OSGiRequirement(String ns, Map<String, Object> attrs, Map<String, String> dirs) {
- this(null, ns, attrs, dirs);
- }
-
- /**
- * Create a requirement.
- * @param res The resource associated with the requirement.
- * @param ns The namespace of the requirement.
- * @param attrs The attributes of the requirement.
- * @param dirs The directives of the requirement.
- */
- public OSGiRequirement(Resource res, String ns, Map<String, Object> attrs, Map<String, String> dirs) {
- super(res, ns, attrs, dirs);
- }
-
- /**
- * Create a requirement based on an existing requirement, providing the resource.
- * The namespace, attributes and directives are copied from the provided requirement.
- * @param resource The resource to be associated with the requirement
- * @param requirement The requirement to base the new requirement on.
- */
- public OSGiRequirement(Resource resource, Requirement requirement) {
- this(resource, requirement.getNamespace(), requirement.getAttributes(), requirement.getDirectives());
- }
-}
diff --git a/src/test/java/org/apache/sling/feature/CapabilityRequirementTest.java b/src/test/java/org/apache/sling/feature/CapabilityRequirementTest.java
deleted file mode 100644
index e834bad..0000000
--- a/src/test/java/org/apache/sling/feature/CapabilityRequirementTest.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sling.feature;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import org.junit.Test;
-import org.osgi.resource.Capability;
-import org.osgi.resource.Requirement;
-import org.osgi.resource.Resource;
-
-public class CapabilityRequirementTest {
- @Test
- public void testCapability() {
- Map<String, Object> attrs = new HashMap<>();
- attrs.put("org.foo", "1234");
- attrs.put("bar", 456);
- Map<String, String> dirs = new HashMap<>();
- dirs.put("my_dir", "my_value");
- Capability c = new OSGiCapability("org.foo", attrs, dirs);
- assertEquals("org.foo", c.getNamespace());
- assertEquals(attrs, c.getAttributes());
- assertEquals(dirs, c.getDirectives());
- assertNull(c.getResource());
- }
-
- @Test
- public void testRequirement() {
- Resource tr = new TestResource();
- Requirement r = new OSGiRequirement(tr, "testing",
- Collections.emptyMap(), Collections.emptyMap());
- assertEquals(tr, r.getResource());
- assertEquals(0, r.getAttributes().size());
- assertEquals(0, r.getDirectives().size());
- }
-
- private static class TestResource implements Resource {
- @Override
- public List<Capability> getCapabilities(String namespace) {
- return Collections.emptyList();
- }
-
- @Override
- public List<Requirement> getRequirements(String namespace) {
- return Collections.emptyList();
- }
- }
-}
--
To stop receiving notification emails like this one, please contact
davidb@apache.org.
[sling-org-apache-sling-feature] 14/22: Support for special feature
names when converting to provisioning model.
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
davidb pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-feature.git
commit f3dffc7ae91a19b2b785c78694eb6b018ce44f71
Author: David Bosschaert <da...@gmail.com>
AuthorDate: Mon Mar 19 15:09:52 2018 +0000
Support for special feature names when converting to provisioning model.
The special feature names must be specified in the variables section
under the variable name 'provisioning.model.name'.
This commit also contains the start of a document-based set of tests for
testing between conversions of the feature model to the provisioning
model and back.
---
src/main/java/org/apache/sling/feature/Feature.java | 19 ++++++++++++++++---
1 file changed, 16 insertions(+), 3 deletions(-)
diff --git a/src/main/java/org/apache/sling/feature/Feature.java b/src/main/java/org/apache/sling/feature/Feature.java
index 874790d..b1ad406 100644
--- a/src/main/java/org/apache/sling/feature/Feature.java
+++ b/src/main/java/org/apache/sling/feature/Feature.java
@@ -16,13 +16,13 @@
*/
package org.apache.sling.feature;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Requirement;
+
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
-import org.osgi.resource.Capability;
-import org.osgi.resource.Requirement;
-
/**
* A feature consists of
* <ul>
@@ -53,6 +53,8 @@ public class Feature implements Comparable<Feature> {
private final Extensions extensions = new Extensions();
+ private final KeyValueMap variables = new KeyValueMap();
+
/** The optional location. */
private volatile String location;
@@ -204,6 +206,14 @@ public class Feature implements Comparable<Feature> {
}
/**
+ * Obtain the variables of the feature
+ * @return The variables
+ */
+ public KeyValueMap getVariables() {
+ return this.variables;
+ }
+
+ /**
* Get the vendor
* @return The vendor or {@code null}
*/
@@ -275,6 +285,9 @@ public class Feature implements Comparable<Feature> {
result.setLicense(this.getLicense());
result.setAssembled(this.isAssembled());
+ // variables
+ result.getVariables().putAll(this.getVariables());
+
// bundles
for(final Artifact b : this.getBundles()) {
final Artifact c = new Artifact(b.getId());
--
To stop receiving notification emails like this one, please contact
davidb@apache.org.
[sling-org-apache-sling-feature] 09/22: Clarify update handling
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
davidb pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-feature.git
commit 5582860f8e9297b329c427423056e125f5a4d80e
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Fri Feb 23 08:19:02 2018 +0100
Clarify update handling
---
.../java/org/apache/sling/feature/Feature.java | 31 ------
.../sling/feature/process/ApplicationBuilder.java | 58 +++++------
.../sling/feature/process/FeatureBuilder.java | 111 +--------------------
.../sling/feature/process/FeatureBuilderTest.java | 2 -
4 files changed, 24 insertions(+), 178 deletions(-)
diff --git a/src/main/java/org/apache/sling/feature/Feature.java b/src/main/java/org/apache/sling/feature/Feature.java
index f0f2cc5..64c2488 100644
--- a/src/main/java/org/apache/sling/feature/Feature.java
+++ b/src/main/java/org/apache/sling/feature/Feature.java
@@ -65,15 +65,9 @@ public class Feature implements Comparable<Feature> {
/** The optional license. */
private volatile String license;
- /** Is this an upgrade of another feature? */
- private volatile ArtifactId upgradeOf;
-
/** Flag indicating whether this is an assembled feature */
private volatile boolean assembled = false;
- /** Contained upgrades (this is usually only set for assembled features*/
- private final List<ArtifactId> upgrades = new ArrayList<>();
-
/**
* Construct a new feature.
* @param id The id of the feature.
@@ -239,31 +233,6 @@ public class Feature implements Comparable<Feature> {
}
/**
- * Set the upgrade of information
- * @param id The artifact id
- */
- public void setUpgradeOf(final ArtifactId id) {
- this.upgradeOf = id;
- }
-
- /**
- * Get the artifact id of the upgrade of information
- * @return The artifact id or {@code null}
- */
- public ArtifactId getUpgradeOf() {
- return this.upgradeOf;
- }
-
- /**
- * Get the list of upgrades applied to this feature
- * The returned object is modifiable.
- * @return The list of upgrades
- */
- public List<ArtifactId> getUpgrades() {
- return this.upgrades;
- }
-
- /**
* Check whether the feature is already assembled
* @return {@code true} if it is assembled, {@code false} if it needs to be assembled
*/
diff --git a/src/main/java/org/apache/sling/feature/process/ApplicationBuilder.java b/src/main/java/org/apache/sling/feature/process/ApplicationBuilder.java
index cc0f235..1a555e1 100644
--- a/src/main/java/org/apache/sling/feature/process/ApplicationBuilder.java
+++ b/src/main/java/org/apache/sling/feature/process/ApplicationBuilder.java
@@ -18,9 +18,7 @@ package org.apache.sling.feature.process;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
import org.apache.sling.feature.Application;
import org.apache.sling.feature.ArtifactId;
@@ -67,9 +65,8 @@ public class ApplicationBuilder {
/**
* Assemble an application based on the provided features.
*
- * Upgrade features are only applied if the provided feature list
- * contains the feature to be upgraded. Otherwise the upgrade feature
- * is ignored.
+ * If the same feature is included more than once only the feature with
+ * the highest version is used. The others are ignored.
*
* @param app The optional application to use as a base.
* @param context The builder context
@@ -90,39 +87,35 @@ public class ApplicationBuilder {
app = new Application();
}
- // detect upgrades and created sorted feature list
- final Map<Feature, List<Feature>> upgrades = new HashMap<>();
+ // Created sorted feature list
+ // Remove duplicate features by selecting the one with the highest version
final List<Feature> sortedFeatureList = new ArrayList<>();
for(final Feature f : features) {
- if ( f.getUpgradeOf() != null ) {
- for(final Feature i : features) {
- if ( i.getId().equals(f.getUpgradeOf()) ) {
- List<Feature> u = upgrades.get(i);
- if ( u == null ) {
- u = new ArrayList<>();
- upgrades.put(i, u);
- }
- u.add(f);
- app.getFeatureIds().add(f.getId());
- break;
- }
+ Feature found = null;
+ for(final Feature s : sortedFeatureList) {
+ if ( s.getId().isSame(f.getId()) ) {
+ found = s;
+ break;
+ }
+ }
+ boolean add = true;
+ // feature with different version found
+ if ( found != null ) {
+ if ( f.getId().getOSGiVersion().compareTo(found.getId().getOSGiVersion()) <= 0 ) {
+ // higher version already included
+ add = false;
+ } else {
+ // remove lower version, higher version will be added
+ app.getFeatureIds().remove(found.getId());
+ sortedFeatureList.remove(found);
}
- } else {
+ }
+ if ( add ) {
app.getFeatureIds().add(f.getId());
sortedFeatureList.add(f);
}
}
- // process upgrades first
- for(final Map.Entry<Feature, List<Feature>> entry : upgrades.entrySet()) {
- final Feature assembled = FeatureBuilder.assemble(entry.getKey(),
- entry.getValue(),
- context);
- // update feature to assembled feature
- sortedFeatureList.remove(entry.getKey());
- sortedFeatureList.add(assembled);
- }
-
// sort
Collections.sort(sortedFeatureList);
@@ -132,11 +125,6 @@ public class ApplicationBuilder {
@Override
public Feature provide(final ArtifactId id) {
- for(final Feature f : upgrades.keySet()) {
- if ( f.getId().equals(id) ) {
- return f;
- }
- }
for(final Feature f : features) {
if ( f.getId().equals(id) ) {
return f;
diff --git a/src/main/java/org/apache/sling/feature/process/FeatureBuilder.java b/src/main/java/org/apache/sling/feature/process/FeatureBuilder.java
index 7208f00..6d57dc6 100644
--- a/src/main/java/org/apache/sling/feature/process/FeatureBuilder.java
+++ b/src/main/java/org/apache/sling/feature/process/FeatureBuilder.java
@@ -17,7 +17,6 @@
package org.apache.sling.feature.process;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -48,91 +47,6 @@ public class FeatureBuilder {
return internalAssemble(new ArrayList<>(), feature, context);
}
- /**
- * Assemble the final feature and apply upgrades
- *
- * If the list of upgrades contains upgrade features not intended for the
- * provided feature, this is not considered an error situation. But the
- * provided upgrade is ignored.
- *
- * @param feature The feature to start
- * @param upgrades The list of upgrades. If this is {@code null} or empty, this method
- * behaves like {@link #assemble(Feature, FeatureProvider)}.
- * @param context The builder context
- * @return The assembled feature.
- * @throws IllegalArgumentException If feature or context is {@code null}
- * @throws IllegalStateException If an included feature can't be provided
- */
- public static Feature assemble(final Feature feature,
- final List<Feature> upgrades,
- final BuilderContext context) {
- if ( feature == null || context == null ) {
- throw new IllegalArgumentException("Feature and/or context must not be null");
- }
-
- // check upgrades
- List<Feature> useUpdates = null;
- if ( upgrades != null && !upgrades.isEmpty() ) {
- useUpdates = new ArrayList<>();
- for(final Feature uf : upgrades) {
- if ( !feature.getId().equals(uf.getUpgradeOf()) ) {
- continue;
- }
- boolean found = false;
- for(final Feature i : useUpdates) {
- if ( i.getId().isSame(uf.getId()) ) {
- if ( uf.getId().getOSGiVersion().compareTo(i.getId().getOSGiVersion()) > 0 ) {
- useUpdates.remove(i);
- } else {
- found = true;
- }
- break;
- }
- }
- if ( !found ) {
- // we add a copy as we manipulate the upgrade below
- useUpdates.add(uf.copy());
- }
- }
- Collections.sort(useUpdates);
- if ( useUpdates.isEmpty() ) {
- useUpdates = null;
- }
- }
-
- // assemble feature without upgrades
- final Feature assembledFeature = internalAssemble(new ArrayList<>(), feature, context);
-
- // handle upgrades
- if ( useUpdates != null ) {
- for(final Feature uf : useUpdates) {
- Include found = null;
- for(final Include inc : uf.getIncludes() ) {
- if ( inc.getId().equals(assembledFeature.getId()) ) {
- found = inc;
- break;
- }
- }
- if ( found != null ) {
- uf.getIncludes().remove(found);
-
- // process include instructions
- include(assembledFeature, found);
- }
-
- // now assemble upgrade, but without considering the base
- uf.setUpgradeOf(null);
- assembledFeature.getUpgrades().add(uf.getId());
- final Feature auf = assemble(uf, context);
-
- // merge
- merge(assembledFeature, auf, context);
- }
- }
-
- return assembledFeature;
- }
-
private static Feature internalAssemble(final List<String> processedFeatures,
final Feature feature,
final BuilderContext context) {
@@ -145,30 +59,7 @@ public class FeatureBuilder {
processedFeatures.add(feature.getId().toMvnId());
// we copy the feature as we set the assembled flag on the result
- final Feature result;
-
- if ( feature.getUpgradeOf() != null ) {
- Include found = null;
- for(final Include inc : feature.getIncludes()) {
- if ( inc.getId().equals(feature.getUpgradeOf()) ) {
- found = inc;
- break;
- }
- }
-
- result = feature.copy(feature.getUpgradeOf());
-
- // add base as the first include
- if ( found == null ) {
- result.getIncludes().add(0, new Include(feature.getUpgradeOf()));
- } else {
- result.getIncludes().remove(found);
- result.getIncludes().add(0, found);
- }
- result.getUpgrades().add(feature.getId());
- } else {
- result = feature.copy();
- }
+ final Feature result = feature.copy();
if ( !result.getIncludes().isEmpty() ) {
diff --git a/src/test/java/org/apache/sling/feature/process/FeatureBuilderTest.java b/src/test/java/org/apache/sling/feature/process/FeatureBuilderTest.java
index 4fc31b3..f990aec 100644
--- a/src/test/java/org/apache/sling/feature/process/FeatureBuilderTest.java
+++ b/src/test/java/org/apache/sling/feature/process/FeatureBuilderTest.java
@@ -104,8 +104,6 @@ public class FeatureBuilderTest {
assertEquals(expected.getDescription(), actuals.getDescription());
assertEquals(expected.getVendor(), actuals.getVendor());
assertEquals(expected.getLicense(), actuals.getLicense());
- assertEquals(expected.getUpgradeOf(), actuals.getUpgradeOf());
- assertEquals(expected.getUpgrades(), actuals.getUpgrades());
// bundles
final List<Map.Entry<Integer, Artifact>> expectedBundles = getBundles(expected);
--
To stop receiving notification emails like this one, please contact
davidb@apache.org.
[sling-org-apache-sling-feature] 15/22: Support variables in
converting from features to provisioning model
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
davidb pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-feature.git
commit f4e132a01e2853615397b885a354f270e4a2a1fb
Author: David Bosschaert <da...@gmail.com>
AuthorDate: Tue Mar 20 10:46:52 2018 +0000
Support variables in converting from features to provisioning model
---
src/main/java/org/apache/sling/feature/Application.java | 11 +++++++++++
src/main/java/org/apache/sling/feature/ArtifactId.java | 6 +++---
2 files changed, 14 insertions(+), 3 deletions(-)
diff --git a/src/main/java/org/apache/sling/feature/Application.java b/src/main/java/org/apache/sling/feature/Application.java
index 7ca58e4..53c3c75 100644
--- a/src/main/java/org/apache/sling/feature/Application.java
+++ b/src/main/java/org/apache/sling/feature/Application.java
@@ -31,6 +31,8 @@ import java.util.List;
* </ul>
*/
public class Application {
+ /** The variables of this application. */
+ private final KeyValueMap variables = new KeyValueMap();
/** Container for bundles. */
private final Bundles bundles = new Bundles();
@@ -109,6 +111,15 @@ public class Application {
this.framework = framework;
}
+ /**
+ * Get the variables
+ * The map is modifiable
+ * @return The map of variables
+ */
+ public KeyValueMap getVariables() {
+ return this.variables;
+ }
+
@Override
public String toString() {
return "Application [features=" + this.features
diff --git a/src/main/java/org/apache/sling/feature/ArtifactId.java b/src/main/java/org/apache/sling/feature/ArtifactId.java
index 9eca7a9..60860de 100644
--- a/src/main/java/org/apache/sling/feature/ArtifactId.java
+++ b/src/main/java/org/apache/sling/feature/ArtifactId.java
@@ -16,12 +16,12 @@
*/
package org.apache.sling.feature;
+import org.osgi.framework.Version;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
-import org.osgi.framework.Version;
-
/**
* An artifact identifier.
*
@@ -67,7 +67,7 @@ public class ArtifactId implements Comparable<ArtifactId> {
this.groupId = groupId;
this.artifactId = artifactId;
this.version = version;
- this.getOSGiVersion();
+
if ( "bundle".equals(type) || type == null || type.isEmpty() ) {
this.type = "jar";
} else {
--
To stop receiving notification emails like this one, please contact
davidb@apache.org.
[sling-org-apache-sling-feature] 10/22: SLING-7512 Order features
based on their dependencies.
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
davidb pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-feature.git
commit 1587e849822ffb784f459a0a8439e6ccd2dd932a
Author: David Bosschaert <bo...@adobe.com>
AuthorDate: Fri Feb 23 13:42:28 2018 +0000
SLING-7512 Order features based on their dependencies.
Very initial implementation which contains the refactoring of
Requirements and Capabilities to use the OSGi ones.
---
.../feature/AbstractCapabilityRequirement.java | 96 +++++++++++++++++
.../java/org/apache/sling/feature/Capability.java | 104 -------------------
.../java/org/apache/sling/feature/Feature.java | 11 +-
.../org/apache/sling/feature/OSGiCapability.java | 59 +++++++++++
.../org/apache/sling/feature/OSGiRequirement.java | 59 +++++++++++
.../java/org/apache/sling/feature/Requirement.java | 113 ---------------------
.../sling/feature/process/ApplicationBuilder.java | 14 +--
.../apache/sling/feature/process/BuilderUtil.java | 4 +-
.../sling/feature/process/FeatureResolver.java | 38 +++++++
.../sling/feature/CapabilityRequirementTest.java | 68 +++++++++++++
.../sling/feature/process/FeatureBuilderTest.java | 39 +++----
11 files changed, 357 insertions(+), 248 deletions(-)
diff --git a/src/main/java/org/apache/sling/feature/AbstractCapabilityRequirement.java b/src/main/java/org/apache/sling/feature/AbstractCapabilityRequirement.java
new file mode 100644
index 0000000..200cb92
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/AbstractCapabilityRequirement.java
@@ -0,0 +1,96 @@
+/*
+ * 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.sling.feature;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.osgi.resource.Resource;
+
+abstract class AbstractCapabilityRequirement {
+ private final Resource resource;
+ private final String namespace;
+ private final Map<String, Object> attributes;
+ private final Map<String, String> directives;
+
+ AbstractCapabilityRequirement(Resource res, String ns, Map<String, Object> attrs, Map<String, String> dirs) {
+ resource = res;
+ namespace = ns;
+ attributes = Collections.unmodifiableMap(new HashMap<>(attrs));
+ directives = Collections.unmodifiableMap(new HashMap<>(dirs));
+ }
+
+ public String getNamespace() {
+ return namespace;
+ }
+
+ public Map<String, Object> getAttributes() {
+ return attributes;
+ }
+
+ public Map<String, String> getDirectives() {
+ return directives;
+ }
+
+ public Resource getResource() {
+ return resource;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((attributes == null) ? 0 : attributes.hashCode());
+ result = prime * result + ((directives == null) ? 0 : directives.hashCode());
+ result = prime * result + ((namespace == null) ? 0 : namespace.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ AbstractCapabilityRequirement other = (AbstractCapabilityRequirement) obj;
+ if (attributes == null) {
+ if (other.attributes != null)
+ return false;
+ } else if (!attributes.equals(other.attributes))
+ return false;
+ if (directives == null) {
+ if (other.directives != null)
+ return false;
+ } else if (!directives.equals(other.directives))
+ return false;
+ if (namespace == null) {
+ if (other.namespace != null)
+ return false;
+ } else if (!namespace.equals(other.namespace))
+ return false;
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + " [resource=" + resource + ", namespace=" + namespace + ", attributes=" + attributes
+ + ", directives=" + directives + "]";
+ }
+}
diff --git a/src/main/java/org/apache/sling/feature/Capability.java b/src/main/java/org/apache/sling/feature/Capability.java
deleted file mode 100644
index 53c9c3b..0000000
--- a/src/main/java/org/apache/sling/feature/Capability.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with this
- * work for additional information regarding copyright ownership. The ASF
- * licenses this file to You under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package org.apache.sling.feature;
-
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * A capability of a feature.
- * The capability is modeled after an OSGi capability: it
- * belongs to a namespace and might have attributes and / or
- * directives.
- */
-public class Capability {
-
- /** The namspace. */
- private final String namespace;
-
- /** Map of attributes. */
- private final Map<String, Object> attributes = new ConcurrentHashMap<>();
-
- /** Map of directives. */
- private final Map<String, Object> directives = new ConcurrentHashMap<>();
-
- /**
- * Create a new Capability.
- * @param namespace The namespace
- * @throws IllegalArgumentException If namespace is {@code null}.
- */
- public Capability(final String namespace) {
- if ( namespace == null ) {
- throw new IllegalArgumentException("namespace must not be null.");
- }
- this.namespace = namespace;
- }
-
- /**
- * The namespace
- * @return The namespace
- */
- public String getNamespace() {
- return namespace;
- }
-
- /**
- * Get the map of attributes.
- * The map is modifiable.
- * @return The map of attributes.
- */
- public Map<String, Object> getAttributes() {
- return attributes;
- }
-
- /**
- * Get the map of directives.
- * The map is modifiable.
- * @return The map of directives.
- */
- public Map<String, Object> getDirectives() {
- return directives;
- }
-
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + attributes.hashCode();
- result = prime * result + directives.hashCode();
- result = prime * result + namespace.hashCode();
- return result;
- }
-
- @Override
- public boolean equals(final Object obj) {
- if (this == obj) {
- return true;
- }
- if (obj == null || getClass() != obj.getClass()) {
- return false;
- }
- final Capability other = (Capability) obj;
- if (!attributes.equals(other.attributes)
- || !directives.equals(other.directives)
- || !namespace.equals(other.namespace)) {
- return false;
- }
- return true;
- }
-}
diff --git a/src/main/java/org/apache/sling/feature/Feature.java b/src/main/java/org/apache/sling/feature/Feature.java
index 64c2488..874790d 100644
--- a/src/main/java/org/apache/sling/feature/Feature.java
+++ b/src/main/java/org/apache/sling/feature/Feature.java
@@ -20,6 +20,9 @@ import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Requirement;
+
/**
* A feature consists of
* <ul>
@@ -296,17 +299,13 @@ public class Feature implements Comparable<Feature> {
// requirements
for(final Requirement r : this.getRequirements()) {
- final Requirement c = new Requirement(r.getNamespace());
- c.getAttributes().putAll(r.getAttributes());
- c.getDirectives().putAll(r.getDirectives());
+ final Requirement c = new OSGiRequirement(r.getNamespace(), r.getAttributes(), r.getDirectives());
result.getRequirements().add(c);
}
// capabilities
for(final Capability r : this.getCapabilities()) {
- final Capability c = new Capability(r.getNamespace());
- c.getAttributes().putAll(r.getAttributes());
- c.getDirectives().putAll(r.getDirectives());
+ final Capability c = new OSGiCapability(r.getNamespace(), r.getAttributes(), r.getDirectives());
result.getCapabilities().add(c);
}
diff --git a/src/main/java/org/apache/sling/feature/OSGiCapability.java b/src/main/java/org/apache/sling/feature/OSGiCapability.java
new file mode 100644
index 0000000..5c2000d
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/OSGiCapability.java
@@ -0,0 +1,59 @@
+/*
+ * 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.sling.feature;
+
+import java.util.Map;
+
+import org.osgi.resource.Capability;
+import org.osgi.resource.Resource;
+
+/**
+ * Implementation of the OSGi Capability interface.
+ */
+public class OSGiCapability extends AbstractCapabilityRequirement implements Capability {
+ /**
+ * Create a capability that is not associated with a resource.
+ * @param res The resource associated with the capability. May be null.
+ * @param ns The namespace of the capability.
+ * @param attrs The attributes of the capability.
+ * @param dirs The directives of the capability.
+ */
+ public OSGiCapability(String ns, Map<String, Object> attrs, Map<String, String> dirs) {
+ this(null, ns, attrs, dirs);
+ }
+
+ /**
+ * Create a capability.
+ * @param res The resource associated with the capability. May be null.
+ * @param ns The namespace of the capability.
+ * @param attrs The attributes of the capability.
+ * @param dirs The directives of the capability.
+ */
+ public OSGiCapability(Resource res, String ns, Map<String, Object> attrs, Map<String, String> dirs) {
+ super(res, ns, attrs, dirs);
+ }
+
+ /**
+ * Create a capability based on an existing capability, providing the resource.
+ * The namespace, attributes and directives are copied from the provided capability.
+ * @param resource The resource to be associated with the capability
+ * @param capability The capability to base the new requirement on.
+ */
+ public OSGiCapability(Resource resource, Capability capability) {
+ this(resource, capability.getNamespace(), capability.getAttributes(), capability.getDirectives());
+ }
+}
diff --git a/src/main/java/org/apache/sling/feature/OSGiRequirement.java b/src/main/java/org/apache/sling/feature/OSGiRequirement.java
new file mode 100644
index 0000000..2727379
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/OSGiRequirement.java
@@ -0,0 +1,59 @@
+/*
+ * 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.sling.feature;
+
+import java.util.Map;
+
+import org.osgi.resource.Requirement;
+import org.osgi.resource.Resource;
+
+/**
+ * Implementation of the OSGi Requirement interface.
+ */
+public class OSGiRequirement extends AbstractCapabilityRequirement implements Requirement {
+ /**
+ * Create a requirement that is not associated with a resource.
+ * @param res The resource associated with the requirement.
+ * @param ns The namespace of the requirement.
+ * @param attrs The attributes of the requirement.
+ * @param dirs The directives of the requirement.
+ */
+ public OSGiRequirement(String ns, Map<String, Object> attrs, Map<String, String> dirs) {
+ this(null, ns, attrs, dirs);
+ }
+
+ /**
+ * Create a requirement.
+ * @param res The resource associated with the requirement.
+ * @param ns The namespace of the requirement.
+ * @param attrs The attributes of the requirement.
+ * @param dirs The directives of the requirement.
+ */
+ public OSGiRequirement(Resource res, String ns, Map<String, Object> attrs, Map<String, String> dirs) {
+ super(res, ns, attrs, dirs);
+ }
+
+ /**
+ * Create a requirement based on an existing requirement, providing the resource.
+ * The namespace, attributes and directives are copied from the provided requirement.
+ * @param resource The resource to be associated with the requirement
+ * @param requirement The requirement to base the new requirement on.
+ */
+ public OSGiRequirement(Resource resource, Requirement requirement) {
+ this(resource, requirement.getNamespace(), requirement.getAttributes(), requirement.getDirectives());
+ }
+}
diff --git a/src/main/java/org/apache/sling/feature/Requirement.java b/src/main/java/org/apache/sling/feature/Requirement.java
deleted file mode 100644
index 3409e51..0000000
--- a/src/main/java/org/apache/sling/feature/Requirement.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with this
- * work for additional information regarding copyright ownership. The ASF
- * licenses this file to You under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package org.apache.sling.feature;
-
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * A requirement for a feature.
- * The requirement is modeled after an OSGi requirement: it
- * belongs to a namespace and might have attributes and / or
- * directives.
- */
-public class Requirement {
-
- /*public static final String RESOLUTION_OPTIONAL = "optional";
-
- public static final String RESOLUTION_DIRECTIVE = "resolution";*/
-
- /** The namspace. */
- private final String namespace;
-
- /** Map of attributes. */
- private final Map<String, Object> attributes = new ConcurrentHashMap<>();
-
- /** Map of directives. */
- private final Map<String, Object> directives = new ConcurrentHashMap<>();
-
- /**
- * Create a new Requirement.
- * @param namespace The namespace
- * @throws IllegalArgumentException If namespace is {@code null}.
- */
- public Requirement(final String namespace) {
- if ( namespace == null ) {
- throw new IllegalArgumentException("namespace must not be null.");
- }
- this.namespace = namespace;
- }
-
- /**
- * The namespace
- * @return The namespace
- */
- public String getNamespace() {
- return namespace;
- }
-
- /**
- * Get the map of attributes.
- * The map is modifiable.
- * @return The map of attributes.
- */
- public Map<String,Object> getAttributes() {
- return attributes;
- }
-
- /**
- * Get the map of directives.
- * The map is modifiable.
- * @return The map of directives.
- */
- public Map<String,Object> getDirectives() {
- return directives;
- }
-
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + attributes.hashCode();
- result = prime * result + directives.hashCode();
- result = prime * result + namespace.hashCode();
- return result;
- }
-
- @Override
- public boolean equals(final Object obj) {
- if (this == obj) {
- return true;
- }
- if (obj == null || getClass() != obj.getClass()) {
- return false;
- }
- final Requirement other = (Requirement) obj;
- if (!attributes.equals(other.attributes)
- || !directives.equals(other.directives)
- || !namespace.equals(other.namespace)) {
- return false;
- }
- return true;
- }
-
- @Override
- public String toString() {
- return "[Requirement namespace='" + namespace + "' attributes=" + attributes + " directives=" + directives + "]";
- }
-}
diff --git a/src/main/java/org/apache/sling/feature/process/ApplicationBuilder.java b/src/main/java/org/apache/sling/feature/process/ApplicationBuilder.java
index 1a555e1..49c17b8 100644
--- a/src/main/java/org/apache/sling/feature/process/ApplicationBuilder.java
+++ b/src/main/java/org/apache/sling/feature/process/ApplicationBuilder.java
@@ -17,7 +17,6 @@
package org.apache.sling.feature.process;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
import org.apache.sling.feature.Application;
@@ -38,6 +37,7 @@ public class ApplicationBuilder {
*
* @param app The optional application to use as a base.
* @param context The builder context
+ * @param resolver The Feature Resolver to use
* @param featureIds The feature ids
* @return The application
* throws IllegalArgumentException If context or featureIds is {@code null}
@@ -45,6 +45,7 @@ public class ApplicationBuilder {
*/
public static Application assemble(final Application app,
final BuilderContext context,
+ final FeatureResolver resolver,
final String... featureIds) {
if ( featureIds == null || context == null ) {
throw new IllegalArgumentException("Features and/or context must not be null");
@@ -59,7 +60,7 @@ public class ApplicationBuilder {
}
index++;
}
- return assemble(app, context, features);
+ return assemble(app, context, resolver, features);
}
/**
@@ -70,6 +71,7 @@ public class ApplicationBuilder {
*
* @param app The optional application to use as a base.
* @param context The builder context
+ * @param resolver The Feature Resolver to use
* @param features The features
* @return The application
* throws IllegalArgumentException If context or featureIds is {@code null}
@@ -78,7 +80,7 @@ public class ApplicationBuilder {
public static Application assemble(
Application app,
final BuilderContext context,
- final Feature... features) {
+ final FeatureResolver resolver, final Feature... features) {
if ( features == null || context == null ) {
throw new IllegalArgumentException("Features and/or context must not be null");
}
@@ -89,7 +91,7 @@ public class ApplicationBuilder {
// Created sorted feature list
// Remove duplicate features by selecting the one with the highest version
- final List<Feature> sortedFeatureList = new ArrayList<>();
+ List<Feature> sortedFeatureList = new ArrayList<>();
for(final Feature f : features) {
Feature found = null;
for(final Feature s : sortedFeatureList) {
@@ -116,8 +118,8 @@ public class ApplicationBuilder {
}
}
- // sort
- Collections.sort(sortedFeatureList);
+ // order by dependency chain
+ sortedFeatureList = resolver.orderFeatures(sortedFeatureList);
// assemble
for(final Feature f : sortedFeatureList) {
diff --git a/src/main/java/org/apache/sling/feature/process/BuilderUtil.java b/src/main/java/org/apache/sling/feature/process/BuilderUtil.java
index 377a5be..90da88e 100644
--- a/src/main/java/org/apache/sling/feature/process/BuilderUtil.java
+++ b/src/main/java/org/apache/sling/feature/process/BuilderUtil.java
@@ -31,13 +31,13 @@ import javax.json.JsonValue.ValueType;
import org.apache.sling.feature.Application;
import org.apache.sling.feature.Artifact;
import org.apache.sling.feature.Bundles;
-import org.apache.sling.feature.Capability;
import org.apache.sling.feature.Configuration;
import org.apache.sling.feature.Configurations;
import org.apache.sling.feature.Extension;
import org.apache.sling.feature.Feature;
import org.apache.sling.feature.KeyValueMap;
-import org.apache.sling.feature.Requirement;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Requirement;
/**
* Utility methods for the builders
diff --git a/src/main/java/org/apache/sling/feature/process/FeatureResolver.java b/src/main/java/org/apache/sling/feature/process/FeatureResolver.java
new file mode 100644
index 0000000..b3eaa35
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/process/FeatureResolver.java
@@ -0,0 +1,38 @@
+/*
+ * 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.sling.feature.process;
+
+import java.util.List;
+
+import org.apache.sling.feature.Feature;
+
+/**
+ * A resolver that can perform operations on the feature model.
+ */
+public interface FeatureResolver extends AutoCloseable {
+ /**
+ * Order the features by their dependency chain. Each feature and its
+ * components are resolved and each other feature providing the capabilities
+ * needed by the feature is placed before the requiring feature in the
+ * result.
+ *
+ * @param features
+ * The features to order.
+ * @return The ordered features.
+ */
+ List<Feature> orderFeatures(List<Feature> features);
+}
diff --git a/src/test/java/org/apache/sling/feature/CapabilityRequirementTest.java b/src/test/java/org/apache/sling/feature/CapabilityRequirementTest.java
new file mode 100644
index 0000000..e834bad
--- /dev/null
+++ b/src/test/java/org/apache/sling/feature/CapabilityRequirementTest.java
@@ -0,0 +1,68 @@
+/*
+ * 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.sling.feature;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.Test;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Requirement;
+import org.osgi.resource.Resource;
+
+public class CapabilityRequirementTest {
+ @Test
+ public void testCapability() {
+ Map<String, Object> attrs = new HashMap<>();
+ attrs.put("org.foo", "1234");
+ attrs.put("bar", 456);
+ Map<String, String> dirs = new HashMap<>();
+ dirs.put("my_dir", "my_value");
+ Capability c = new OSGiCapability("org.foo", attrs, dirs);
+ assertEquals("org.foo", c.getNamespace());
+ assertEquals(attrs, c.getAttributes());
+ assertEquals(dirs, c.getDirectives());
+ assertNull(c.getResource());
+ }
+
+ @Test
+ public void testRequirement() {
+ Resource tr = new TestResource();
+ Requirement r = new OSGiRequirement(tr, "testing",
+ Collections.emptyMap(), Collections.emptyMap());
+ assertEquals(tr, r.getResource());
+ assertEquals(0, r.getAttributes().size());
+ assertEquals(0, r.getDirectives().size());
+ }
+
+ private static class TestResource implements Resource {
+ @Override
+ public List<Capability> getCapabilities(String namespace) {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public List<Requirement> getRequirements(String namespace) {
+ return Collections.emptyList();
+ }
+ }
+}
diff --git a/src/test/java/org/apache/sling/feature/process/FeatureBuilderTest.java b/src/test/java/org/apache/sling/feature/process/FeatureBuilderTest.java
index f990aec..ca27cf5 100644
--- a/src/test/java/org/apache/sling/feature/process/FeatureBuilderTest.java
+++ b/src/test/java/org/apache/sling/feature/process/FeatureBuilderTest.java
@@ -22,6 +22,7 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -29,13 +30,15 @@ import java.util.Map;
import org.apache.sling.feature.Artifact;
import org.apache.sling.feature.ArtifactId;
import org.apache.sling.feature.BundlesTest;
-import org.apache.sling.feature.Capability;
import org.apache.sling.feature.Configuration;
import org.apache.sling.feature.Extension;
import org.apache.sling.feature.Feature;
import org.apache.sling.feature.Include;
-import org.apache.sling.feature.Requirement;
+import org.apache.sling.feature.OSGiCapability;
+import org.apache.sling.feature.OSGiRequirement;
import org.junit.Test;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Requirement;
public class FeatureBuilderTest {
@@ -193,18 +196,19 @@ public class FeatureBuilderTest {
@Test public void testNoIncludesNoUpgrade() throws Exception {
final Feature base = new Feature(ArtifactId.parse("org.apache.sling/test-feature/1.1"));
- final Requirement r1 = new Requirement("osgi.contract");
- r1.getDirectives().put("filter", "(&(osgi.contract=JavaServlet)(version=3.1))");
+ final Requirement r1 = new OSGiRequirement("osgi.contract",
+ Collections.emptyMap(), Collections.singletonMap("filter", "(&(osgi.contract=JavaServlet)(version=3.1))"));
base.getRequirements().add(r1);
- final Capability c1 = new Capability("osgi.implementation");
- c1.getAttributes().put("osgi.implementation", "osgi.http");
- c1.getAttributes().put("version:Version", "1.1");
- c1.getDirectives().put("uses", "javax.servlet,javax.servlet.http,org.osgi.service.http.context,org.osgi.service.http.whiteboard");
+ Map<String, Object> attrs = new HashMap<>();
+ attrs.put("osgi.implementation", "osgi.http");
+ attrs.put("version:Version", "1.1");
+ final Capability c1 = new OSGiCapability("osgi.implementation", attrs,
+ Collections.singletonMap("uses", "javax.servlet,javax.servlet.http,org.osgi.service.http.context,org.osgi.service.http.whiteboard"));
base.getCapabilities().add(c1);
- final Capability c2 = new Capability("osgi.service");
- c2.getAttributes().put("objectClass:List<String>", "org.osgi.service.http.runtime.HttpServiceRuntime");
- c2.getDirectives().put("uses", "org.osgi.service.http.runtime,org.osgi.service.http.runtime.dto");
+ final Capability c2 = new OSGiCapability("osgi.service",
+ Collections.singletonMap("objectClass:List<String>", "org.osgi.service.http.runtime.HttpServiceRuntime"),
+ Collections.singletonMap("uses", "org.osgi.service.http.runtime,org.osgi.service.http.runtime.dto"));
base.getCapabilities().add(c2);
base.getFrameworkProperties().put("foo", "1");
@@ -241,14 +245,15 @@ public class FeatureBuilderTest {
final Include i1 = new Include(ArtifactId.parse("g/a/1"));
base.getIncludes().add(i1);
- final Requirement r1 = new Requirement("osgi.contract");
- r1.getDirectives().put("filter", "(&(osgi.contract=JavaServlet)(version=3.1))");
+ final Requirement r1 = new OSGiRequirement("osgi.contract",
+ Collections.emptyMap(), Collections.singletonMap("filter", "(&(osgi.contract=JavaServlet)(version=3.1))"));
base.getRequirements().add(r1);
- final Capability c1 = new Capability("osgi.implementation");
- c1.getAttributes().put("osgi.implementation", "osgi.http");
- c1.getAttributes().put("version:Version", "1.1");
- c1.getDirectives().put("uses", "javax.servlet,javax.servlet.http,org.osgi.service.http.context,org.osgi.service.http.whiteboard");
+ Map<String, Object> attrs = new HashMap<>();
+ attrs.put("osgi.implementation", "osgi.http");
+ attrs.put("version:Version", "1.1");
+ final Capability c1 = new OSGiCapability("osgi.implementation", attrs,
+ Collections.singletonMap("uses", "javax.servlet,javax.servlet.http,org.osgi.service.http.context,org.osgi.service.http.whiteboard"));
base.getCapabilities().add(c1);
base.getFrameworkProperties().put("foo", "1");
--
To stop receiving notification emails like this one, please contact
davidb@apache.org.
[sling-org-apache-sling-feature] 02/22: Add section about OBR
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
davidb pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-feature.git
commit 30803cddafec55d6c4c996d63e077df4efad352d
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Mon Nov 6 12:33:56 2017 +0100
Add section about OBR
---
readme.md | 24 ++++++++++++++++++++++--
1 file changed, 22 insertions(+), 2 deletions(-)
diff --git a/readme.md b/readme.md
index 2fbf1f4..facc995 100644
--- a/readme.md
+++ b/readme.md
@@ -208,9 +208,29 @@ This is a feature example:
}
}
-# Relation to OBR
+# Relation to Repository Specification (Chapter 132)
+
+There are two major differences between a repository as described in the Repository Service Description and the feature model. A repository contains a list of more or less unrelated resources whereas a feature describes resources as a unit. For example a feature allows to define a bundle together with OSGi configurations - which ensures that whenever this feature is used, the bundle *together with* the configurations are deployed. A repository can only describe the bundle as a separate r [...]
+
+The second difference is the handling of requirements and capabilities. While a repository is supposed to list all requirements and capabilities of a resource as part of the description, the feature model does not require this. As the feature model refers to the bundle and the bundle has the requirements and capabilities as metadata, there is no need to repeat that information.
+
+By these two differences you can already tell, that a repository contents is usually generated by tools while a feature is usually a human created resource. While it is possible to create a repository index out of a feature, the other way round does not work as the repository has no standard way to define relationships between resources.
+
+# Requirements and Capabilities of Artifacts
+
+The feature model does not allow to explicitly list requirements or capabilities for artifacts. An artifact, for example a bundle, contains this information as part of its metadata. However, to calculate or display these, the tool processing the feature needs to have access to the artifact and needs to extract this. While in general this does not pose a problem by itself, it might happen that the same artifact is processed several times for example during a build process, causing overhead.
+
+To avoid this, a feature might contain an additional section, named "reqscaps" (TODO find a better name). This section is grouped by artifact ids and contains the requirements and capabilities of each artifact. While the requirements and capabilities of a single artifact must be correct and neither leave out or add additional ones, the list of artifacts must not be complete. Tooling will first look into this section to get requirements and capabilities for an artifact. If there are none, [...]
+
+ {
+ ...
+ "reqscaps" : {
+ "org.apache.sling:org.apache.sling.scripting.jsp:1.0.0" : {
+ "capabilities" : [],
+ "requirements" : []
+ }
+ }
-TODO
# Provisioning Applications
--
To stop receiving notification emails like this one, please contact
davidb@apache.org.
[sling-org-apache-sling-feature] 12/22: SLING-7521 Order bundles in
the generated app based on feature order and start order
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
davidb pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-feature.git
commit 5ec3548e14f1ebfb383ec32e427cf131202bb119
Author: David Bosschaert <bo...@adobe.com>
AuthorDate: Mon Mar 5 15:05:23 2018 +0000
SLING-7521 Order bundles in the generated app based on feature order and start order
Order resource (bundles and features) in the resulting application based on the order
of resolved features and then also in the order of the start order within the feature.
---
.../org/apache/sling/feature/FeatureResource.java | 50 ++++++++++++++++++++++
.../sling/feature/process/ApplicationBuilder.java | 43 +++++++++++++------
.../sling/feature/process/FeatureResolver.java | 13 +++---
3 files changed, 88 insertions(+), 18 deletions(-)
diff --git a/src/main/java/org/apache/sling/feature/FeatureResource.java b/src/main/java/org/apache/sling/feature/FeatureResource.java
new file mode 100644
index 0000000..38b0b43
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/FeatureResource.java
@@ -0,0 +1,50 @@
+/*
+ * 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.sling.feature;
+
+import org.osgi.framework.Version;
+import org.osgi.resource.Resource;
+
+/**
+ * A Resource that is associated with an Maven Artifact and belongs to a Feature.
+ */
+public interface FeatureResource extends Resource {
+ /**
+ * Obtain the ID of the resource. If the resource is a bundle then this
+ * is the bundle symbolic name.
+ * @return The ID of the resource.
+ */
+ String getId();
+
+ /**
+ * Obtain the version of the resource.
+ * @return The version of the resource.
+ */
+ Version getVersion();
+
+ /**
+ * Obtain the associated (Maven) Artifact.
+ * @return The artifact for this Resource.
+ */
+ Artifact getArtifact();
+
+ /**
+ * Obtain the feature that contains this resource.
+ * @return The feature that contains the resource.
+ */
+ Feature getFeature();
+}
diff --git a/src/main/java/org/apache/sling/feature/process/ApplicationBuilder.java b/src/main/java/org/apache/sling/feature/process/ApplicationBuilder.java
index 49c17b8..2a9698a 100644
--- a/src/main/java/org/apache/sling/feature/process/ApplicationBuilder.java
+++ b/src/main/java/org/apache/sling/feature/process/ApplicationBuilder.java
@@ -16,12 +16,14 @@
*/
package org.apache.sling.feature.process;
-import java.util.ArrayList;
-import java.util.List;
-
import org.apache.sling.feature.Application;
+import org.apache.sling.feature.Artifact;
import org.apache.sling.feature.ArtifactId;
import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.FeatureResource;
+
+import java.util.ArrayList;
+import java.util.List;
/**
* Build an application based on features.
@@ -89,12 +91,11 @@ public class ApplicationBuilder {
app = new Application();
}
- // Created sorted feature list
// Remove duplicate features by selecting the one with the highest version
- List<Feature> sortedFeatureList = new ArrayList<>();
+ final List<Feature> featureList = new ArrayList<>();
for(final Feature f : features) {
Feature found = null;
- for(final Feature s : sortedFeatureList) {
+ for(final Feature s : featureList) {
if ( s.getId().isSame(f.getId()) ) {
found = s;
break;
@@ -108,21 +109,29 @@ public class ApplicationBuilder {
add = false;
} else {
// remove lower version, higher version will be added
- app.getFeatureIds().remove(found.getId());
- sortedFeatureList.remove(found);
+ featureList.remove(found);
}
}
if ( add ) {
- app.getFeatureIds().add(f.getId());
- sortedFeatureList.add(f);
+ featureList.add(f);
}
}
// order by dependency chain
- sortedFeatureList = resolver.orderFeatures(sortedFeatureList);
+ final List<FeatureResource> sortedResources = resolver.orderResources(featureList);
+
+ final List<Feature> sortedFeatures = new ArrayList<>();
+ for (final FeatureResource fr : sortedResources) {
+ Feature f = fr.getFeature();
+ if (!sortedFeatures.contains(f)) {
+ sortedFeatures.add(f);
+ }
+ }
// assemble
- for(final Feature f : sortedFeatureList) {
+ int featureStartOrder = 5; // begin with start order a little higher than 0
+ for(final Feature f : sortedFeatures) {
+ app.getFeatureIds().add(f.getId());
final Feature assembled = FeatureBuilder.assemble(f, context.clone(new FeatureProvider() {
@Override
@@ -136,6 +145,16 @@ public class ApplicationBuilder {
}
}));
+ int globalStartOrder = featureStartOrder;
+ for (Artifact a : assembled.getBundles()) {
+ int so = a.getStartOrder() + featureStartOrder;
+ if (so > globalStartOrder)
+ globalStartOrder = so;
+ a.setStartOrder(so);
+ }
+ // Next feature will have a higher start order than the previous
+ featureStartOrder = globalStartOrder + 1;
+
merge(app, assembled);
}
diff --git a/src/main/java/org/apache/sling/feature/process/FeatureResolver.java b/src/main/java/org/apache/sling/feature/process/FeatureResolver.java
index b3eaa35..5fbba7c 100644
--- a/src/main/java/org/apache/sling/feature/process/FeatureResolver.java
+++ b/src/main/java/org/apache/sling/feature/process/FeatureResolver.java
@@ -19,20 +19,21 @@ package org.apache.sling.feature.process;
import java.util.List;
import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.FeatureResource;
/**
* A resolver that can perform operations on the feature model.
*/
public interface FeatureResolver extends AutoCloseable {
/**
- * Order the features by their dependency chain. Each feature and its
- * components are resolved and each other feature providing the capabilities
- * needed by the feature is placed before the requiring feature in the
- * result.
+ * Order the resources in list of features by their dependency chain.
+ * Each feature and its components are resolved. Then all the resources
+ * in the feature are ordered so that each resource is placed before
+ * the requiring feature/resources in the result.
*
* @param features
* The features to order.
- * @return The ordered features.
+ * @return The ordered resources from the features.
*/
- List<Feature> orderFeatures(List<Feature> features);
+ List<FeatureResource> orderResources(List<Feature> features);
}
--
To stop receiving notification emails like this one, please contact
davidb@apache.org.
[sling-org-apache-sling-feature] 07/22: Refactor start order
handling
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
davidb pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-feature.git
commit 3d3750fa0e015adaedd2c8661820a4f2a0b31551
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Fri Jan 19 08:07:53 2018 +0100
Refactor start order handling
---
.../java/org/apache/sling/feature/Artifact.java | 41 ++++++++++++
.../java/org/apache/sling/feature/Bundles.java | 76 +++-------------------
.../apache/sling/feature/process/BuilderUtil.java | 4 +-
3 files changed, 52 insertions(+), 69 deletions(-)
diff --git a/src/main/java/org/apache/sling/feature/Artifact.java b/src/main/java/org/apache/sling/feature/Artifact.java
index 6821e26..81ff660 100644
--- a/src/main/java/org/apache/sling/feature/Artifact.java
+++ b/src/main/java/org/apache/sling/feature/Artifact.java
@@ -63,6 +63,47 @@ public class Artifact implements Comparable<Artifact> {
return this.metadata;
}
+ /**
+ * Get the start order of the artifact.
+ * This is a convenience method which gets the value for the property named
+ * {@code #KEY_START_ORDER} from the metadata.
+ * @return The start order, if no start order is defined, {@code 0} is returned.
+ * @throws NumberFormatException If the stored metadata is not a number
+ * @throws IllegalStateException If the stored metadata is a negative number
+ */
+ public int getStartOrder() {
+ final String order = this.getMetadata().get(Artifact.KEY_START_ORDER);
+ final int startOrder;
+ if ( order != null ) {
+ startOrder = Integer.parseInt(order);
+ if ( startOrder < 0 ) {
+ throw new IllegalStateException("Start order must be >= 0 but is " + order);
+ }
+ } else {
+ startOrder = 0;
+ }
+
+ return startOrder;
+ }
+
+ /**
+ * Set the start order of the artifact
+ * This is a convenience method which sets the value of the property named
+ * {@code #KEY_START_ORDER} from the metadata.
+ * @param startOrder The start order
+ * @throws IllegalArgumentException If the number is negative
+ */
+ public void setStartOrder(final int startOrder) {
+ if ( startOrder < 0 ) {
+ throw new IllegalArgumentException("Start order must be >= 0 but is " + startOrder);
+ }
+ if ( startOrder == 0 ) {
+ this.getMetadata().remove(KEY_START_ORDER);
+ } else {
+ this.getMetadata().put(KEY_START_ORDER, String.valueOf(startOrder));
+ }
+ }
+
@Override
public int compareTo(final Artifact o) {
return this.id.compareTo(o.id);
diff --git a/src/main/java/org/apache/sling/feature/Bundles.java b/src/main/java/org/apache/sling/feature/Bundles.java
index 269d39f..12b5b52 100644
--- a/src/main/java/org/apache/sling/feature/Bundles.java
+++ b/src/main/java/org/apache/sling/feature/Bundles.java
@@ -34,25 +34,9 @@ public class Bundles implements Iterable<Artifact> {
private final List<Artifact> bundles = new ArrayList<>();
/**
- * Get the start order of a bundle.
- * @param bundle The bundle
- * @return The start order, if no start order is defined, {@code 0} is returned.
- */
- public static int getStartOrder(final Artifact bundle) {
- final String order = bundle.getMetadata().get(Artifact.KEY_START_ORDER);
- final int startOrder;
- if ( order != null ) {
- startOrder = Integer.parseInt(order);
- } else {
- startOrder = 0;
- }
-
- return startOrder;
- }
-
- /**
* Get the map of all bundles sorted by start order. The map is sorted
- * and iterating over the keys is done in start level order.
+ * and iterating over the keys is done in start order. Bundles without a start
+ * order (having the value 0) are returned last.
* @return The map of bundles. The map is unmodifiable.
*/
public Map<Integer, List<Artifact>> getBundlesByStartOrder() {
@@ -74,7 +58,7 @@ public class Bundles implements Iterable<Artifact> {
});
for(final Artifact bundle : this.bundles) {
- final int startOrder = getStartOrder(bundle);
+ final int startOrder = bundle.getStartOrder();
List<Artifact> list = startOrderMap.get(startOrder);
if ( list == null ) {
list = new ArrayList<>();
@@ -85,31 +69,6 @@ public class Bundles implements Iterable<Artifact> {
return Collections.unmodifiableMap(startOrderMap);
}
- public List<Map.Entry<Integer, Artifact>> getAllBundles() {
- final List<Map.Entry<Integer, Artifact>> list = new ArrayList<>();
- for(final Artifact a : this) {
- list.add(new Map.Entry<Integer, Artifact>() {
-
- @Override
- public Artifact setValue(Artifact value) {
- return null;
- }
-
- @Override
- public Artifact getValue() {
- // TODO Auto-generated method stub
- return a;
- }
-
- @Override
- public Integer getKey() {
- return getStartOrder(a);
- }
- });
- }
- return list;
- }
-
/**
* Add an artifact as a bundle.
* @param bundle The bundle
@@ -158,31 +117,14 @@ public class Bundles implements Iterable<Artifact> {
}
/**
- * Get start order and artifact for the given id, neglecting the version
+ * Get the artifact for the given id, neglecting the version
* @param id The artifact id
* @return A map entry with start order and artifact, {@code null} otherwise
*/
- public Map.Entry<Integer, Artifact> getSame(final ArtifactId id) {
- for(final Artifact bundle : this) {
+ public Artifact getSame(final ArtifactId id) {
+ for(final Artifact bundle : this.bundles) {
if ( bundle.getId().isSame(id)) {
- final int startOrder = getStartOrder(bundle);
- return new Map.Entry<Integer, Artifact>() {
-
- @Override
- public Integer getKey() {
- return startOrder;
- }
-
- @Override
- public Artifact getValue() {
- return bundle;
- }
-
- @Override
- public Artifact setValue(final Artifact value) {
- throw new IllegalStateException();
- }
- };
+ return bundle;
}
}
return null;
@@ -194,7 +136,7 @@ public class Bundles implements Iterable<Artifact> {
* @return {@code true} if the artifact exists
*/
public boolean containsExact(final ArtifactId id) {
- for(final Artifact entry : this) {
+ for(final Artifact entry : this.bundles) {
if ( entry.getId().equals(id)) {
return true;
}
@@ -208,7 +150,7 @@ public class Bundles implements Iterable<Artifact> {
* @return {@code true} if the artifact exists
*/
public boolean containsSame(final ArtifactId id) {
- for(final Artifact entry : this) {
+ for(final Artifact entry : this.bundles) {
if ( entry.getId().isSame(id)) {
return true;
}
diff --git a/src/main/java/org/apache/sling/feature/process/BuilderUtil.java b/src/main/java/org/apache/sling/feature/process/BuilderUtil.java
index 1db7bf8..377a5be 100644
--- a/src/main/java/org/apache/sling/feature/process/BuilderUtil.java
+++ b/src/main/java/org/apache/sling/feature/process/BuilderUtil.java
@@ -58,8 +58,8 @@ class BuilderUtil {
// version handling - use provided algorithm
boolean replace = true;
if ( artifactMergeAlg == ArtifactMerge.HIGHEST ) {
- final Map.Entry<Integer, Artifact> existing = target.getSame(a.getId());
- if ( existing != null && existing.getValue().getId().getOSGiVersion().compareTo(a.getId().getOSGiVersion()) > 0 ) {
+ final Artifact existing = target.getSame(a.getId());
+ if ( existing != null && existing.getId().getOSGiVersion().compareTo(a.getId().getOSGiVersion()) > 0 ) {
replace = false;
}
}
--
To stop receiving notification emails like this one, please contact
davidb@apache.org.
[sling-org-apache-sling-feature] 05/22: Fix ArtifactId.toMvnId to
be symmetrical with fromMvnId
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
davidb pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-feature.git
commit 7a98140cb58485ee2404874d33dce221351d0572
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Tue Nov 28 14:33:10 2017 +0200
Fix ArtifactId.toMvnId to be symmetrical with fromMvnId
The fromMvnId method expects the values in the
groupId:artifactId[:packaging[:classifier]]:version format,
but toMvnId was outputting them in the
groupId:artifactId:version[:packaging[:classifier]] format.
---
src/main/java/org/apache/sling/feature/ArtifactId.java | 4 ++--
src/test/java/org/apache/sling/feature/ArtifactIdTest.java | 5 +++++
2 files changed, 7 insertions(+), 2 deletions(-)
diff --git a/src/main/java/org/apache/sling/feature/ArtifactId.java b/src/main/java/org/apache/sling/feature/ArtifactId.java
index a7231c8..9eca7a9 100644
--- a/src/main/java/org/apache/sling/feature/ArtifactId.java
+++ b/src/main/java/org/apache/sling/feature/ArtifactId.java
@@ -205,8 +205,6 @@ public class ArtifactId implements Comparable<ArtifactId> {
sb.append(this.groupId);
sb.append(':');
sb.append(this.artifactId);
- sb.append(':');
- sb.append(version);
if ( this.classifier != null || !"jar".equals(this.type)) {
sb.append(':');
sb.append(this.type);
@@ -215,6 +213,8 @@ public class ArtifactId implements Comparable<ArtifactId> {
sb.append(this.classifier);
}
}
+ sb.append(':');
+ sb.append(version);
return sb.toString();
}
diff --git a/src/test/java/org/apache/sling/feature/ArtifactIdTest.java b/src/test/java/org/apache/sling/feature/ArtifactIdTest.java
index 958e117..2b01cdf 100644
--- a/src/test/java/org/apache/sling/feature/ArtifactIdTest.java
+++ b/src/test/java/org/apache/sling/feature/ArtifactIdTest.java
@@ -141,4 +141,9 @@ public class ArtifactIdTest {
assertEquals("zip", id.getType());
assertEquals("foo", id.getClassifier());
}
+
+ @Test public void testClassifierAndTypeToMvnId() {
+ final ArtifactId id = new ArtifactId("group.a", "artifact.b", "1.0", "foo", "zip");
+ assertEquals("group.a:artifact.b:zip:foo:1.0", id.toMvnId());
+ }
}
--
To stop receiving notification emails like this one, please contact
davidb@apache.org.
[sling-org-apache-sling-feature] 06/22: Start changing start level
handling to start order handling
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
davidb pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-feature.git
commit 062f90bcd89ccdfd684c30abdfef35ca6b647384
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Thu Jan 18 16:14:50 2018 +0100
Start changing start level handling to start order handling
---
.../java/org/apache/sling/feature/Artifact.java | 3 +
.../java/org/apache/sling/feature/Bundles.java | 221 ++++++++++-----------
.../java/org/apache/sling/feature/Feature.java | 9 +-
.../apache/sling/feature/process/BuilderUtil.java | 4 +-
.../java/org/apache/sling/feature/BundlesTest.java | 30 ++-
.../sling/feature/process/BuilderUtilTest.java | 64 ++++--
.../sling/feature/process/FeatureBuilderTest.java | 62 ++++--
7 files changed, 219 insertions(+), 174 deletions(-)
diff --git a/src/main/java/org/apache/sling/feature/Artifact.java b/src/main/java/org/apache/sling/feature/Artifact.java
index 4fada27..6821e26 100644
--- a/src/main/java/org/apache/sling/feature/Artifact.java
+++ b/src/main/java/org/apache/sling/feature/Artifact.java
@@ -25,6 +25,9 @@ package org.apache.sling.feature;
*/
public class Artifact implements Comparable<Artifact> {
+ /** This key might be used by bundles to define the start order. */
+ public static final String KEY_START_ORDER = "start-order";
+
/** The artifact id. */
private final ArtifactId id;
diff --git a/src/main/java/org/apache/sling/feature/Bundles.java b/src/main/java/org/apache/sling/feature/Bundles.java
index a359575..269d39f 100644
--- a/src/main/java/org/apache/sling/feature/Bundles.java
+++ b/src/main/java/org/apache/sling/feature/Bundles.java
@@ -18,60 +18,117 @@ package org.apache.sling.feature;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
-import java.util.Map.Entry;
-import java.util.NoSuchElementException;
import java.util.TreeMap;
/**
- * Bundles groups bundle {@code Artifact}s by start level.
+ * Bundles groups a list of bundles {@code Artifact} and provides a means
+ * to sort them based on start order.
*/
-public class Bundles implements Iterable<Map.Entry<Integer, Artifact>> {
+public class Bundles implements Iterable<Artifact> {
- /** Map of bundles grouped by start level */
- private final Map<Integer, List<Artifact>> startLevelMap = new TreeMap<>();
+ /** The list of bundles. */
+ private final List<Artifact> bundles = new ArrayList<>();
/**
- * Get the map of all bundles sorted by start level. The map is sorted
+ * Get the start order of a bundle.
+ * @param bundle The bundle
+ * @return The start order, if no start order is defined, {@code 0} is returned.
+ */
+ public static int getStartOrder(final Artifact bundle) {
+ final String order = bundle.getMetadata().get(Artifact.KEY_START_ORDER);
+ final int startOrder;
+ if ( order != null ) {
+ startOrder = Integer.parseInt(order);
+ } else {
+ startOrder = 0;
+ }
+
+ return startOrder;
+ }
+
+ /**
+ * Get the map of all bundles sorted by start order. The map is sorted
* and iterating over the keys is done in start level order.
* @return The map of bundles. The map is unmodifiable.
*/
- public Map<Integer, List<Artifact>> getBundlesByStartLevel() {
- return Collections.unmodifiableMap(this.startLevelMap);
+ public Map<Integer, List<Artifact>> getBundlesByStartOrder() {
+ final Map<Integer, List<Artifact>> startOrderMap = new TreeMap<>(new Comparator<Integer>() {
+
+ @Override
+ public int compare(final Integer o1, final Integer o2) {
+ if ( o1 == o2 ) {
+ return 0;
+ }
+ if ( o1 == 0 ) {
+ return 1;
+ }
+ if ( o2 == 0 ) {
+ return -1;
+ }
+ return o1-o2;
+ }
+ });
+
+ for(final Artifact bundle : this.bundles) {
+ final int startOrder = getStartOrder(bundle);
+ List<Artifact> list = startOrderMap.get(startOrder);
+ if ( list == null ) {
+ list = new ArrayList<>();
+ startOrderMap.put(startOrder, list);
+ }
+ list.add(bundle);
+ }
+ return Collections.unmodifiableMap(startOrderMap);
+ }
+
+ public List<Map.Entry<Integer, Artifact>> getAllBundles() {
+ final List<Map.Entry<Integer, Artifact>> list = new ArrayList<>();
+ for(final Artifact a : this) {
+ list.add(new Map.Entry<Integer, Artifact>() {
+
+ @Override
+ public Artifact setValue(Artifact value) {
+ return null;
+ }
+
+ @Override
+ public Artifact getValue() {
+ // TODO Auto-generated method stub
+ return a;
+ }
+
+ @Override
+ public Integer getKey() {
+ return getStartOrder(a);
+ }
+ });
+ }
+ return list;
}
/**
- * Add an artifact in the given start level.
- * @param startLevel The start level
+ * Add an artifact as a bundle.
* @param bundle The bundle
*/
- public void add(final int startLevel, final Artifact bundle) {
- List<Artifact> list = this.startLevelMap.get(startLevel);
- if ( list == null ) {
- list = new ArrayList<>();
- this.startLevelMap.put(startLevel, list);
- }
- list.add(bundle);
+ public void add(final Artifact bundle) {
+ this.bundles.add(bundle);
}
/**
* Remove the exact artifact.
- * All start levels are searched for such an artifact. The first one found is removed.
+ * All start orders are searched for such an artifact. The first one found is removed.
* @param id The artifact id
* @return {@code true} if the artifact has been removed
*/
public boolean removeExact(final ArtifactId id) {
- for(final Map.Entry<Integer, List<Artifact>> entry : this.startLevelMap.entrySet()) {
- for(final Artifact artifact : entry.getValue()) {
- if ( artifact.getId().equals(id)) {
- entry.getValue().remove(artifact);
- if ( entry.getValue().isEmpty() ) {
- this.startLevelMap.remove(entry.getKey());
- }
- return true;
- }
+ for(final Artifact artifact : this.bundles) {
+ if ( artifact.getId().equals(id)) {
+ this.bundles.remove(artifact);
+ return true;
}
}
return false;
@@ -79,50 +136,46 @@ public class Bundles implements Iterable<Map.Entry<Integer, Artifact>> {
/**
* Remove the same artifact, neglecting the version.
- * All start levels are searched for such an artifact. The first one found is removed.
+ * All start orders are searched for such an artifact. The first one found is removed.
* @param id The artifact id
* @return {@code true} if the artifact has been removed
*/
public boolean removeSame(final ArtifactId id) {
- for(final Map.Entry<Integer, List<Artifact>> entry : this.startLevelMap.entrySet()) {
- for(final Artifact artifact : entry.getValue()) {
- if ( artifact.getId().isSame(id)) {
- entry.getValue().remove(artifact);
- if ( entry.getValue().isEmpty() ) {
- this.startLevelMap.remove(entry.getKey());
- }
- return true;
- }
+ for(final Artifact artifact : this.bundles) {
+ if ( artifact.getId().isSame(id)) {
+ this.bundles.remove(artifact);
+ return true;
}
}
return false;
}
/**
- * Clear the bundles map.
+ * Clear the bundles list.
*/
public void clear() {
- this.startLevelMap.clear();
+ this.bundles.clear();
}
/**
- * Get start level and artifact for the given id, neglecting the version
+ * Get start order and artifact for the given id, neglecting the version
* @param id The artifact id
- * @return A map entry with start level and artifact, {@code null} otherwise
+ * @return A map entry with start order and artifact, {@code null} otherwise
*/
public Map.Entry<Integer, Artifact> getSame(final ArtifactId id) {
- for(final Map.Entry<Integer, Artifact> entry : this) {
- if ( entry.getValue().getId().isSame(id)) {
+ for(final Artifact bundle : this) {
+ if ( bundle.getId().isSame(id)) {
+ final int startOrder = getStartOrder(bundle);
return new Map.Entry<Integer, Artifact>() {
@Override
public Integer getKey() {
- return entry.getKey();
+ return startOrder;
}
@Override
public Artifact getValue() {
- return entry.getValue();
+ return bundle;
}
@Override
@@ -141,8 +194,8 @@ public class Bundles implements Iterable<Map.Entry<Integer, Artifact>> {
* @return {@code true} if the artifact exists
*/
public boolean containsExact(final ArtifactId id) {
- for(final Map.Entry<Integer, Artifact> entry : this) {
- if ( entry.getValue().getId().equals(id)) {
+ for(final Artifact entry : this) {
+ if ( entry.getId().equals(id)) {
return true;
}
}
@@ -155,8 +208,8 @@ public class Bundles implements Iterable<Map.Entry<Integer, Artifact>> {
* @return {@code true} if the artifact exists
*/
public boolean containsSame(final ArtifactId id) {
- for(final Map.Entry<Integer, Artifact> entry : this) {
- if ( entry.getValue().getId().isSame(id)) {
+ for(final Artifact entry : this) {
+ if ( entry.getId().isSame(id)) {
return true;
}
}
@@ -167,74 +220,12 @@ public class Bundles implements Iterable<Map.Entry<Integer, Artifact>> {
* Iterate over all bundles
*/
@Override
- public Iterator<Map.Entry<Integer, Artifact>> iterator() {
- final Iterator<Map.Entry<Integer, List<Artifact>>> mainIter = this.startLevelMap.entrySet().iterator();
- return new Iterator<Map.Entry<Integer,Artifact>>() {
-
- private Map.Entry<Integer, Artifact> next = seek();
-
- private Integer level;
-
- private Iterator<Artifact> innerIter;
-
- private Map.Entry<Integer, Artifact> seek() {
- Map.Entry<Integer, Artifact> entry = null;
- while ( this.innerIter != null || mainIter.hasNext() ) {
- if ( innerIter != null ) {
- if ( innerIter.hasNext() ) {
- final Artifact a = innerIter.next();
- final Integer l = this.level;
- entry = new Map.Entry<Integer, Artifact>() {
-
- @Override
- public Integer getKey() {
- return l;
- }
-
- @Override
- public Artifact getValue() {
- return a;
- }
-
- @Override
- public Artifact setValue(Artifact value) {
- throw new UnsupportedOperationException();
- }
- };
- break;
- } else {
- innerIter = null;
- }
- } else {
- final Map.Entry<Integer, List<Artifact>> e = mainIter.next();
- this.level = e.getKey();
- this.innerIter = e.getValue().iterator();
- }
- }
- return entry;
- }
-
- @Override
- public boolean hasNext() {
- return this.next != null;
- }
-
- @Override
- public Entry<Integer, Artifact> next() {
- final Entry<Integer, Artifact> result = next;
- if ( result == null ) {
- throw new NoSuchElementException();
- }
- this.next = seek();
- return result;
- }
-
- };
+ public Iterator<Artifact> iterator() {
+ return Collections.unmodifiableList(this.bundles).iterator();
}
@Override
public String toString() {
- return "Bundles [" + this.startLevelMap
- + "]";
+ return "Bundles " + this.bundles;
}
}
diff --git a/src/main/java/org/apache/sling/feature/Feature.java b/src/main/java/org/apache/sling/feature/Feature.java
index 3665689..f0f2cc5 100644
--- a/src/main/java/org/apache/sling/feature/Feature.java
+++ b/src/main/java/org/apache/sling/feature/Feature.java
@@ -19,7 +19,6 @@ package org.apache.sling.feature;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
-import java.util.Map;
/**
* A feature consists of
@@ -305,11 +304,11 @@ public class Feature implements Comparable<Feature> {
result.setAssembled(this.isAssembled());
// bundles
- for(final Map.Entry<Integer, Artifact> entry : this.getBundles()) {
- final Artifact c = new Artifact(entry.getValue().getId());
- c.getMetadata().putAll(entry.getValue().getMetadata());
+ for(final Artifact b : this.getBundles()) {
+ final Artifact c = new Artifact(b.getId());
+ c.getMetadata().putAll(b.getMetadata());
- result.getBundles().add(entry.getKey(), c);
+ result.getBundles().add(c);
}
// configurations
diff --git a/src/main/java/org/apache/sling/feature/process/BuilderUtil.java b/src/main/java/org/apache/sling/feature/process/BuilderUtil.java
index 19c606c..1db7bf8 100644
--- a/src/main/java/org/apache/sling/feature/process/BuilderUtil.java
+++ b/src/main/java/org/apache/sling/feature/process/BuilderUtil.java
@@ -53,7 +53,7 @@ class BuilderUtil {
static void mergeBundles(final Bundles target,
final Bundles source,
final ArtifactMerge artifactMergeAlg) {
- for(final Map.Entry<Integer, List<Artifact>> entry : source.getBundlesByStartLevel().entrySet()) {
+ for(final Map.Entry<Integer, List<Artifact>> entry : source.getBundlesByStartOrder().entrySet()) {
for(final Artifact a : entry.getValue()) {
// version handling - use provided algorithm
boolean replace = true;
@@ -65,7 +65,7 @@ class BuilderUtil {
}
if ( replace ) {
target.removeSame(a.getId());
- target.add(entry.getKey(), a);
+ target.add(a);
}
}
}
diff --git a/src/test/java/org/apache/sling/feature/BundlesTest.java b/src/test/java/org/apache/sling/feature/BundlesTest.java
index 3346f19..59d87eb 100644
--- a/src/test/java/org/apache/sling/feature/BundlesTest.java
+++ b/src/test/java/org/apache/sling/feature/BundlesTest.java
@@ -18,6 +18,7 @@ package org.apache.sling.feature;
import static org.junit.Assert.assertEquals;
+import java.util.List;
import java.util.Map;
import org.junit.Test;
@@ -27,19 +28,28 @@ public class BundlesTest {
@Test
public void testIterator() {
final Bundles bundles = new Bundles();
- bundles.add(1, new Artifact(ArtifactId.parse("1/a/1")));
- bundles.add(5, new Artifact(ArtifactId.parse("5/a/5")));
- bundles.add(5, new Artifact(ArtifactId.parse("5/b/6")));
- bundles.add(2, new Artifact(ArtifactId.parse("2/b/2")));
- bundles.add(2, new Artifact(ArtifactId.parse("2/a/3")));
- bundles.add(4, new Artifact(ArtifactId.parse("4/x/4")));
+ bundles.add(createBundle("1/a/1", 1));
+ bundles.add(createBundle("5/a/5", 5));
+ bundles.add(createBundle("5/b/6", 5));
+ bundles.add(createBundle("2/b/2", 2));
+ bundles.add(createBundle("2/a/3", 2));
+ bundles.add(createBundle("4/x/4", 4));
int index = 1;
- for(final Map.Entry<Integer, Artifact> entry : bundles) {
- assertEquals(entry.getKey().toString(), entry.getValue().getId().getGroupId());
- assertEquals(index, entry.getValue().getId().getOSGiVersion().getMajor());
- index++;
+ for(final Map.Entry<Integer, List<Artifact>> entry : bundles.getBundlesByStartOrder().entrySet()) {
+ for(final Artifact a : entry.getValue()) {
+ assertEquals(entry.getKey().toString(), a.getId().getGroupId());
+ assertEquals(index, a.getId().getOSGiVersion().getMajor());
+ index++;
+ }
}
assertEquals(7, index);
}
+
+ public static Artifact createBundle(final String id, final int startOrder) {
+ final Artifact a = new Artifact(ArtifactId.parse(id));
+ a.getMetadata().put(Artifact.KEY_START_ORDER, String.valueOf(startOrder));
+
+ return a;
+ }
}
diff --git a/src/test/java/org/apache/sling/feature/process/BuilderUtilTest.java b/src/test/java/org/apache/sling/feature/process/BuilderUtilTest.java
index a33ef95..42ce069 100644
--- a/src/test/java/org/apache/sling/feature/process/BuilderUtilTest.java
+++ b/src/test/java/org/apache/sling/feature/process/BuilderUtilTest.java
@@ -26,6 +26,7 @@ import java.util.Map;
import org.apache.sling.feature.Artifact;
import org.apache.sling.feature.ArtifactId;
import org.apache.sling.feature.Bundles;
+import org.apache.sling.feature.BundlesTest;
import org.apache.sling.feature.process.BuilderUtil.ArtifactMerge;
import org.junit.Test;
@@ -33,9 +34,28 @@ public class BuilderUtilTest {
private List<Map.Entry<Integer, Artifact>> getBundles(final Bundles f) {
final List<Map.Entry<Integer, Artifact>> result = new ArrayList<>();
- for(final Map.Entry<Integer, Artifact> entry : f) {
- result.add(entry);
+ for(final Map.Entry<Integer, List<Artifact>> entry : f.getBundlesByStartOrder().entrySet()) {
+ for(final Artifact artifact : entry.getValue()) {
+ result.add(new Map.Entry<Integer, Artifact>() {
+
+ @Override
+ public Integer getKey() {
+ return entry.getKey();
+ }
+
+ @Override
+ public Artifact getValue() {
+ return artifact;
+ }
+
+ @Override
+ public Artifact setValue(Artifact value) {
+ return null;
+ }
+ });
+ }
}
+
return result;
}
@@ -53,14 +73,14 @@ public class BuilderUtilTest {
@Test public void testMergeBundlesWithAlgHighest() {
final Bundles target = new Bundles();
- target.add(1, new Artifact(ArtifactId.parse("g/a/1.0")));
- target.add(2, new Artifact(ArtifactId.parse("g/b/2.0")));
- target.add(3, new Artifact(ArtifactId.parse("g/c/2.5")));
+ target.add(BundlesTest.createBundle("g/a/1.0", 1));
+ target.add(BundlesTest.createBundle("g/b/2.0", 2));
+ target.add(BundlesTest.createBundle("g/c/2.5", 3));
final Bundles source = new Bundles();
- source.add(1, new Artifact(ArtifactId.parse("g/a/1.1")));
- source.add(2, new Artifact(ArtifactId.parse("g/b/1.9")));
- source.add(3, new Artifact(ArtifactId.parse("g/c/2.5")));
+ source.add(BundlesTest.createBundle("g/a/1.1", 1));
+ source.add(BundlesTest.createBundle("g/b/1.9", 2));
+ source.add(BundlesTest.createBundle("g/c/2.5", 3));
BuilderUtil.mergeBundles(target, source, ArtifactMerge.HIGHEST);
@@ -74,14 +94,14 @@ public class BuilderUtilTest {
@Test public void testMergeBundlesWithAlgLatest() {
final Bundles target = new Bundles();
- target.add(1, new Artifact(ArtifactId.parse("g/a/1.0")));
- target.add(2, new Artifact(ArtifactId.parse("g/b/2.0")));
- target.add(3, new Artifact(ArtifactId.parse("g/c/2.5")));
+ target.add(BundlesTest.createBundle("g/a/1.0", 1));
+ target.add(BundlesTest.createBundle("g/b/2.0", 2));
+ target.add(BundlesTest.createBundle("g/c/2.5", 3));
final Bundles source = new Bundles();
- source.add(1, new Artifact(ArtifactId.parse("g/a/1.1")));
- source.add(2, new Artifact(ArtifactId.parse("g/b/1.9")));
- source.add(3, new Artifact(ArtifactId.parse("g/c/2.5")));
+ source.add(BundlesTest.createBundle("g/a/1.1", 1));
+ source.add(BundlesTest.createBundle("g/b/1.9", 2));
+ source.add(BundlesTest.createBundle("g/c/2.5", 3));
BuilderUtil.mergeBundles(target, source, ArtifactMerge.LATEST);
@@ -95,10 +115,10 @@ public class BuilderUtilTest {
@Test public void testMergeBundlesDifferentStartlevel() {
final Bundles target = new Bundles();
- target.add(1, new Artifact(ArtifactId.parse("g/a/1.0")));
+ target.add(BundlesTest.createBundle("g/a/1.0", 1));
final Bundles source = new Bundles();
- source.add(2, new Artifact(ArtifactId.parse("g/a/1.1")));
+ source.add(BundlesTest.createBundle("g/a/1.1", 2));
BuilderUtil.mergeBundles(target, source, ArtifactMerge.LATEST);
@@ -110,14 +130,14 @@ public class BuilderUtilTest {
@Test public void testMergeBundles() {
final Bundles target = new Bundles();
- target.add(1, new Artifact(ArtifactId.parse("g/a/1.0")));
- target.add(2, new Artifact(ArtifactId.parse("g/b/2.0")));
- target.add(3, new Artifact(ArtifactId.parse("g/c/2.5")));
+ target.add(BundlesTest.createBundle("g/a/1.0", 1));
+ target.add(BundlesTest.createBundle("g/b/2.0", 2));
+ target.add(BundlesTest.createBundle("g/c/2.5", 3));
final Bundles source = new Bundles();
- source.add(1, new Artifact(ArtifactId.parse("g/d/1.1")));
- source.add(2, new Artifact(ArtifactId.parse("g/e/1.9")));
- source.add(3, new Artifact(ArtifactId.parse("g/f/2.5")));
+ source.add(BundlesTest.createBundle("g/d/1.1", 1));
+ source.add(BundlesTest.createBundle("g/e/1.9", 2));
+ source.add(BundlesTest.createBundle("g/f/2.5", 3));
BuilderUtil.mergeBundles(target, source, ArtifactMerge.LATEST);
diff --git a/src/test/java/org/apache/sling/feature/process/FeatureBuilderTest.java b/src/test/java/org/apache/sling/feature/process/FeatureBuilderTest.java
index 5ef1a62..4fc31b3 100644
--- a/src/test/java/org/apache/sling/feature/process/FeatureBuilderTest.java
+++ b/src/test/java/org/apache/sling/feature/process/FeatureBuilderTest.java
@@ -28,6 +28,7 @@ import java.util.Map;
import org.apache.sling.feature.Artifact;
import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.BundlesTest;
import org.apache.sling.feature.Capability;
import org.apache.sling.feature.Configuration;
import org.apache.sling.feature.Extension;
@@ -46,11 +47,11 @@ public class FeatureBuilderTest {
f1.getFrameworkProperties().put("foo", "2");
f1.getFrameworkProperties().put("bar", "X");
- f1.getBundles().add(3, new Artifact(ArtifactId.parse("org.apache.sling/foo-bar/4.5.6")));
- f1.getBundles().add(5, new Artifact(ArtifactId.parse("group/testnewversion_low/2")));
- f1.getBundles().add(5, new Artifact(ArtifactId.parse("group/testnewversion_high/2")));
- f1.getBundles().add(5, new Artifact(ArtifactId.parse("group/testnewstartlevel/1")));
- f1.getBundles().add(5, new Artifact(ArtifactId.parse("group/testnewstartlevelandversion/1")));
+ f1.getBundles().add(BundlesTest.createBundle("org.apache.sling/foo-bar/4.5.6", 3));
+ f1.getBundles().add(BundlesTest.createBundle("group/testnewversion_low/2", 5));
+ f1.getBundles().add(BundlesTest.createBundle("group/testnewversion_high/2", 5));
+ f1.getBundles().add(BundlesTest.createBundle("group/testnewstartlevel/1", 5));
+ f1.getBundles().add(BundlesTest.createBundle("group/testnewstartlevelandversion/1", 5));
final Configuration c1 = new Configuration("org.apache.sling.foo");
c1.getProperties().put("prop", "value");
@@ -69,9 +70,28 @@ public class FeatureBuilderTest {
private List<Map.Entry<Integer, Artifact>> getBundles(final Feature f) {
final List<Map.Entry<Integer, Artifact>> result = new ArrayList<>();
- for(final Map.Entry<Integer, Artifact> entry : f.getBundles()) {
- result.add(entry);
+ for(final Map.Entry<Integer, List<Artifact>> entry : f.getBundles().getBundlesByStartOrder().entrySet()) {
+ for(final Artifact artifact : entry.getValue()) {
+ result.add(new Map.Entry<Integer, Artifact>() {
+
+ @Override
+ public Integer getKey() {
+ return entry.getKey();
+ }
+
+ @Override
+ public Artifact getValue() {
+ return artifact;
+ }
+
+ @Override
+ public Artifact setValue(Artifact value) {
+ return null;
+ }
+ });
+ }
}
+
return result;
}
@@ -194,11 +214,12 @@ public class FeatureBuilderTest {
base.getFrameworkProperties().put("org.apache.felix.scr.directory", "launchpad/scr");
final Artifact a1 = new Artifact(ArtifactId.parse("org.apache.sling/oak-server/1.0.0"));
+ a1.getMetadata().put(Artifact.KEY_START_ORDER, "1");
a1.getMetadata().put("hash", "4632463464363646436");
- base.getBundles().add(1, a1);
- base.getBundles().add(1, new Artifact(ArtifactId.parse("org.apache.sling/application-bundle/2.0.0")));
- base.getBundles().add(1, new Artifact(ArtifactId.parse("org.apache.sling/another-bundle/2.1.0")));
- base.getBundles().add(2, new Artifact(ArtifactId.parse("org.apache.sling/foo-xyz/1.2.3")));
+ base.getBundles().add(a1);
+ base.getBundles().add(BundlesTest.createBundle("org.apache.sling/application-bundle/2.0.0", 1));
+ base.getBundles().add(BundlesTest.createBundle("org.apache.sling/another-bundle/2.1.0", 1));
+ base.getBundles().add(BundlesTest.createBundle("org.apache.sling/foo-xyz/1.2.3", 2));
final Configuration co1 = new Configuration("my.pid");
co1.getProperties().put("foo", 5L);
@@ -237,15 +258,16 @@ public class FeatureBuilderTest {
base.getFrameworkProperties().put("org.apache.felix.scr.directory", "launchpad/scr");
final Artifact a1 = new Artifact(ArtifactId.parse("org.apache.sling/oak-server/1.0.0"));
+ a1.getMetadata().put(Artifact.KEY_START_ORDER, "1");
a1.getMetadata().put("hash", "4632463464363646436");
- base.getBundles().add(1, a1);
- base.getBundles().add(1, new Artifact(ArtifactId.parse("org.apache.sling/application-bundle/2.0.0")));
- base.getBundles().add(1, new Artifact(ArtifactId.parse("org.apache.sling/another-bundle/2.1.0")));
- base.getBundles().add(2, new Artifact(ArtifactId.parse("org.apache.sling/foo-xyz/1.2.3")));
- base.getBundles().add(5, new Artifact(ArtifactId.parse("group/testnewversion_low/1")));
- base.getBundles().add(5, new Artifact(ArtifactId.parse("group/testnewversion_high/5")));
- base.getBundles().add(10, new Artifact(ArtifactId.parse("group/testnewstartlevel/1")));
- base.getBundles().add(10, new Artifact(ArtifactId.parse("group/testnewstartlevelandversion/2")));
+ base.getBundles().add(a1);
+ base.getBundles().add(BundlesTest.createBundle("org.apache.sling/application-bundle/2.0.0", 1));
+ base.getBundles().add(BundlesTest.createBundle("org.apache.sling/another-bundle/2.1.0", 1));
+ base.getBundles().add(BundlesTest.createBundle("org.apache.sling/foo-xyz/1.2.3", 2));
+ base.getBundles().add(BundlesTest.createBundle("group/testnewversion_low/1", 5));
+ base.getBundles().add(BundlesTest.createBundle("group/testnewversion_high/5", 5));
+ base.getBundles().add(BundlesTest.createBundle("group/testnewstartlevel/1", 10));
+ base.getBundles().add(BundlesTest.createBundle("group/testnewstartlevelandversion/2", 10));
final Configuration co1 = new Configuration("my.pid");
co1.getProperties().put("foo", 5L);
@@ -263,7 +285,7 @@ public class FeatureBuilderTest {
final Feature result = base.copy();
result.getIncludes().remove(0);
result.getFrameworkProperties().put("bar", "X");
- result.getBundles().add(3, new Artifact(ArtifactId.parse("org.apache.sling/foo-bar/4.5.6")));
+ result.getBundles().add(BundlesTest.createBundle("org.apache.sling/foo-bar/4.5.6", 3));
final Configuration co3 = new Configuration("org.apache.sling.foo");
co3.getProperties().put("prop", "value");
result.getConfigurations().add(co3);
--
To stop receiving notification emails like this one, please contact
davidb@apache.org.
[sling-org-apache-sling-feature] 11/22: Update to parent pom 33
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
davidb pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-feature.git
commit 7083fc78e6f2e84dcd3f3b5a224b9e52489d0271
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Mon Feb 26 13:24:32 2018 +0100
Update to parent pom 33
---
pom.xml | 16 ++++++++++++----
1 file changed, 12 insertions(+), 4 deletions(-)
diff --git a/pom.xml b/pom.xml
index 5cb1614..46f2116 100644
--- a/pom.xml
+++ b/pom.xml
@@ -17,7 +17,7 @@
<parent>
<groupId>org.apache.sling</groupId>
<artifactId>sling</artifactId>
- <version>32</version>
+ <version>33</version>
<relativePath />
</parent>
@@ -60,14 +60,22 @@
</build>
<dependencies>
<dependency>
- <groupId>org.apache.geronimo.specs</groupId>
- <artifactId>geronimo-json_1.0_spec</artifactId>
- <version>1.0-alpha-1</version>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.annotation.versioning</artifactId>
+ <version>1.0.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>osgi.core</artifactId>
+ <version>6.0.0</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.geronimo.specs</groupId>
+ <artifactId>geronimo-json_1.0_spec</artifactId>
+ <version>1.0-alpha-1</version>
+ <scope>provided</scope>
</dependency>
<!-- Testing -->
--
To stop receiving notification emails like this one, please contact
davidb@apache.org.
[sling-org-apache-sling-feature] 21/22: Move Feature Model Builder
into feature module.
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
davidb pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-feature.git
commit ec2a23ee7ae3d57dfbd2698957409959dc11ecbf
Author: David Bosschaert <da...@gmail.com>
AuthorDate: Wed Apr 25 10:19:53 2018 +0100
Move Feature Model Builder into feature module.
---
.../sling/feature/builder/ApplicationBuilder.java | 153 +++++++++++
.../sling/feature/builder/BuilderContext.java | 81 ++++++
.../apache/sling/feature/builder/BuilderUtil.java | 267 ++++++++++++++++++
.../sling/feature/builder/FeatureBuilder.java | 182 +++++++++++++
.../feature/builder/FeatureExtensionHandler.java | 59 ++++
.../sling/feature/builder/FeatureProvider.java | 36 +++
.../apache/sling/feature/builder/package-info.java | 23 ++
.../sling/feature/builder/BuilderUtilTest.java | 160 +++++++++++
.../sling/feature/builder/FeatureBuilderTest.java | 303 +++++++++++++++++++++
9 files changed, 1264 insertions(+)
diff --git a/src/main/java/org/apache/sling/feature/builder/ApplicationBuilder.java b/src/main/java/org/apache/sling/feature/builder/ApplicationBuilder.java
new file mode 100644
index 0000000..e41dc94
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/builder/ApplicationBuilder.java
@@ -0,0 +1,153 @@
+/*
+ * 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.sling.feature.builder;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.sling.feature.Application;
+import org.apache.sling.feature.Artifact;
+import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.Feature;
+
+/**
+ * Build an application based on features.
+ */
+public class ApplicationBuilder {
+
+ /**
+ * Assemble an application based on the provided feature Ids.
+ * The features are processed in the order they are provided.
+ *
+ * @param app The optional application to use as a base.
+ * @param context The builder context
+ * @param featureIds The feature ids
+ * @return The application
+ * throws IllegalArgumentException If context or featureIds is {@code null}
+ * throws IllegalStateException If the provided ids are invalid, or the feature can't be provided
+ */
+ public static Application assemble(final Application app,
+ final BuilderContext context,
+ final String... featureIds) {
+ if ( featureIds == null || context == null ) {
+ throw new IllegalArgumentException("Features and/or context must not be null");
+ }
+
+ final Feature[] features = new Feature[featureIds.length];
+ int index = 0;
+ for(final String id : featureIds) {
+ features[index] = context.getFeatureProvider().provide(ArtifactId.parse(id));
+ if ( features[index] == null ) {
+ throw new IllegalStateException("Unable to find included feature " + id);
+ }
+ index++;
+ }
+ return assemble(app, context, features);
+ }
+
+ /**
+ * Assemble an application based on the provided features.
+ *
+ * The features are processed in the order they are provided.
+ * If the same feature is included more than once only the feature with
+ * the highest version is used. The others are ignored.
+ *
+ * @param app The optional application to use as a base.
+ * @param context The builder context
+ * @param features The features
+ * @return The application
+ * throws IllegalArgumentException If context or featureIds is {@code null}
+ * throws IllegalStateException If a feature can't be provided
+ */
+ public static Application assemble(
+ Application app,
+ final BuilderContext context,
+ final Feature... features) {
+ if ( features == null || context == null ) {
+ throw new IllegalArgumentException("Features and/or context must not be null");
+ }
+
+ if ( app == null ) {
+ app = new Application();
+ }
+
+ // Remove duplicate features by selecting the one with the highest version
+ final List<Feature> featureList = new ArrayList<>();
+ for(final Feature f : features) {
+ Feature found = null;
+ for(final Feature s : featureList) {
+ if ( s.getId().isSame(f.getId()) ) {
+ found = s;
+ break;
+ }
+ }
+ boolean add = true;
+ // feature with different version found
+ if ( found != null ) {
+ if ( f.getId().getOSGiVersion().compareTo(found.getId().getOSGiVersion()) <= 0 ) {
+ // higher version already included
+ add = false;
+ } else {
+ // remove lower version, higher version will be added
+ featureList.remove(found);
+ }
+ }
+ if ( add ) {
+ featureList.add(f);
+ }
+ }
+
+ // assemble
+ int featureStartOrder = 5; // begin with start order a little higher than 0
+ for(final Feature f : featureList) {
+ app.getFeatureIds().add(f.getId());
+ final Feature assembled = FeatureBuilder.assemble(f, context.clone(new FeatureProvider() {
+
+ @Override
+ public Feature provide(final ArtifactId id) {
+ for(final Feature f : features) {
+ if ( f.getId().equals(id) ) {
+ return f;
+ }
+ }
+ return context.getFeatureProvider().provide(id);
+ }
+ }));
+
+ int globalStartOrder = featureStartOrder;
+ for (Artifact a : assembled.getBundles()) {
+ int so = a.getStartOrder() + featureStartOrder;
+ if (so > globalStartOrder)
+ globalStartOrder = so;
+ a.setStartOrder(so);
+ }
+ // Next feature will have a higher start order than the previous
+ featureStartOrder = globalStartOrder + 1;
+
+ merge(app, assembled);
+ }
+
+ return app;
+ }
+
+ private static void merge(final Application target, final Feature source) {
+ BuilderUtil.mergeBundles(target.getBundles(), source.getBundles(), BuilderUtil.ArtifactMerge.HIGHEST);
+ BuilderUtil.mergeConfigurations(target.getConfigurations(), source.getConfigurations());
+ BuilderUtil.mergeFrameworkProperties(target.getFrameworkProperties(), source.getFrameworkProperties());
+ BuilderUtil.mergeExtensions(target, source, BuilderUtil.ArtifactMerge.HIGHEST);
+ }
+}
diff --git a/src/main/java/org/apache/sling/feature/builder/BuilderContext.java b/src/main/java/org/apache/sling/feature/builder/BuilderContext.java
new file mode 100644
index 0000000..ca3d82a
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/builder/BuilderContext.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.sling.feature.builder;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * Builder context holds services used by {@link ApplicationBuilder}
+ * and {@link FeatureBuilder}.
+ */
+public class BuilderContext {
+
+ private final FeatureProvider provider;
+
+ private final List<FeatureExtensionHandler> featureExtensionHandlers = new CopyOnWriteArrayList<>();
+
+ /**
+ * Create a new context
+ *
+ * @param provider A provider providing the included features
+ * @throws IllegalArgumentException If feature provider is {@code null}
+ */
+ public BuilderContext(final FeatureProvider provider) {
+ if ( provider == null ) {
+ throw new IllegalArgumentException("Provider must not be null");
+ }
+ this.provider = provider;
+ }
+
+ /**
+ * Add a feature extension handler
+ * @param handler A handler
+ * @return This instance
+ */
+ public BuilderContext add(final FeatureExtensionHandler handler) {
+ featureExtensionHandlers.add(handler);
+ return this;
+ }
+
+ /**
+ * Get the feature provider.
+ * @return The feature provider
+ */
+ FeatureProvider getFeatureProvider() {
+ return this.provider;
+ }
+
+ /**
+ * Get the list of extension handlers
+ * @return The list of handlers
+ */
+ List<FeatureExtensionHandler> getFeatureExtensionHandlers() {
+ return this.featureExtensionHandlers;
+ }
+
+ /**
+ * Clone the context and replace the feature provider
+ * @param featureProvider The new feature provider
+ * @return Cloned context
+ */
+ BuilderContext clone(final FeatureProvider featureProvider) {
+ final BuilderContext ctx = new BuilderContext(featureProvider);
+ ctx.featureExtensionHandlers.addAll(featureExtensionHandlers);
+ return ctx;
+ }
+}
diff --git a/src/main/java/org/apache/sling/feature/builder/BuilderUtil.java b/src/main/java/org/apache/sling/feature/builder/BuilderUtil.java
new file mode 100644
index 0000000..ad512dd
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/builder/BuilderUtil.java
@@ -0,0 +1,267 @@
+/*
+ * 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.sling.feature.builder;
+
+import java.io.StringReader;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+
+import javax.json.Json;
+import javax.json.JsonArray;
+import javax.json.JsonObject;
+import javax.json.JsonStructure;
+import javax.json.JsonValue;
+import javax.json.JsonValue.ValueType;
+
+import org.apache.sling.feature.Application;
+import org.apache.sling.feature.Artifact;
+import org.apache.sling.feature.Bundles;
+import org.apache.sling.feature.Configuration;
+import org.apache.sling.feature.Configurations;
+import org.apache.sling.feature.Extension;
+import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.KeyValueMap;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Requirement;
+
+/**
+ * Utility methods for the builders
+ */
+class BuilderUtil {
+
+ enum ArtifactMerge {
+ LATEST,
+ HIGHEST
+ };
+
+ // bundles
+ static void mergeBundles(final Bundles target,
+ final Bundles source,
+ final ArtifactMerge artifactMergeAlg) {
+ for(final Map.Entry<Integer, List<Artifact>> entry : source.getBundlesByStartOrder().entrySet()) {
+ for(final Artifact a : entry.getValue()) {
+ // version handling - use provided algorithm
+ boolean replace = true;
+ if ( artifactMergeAlg == ArtifactMerge.HIGHEST ) {
+ final Artifact existing = target.getSame(a.getId());
+ if ( existing != null && existing.getId().getOSGiVersion().compareTo(a.getId().getOSGiVersion()) > 0 ) {
+ replace = false;
+ }
+ }
+ if ( replace ) {
+ target.removeSame(a.getId());
+ target.add(a);
+ }
+ }
+ }
+ }
+
+ // configurations - merge / override
+ static void mergeConfigurations(final Configurations target, final Configurations source) {
+ for(final Configuration cfg : source) {
+ boolean found = false;
+ for(final Configuration current : target) {
+ if ( current.compareTo(cfg) == 0 ) {
+ found = true;
+ // merge / override properties
+ final Enumeration<String> i = cfg.getProperties().keys();
+ while ( i.hasMoreElements() ) {
+ final String key = i.nextElement();
+ current.getProperties().put(key, cfg.getProperties().get(key));
+ }
+ break;
+ }
+ }
+ if ( !found ) {
+ target.add(cfg);
+ }
+ }
+ }
+
+ // framework properties (add/merge)
+ static void mergeFrameworkProperties(final KeyValueMap target, final KeyValueMap source) {
+ target.putAll(source);
+ }
+
+ // requirements (add)
+ static void mergeRequirements(final List<Requirement> target, final List<Requirement> source) {
+ for(final Requirement req : source) {
+ if ( !target.contains(req) ) {
+ target.add(req);
+ }
+ }
+ }
+
+ // capabilities (add)
+ static void mergeCapabilities(final List<Capability> target, final List<Capability> source) {
+ for(final Capability cap : source) {
+ if ( !target.contains(cap) ) {
+ target.add(cap);
+ }
+ }
+ }
+
+ // default merge for extensions
+ static void mergeExtensions(final Extension target,
+ final Extension source,
+ final ArtifactMerge artifactMergeAlg) {
+ switch ( target.getType() ) {
+ case TEXT : // simply append
+ target.setText(target.getText() + "\n" + source.getText());
+ break;
+ case JSON : final JsonStructure struct1;
+ try ( final StringReader reader = new StringReader(target.getJSON()) ) {
+ struct1 = Json.createReader(reader).read();
+ }
+ final JsonStructure struct2;
+ try ( final StringReader reader = new StringReader(source.getJSON()) ) {
+ struct2 = Json.createReader(reader).read();
+ }
+
+ if ( struct1.getValueType() != struct2.getValueType() ) {
+ throw new IllegalStateException("Found different JSON types for extension " + target.getName()
+ + " : " + struct1.getValueType() + " and " + struct2.getValueType());
+ }
+ if ( struct1.getValueType() == ValueType.ARRAY ) {
+ // array is append
+ final JsonArray a1 = (JsonArray)struct1;
+ final JsonArray a2 = (JsonArray)struct2;
+ for(final JsonValue val : a2) {
+ a1.add(val);
+ }
+ } else {
+ // object is merge
+ merge((JsonObject)struct1, (JsonObject)struct2);
+ }
+ break;
+
+ case ARTIFACTS : for(final Artifact a : source.getArtifacts()) {
+ // use artifactMergeAlg
+ boolean add = true;
+ for(final Artifact targetArtifact : target.getArtifacts()) {
+ if ( targetArtifact.getId().isSame(a.getId()) ) {
+ if ( artifactMergeAlg == ArtifactMerge.HIGHEST ) {
+ if ( targetArtifact.getId().getOSGiVersion().compareTo(a.getId().getOSGiVersion()) > 0 ) {
+ add = false;
+ } else {
+ target.getArtifacts().remove(targetArtifact);
+ }
+ } else { // latest
+
+ target.getArtifacts().remove(targetArtifact);
+ }
+ break;
+ }
+ }
+
+ if ( add ) {
+ target.getArtifacts().add(a);
+ }
+
+ }
+ break;
+ }
+ }
+
+ // extensions (add/merge)
+ static void mergeExtensions(final Feature target,
+ final Feature source,
+ final ArtifactMerge artifactMergeAlg,
+ final BuilderContext context) {
+ for(final Extension ext : source.getExtensions()) {
+ boolean found = false;
+ for(final Extension current : target.getExtensions()) {
+ if ( current.getName().equals(ext.getName()) ) {
+ found = true;
+ if ( current.getType() != ext.getType() ) {
+ throw new IllegalStateException("Found different types for extension " + current.getName()
+ + " : " + current.getType() + " and " + ext.getType());
+ }
+ boolean handled = false;
+ for(final FeatureExtensionHandler fem : context.getFeatureExtensionHandlers()) {
+ if ( fem.canMerge(current.getName()) ) {
+ fem.merge(target, source, current.getName());
+ handled = true;
+ break;
+ }
+ }
+ if ( !handled ) {
+ // default merge
+ mergeExtensions(current, ext, artifactMergeAlg);
+ }
+ }
+ }
+ if ( !found ) {
+ target.getExtensions().add(ext);
+ }
+ }
+ // post processing
+ for(final Extension ext : target.getExtensions()) {
+ for(final FeatureExtensionHandler fem : context.getFeatureExtensionHandlers()) {
+ fem.postProcess(target, ext.getName());
+ }
+ }
+ }
+
+ static void mergeExtensions(final Application target,
+ final Feature source,
+ final ArtifactMerge artifactMergeAlg) {
+ for(final Extension ext : source.getExtensions()) {
+ boolean found = false;
+ for(final Extension current : target.getExtensions()) {
+ if ( current.getName().equals(ext.getName()) ) {
+ found = true;
+ if ( current.getType() != ext.getType() ) {
+ throw new IllegalStateException("Found different types for extension " + current.getName()
+ + " : " + current.getType() + " and " + ext.getType());
+ }
+ // default merge
+ mergeExtensions(current, ext, artifactMergeAlg);
+ }
+ }
+ if ( !found ) {
+ target.getExtensions().add(ext);
+ }
+ }
+ }
+
+ private static void merge(final JsonObject obj1, final JsonObject obj2) {
+ for(final Map.Entry<String, JsonValue> entry : obj2.entrySet()) {
+ if ( !obj1.containsKey(entry.getKey()) ) {
+ obj1.put(entry.getKey(), entry.getValue());
+ } else {
+ final JsonValue oldValue = obj1.get(entry.getKey());
+ if ( oldValue.getValueType() != entry.getValue().getValueType() ) {
+ // new type wins
+ obj1.put(entry.getKey(), entry.getValue());
+ } else if ( oldValue.getValueType() == ValueType.ARRAY ) {
+ final JsonArray a1 = (JsonArray)oldValue;
+ final JsonArray a2 = (JsonArray)entry.getValue();
+ for(final JsonValue val : a2) {
+ a1.add(val);
+ }
+
+ } else if ( oldValue.getValueType() == ValueType.OBJECT ) {
+ merge((JsonObject)oldValue, (JsonObject)entry.getValue());
+ } else {
+ obj1.put(entry.getKey(), entry.getValue());
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/org/apache/sling/feature/builder/FeatureBuilder.java b/src/main/java/org/apache/sling/feature/builder/FeatureBuilder.java
new file mode 100644
index 0000000..8713ed4
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/builder/FeatureBuilder.java
@@ -0,0 +1,182 @@
+/*
+ * 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.sling.feature.builder;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.sling.feature.Artifact;
+import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.Configuration;
+import org.apache.sling.feature.Extension;
+import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.Include;
+
+public class FeatureBuilder {
+
+ /**
+ * Assemble the full feature by processing all includes.
+ *
+ * @param feature The feature to start
+ * @param context The builder context
+ * @return The assembled feature.
+ * @throws IllegalArgumentException If feature or context is {@code null}
+ * @throws IllegalStateException If an included feature can't be provided or merged.
+ */
+ public static Feature assemble(final Feature feature,
+ final BuilderContext context) {
+ if ( feature == null || context == null ) {
+ throw new IllegalArgumentException("Feature and/or context must not be null");
+ }
+ return internalAssemble(new ArrayList<>(), feature, context);
+ }
+
+ private static Feature internalAssemble(final List<String> processedFeatures,
+ final Feature feature,
+ final BuilderContext context) {
+ if ( feature.isAssembled() ) {
+ return feature;
+ }
+ if ( processedFeatures.contains(feature.getId().toMvnId()) ) {
+ throw new IllegalStateException("Recursive inclusion of " + feature.getId().toMvnId() + " via " + processedFeatures);
+ }
+ processedFeatures.add(feature.getId().toMvnId());
+
+ // we copy the feature as we set the assembled flag on the result
+ final Feature result = feature.copy();
+
+ if ( !result.getIncludes().isEmpty() ) {
+
+ final List<Include> includes = new ArrayList<>(result.getIncludes());
+
+ // clear everything in the result, will be added in the process
+ result.getBundles().clear();
+ result.getFrameworkProperties().clear();
+ result.getConfigurations().clear();
+ result.getRequirements().clear();
+ result.getCapabilities().clear();
+ result.getIncludes().clear();
+ result.getExtensions().clear();
+
+ for(final Include i : includes) {
+ final Feature f = context.getFeatureProvider().provide(i.getId());
+ if ( f == null ) {
+ throw new IllegalStateException("Unable to find included feature " + i.getId());
+ }
+ final Feature af = internalAssemble(processedFeatures, f, context);
+
+ // process include instructions
+ include(af, i);
+
+ // and now merge
+ merge(result, af, context);
+ }
+ merge(result, feature, context);
+ }
+ processedFeatures.remove(feature.getId().toMvnId());
+
+ result.setAssembled(true);
+ return result;
+ }
+
+ private static void merge(final Feature target,
+ final Feature source,
+ final BuilderContext context) {
+ BuilderUtil.mergeBundles(target.getBundles(), source.getBundles(), BuilderUtil.ArtifactMerge.LATEST);
+ BuilderUtil.mergeConfigurations(target.getConfigurations(), source.getConfigurations());
+ BuilderUtil.mergeFrameworkProperties(target.getFrameworkProperties(), source.getFrameworkProperties());
+ BuilderUtil.mergeRequirements(target.getRequirements(), source.getRequirements());
+ BuilderUtil.mergeCapabilities(target.getCapabilities(), source.getCapabilities());
+ BuilderUtil.mergeExtensions(target,
+ source,
+ BuilderUtil.ArtifactMerge.LATEST,
+ context);
+ }
+
+ private static void include(final Feature base, final Include i) {
+ // process removals
+ // bundles
+ for(final ArtifactId a : i.getBundleRemovals()) {
+ base.getBundles().removeExact(a);
+ final Iterator<Configuration> iter = base.getConfigurations().iterator();
+ while ( iter.hasNext() ) {
+ final Configuration cfg = iter.next();
+ final String bundleId = (String)cfg.getProperties().get(Configuration.PROP_ARTIFACT);
+ if ( a.toMvnId().equals(bundleId) ) {
+ iter.remove();
+ }
+ }
+ }
+ // configurations
+ for(final String c : i.getConfigurationRemovals()) {
+ final int attrPos = c.indexOf('@');
+ final String val = (attrPos == -1 ? c : c.substring(0, attrPos));
+ final String attr = (attrPos == -1 ? null : c.substring(attrPos + 1));
+
+ final int sepPos = val.indexOf('~');
+ Configuration found = null;
+ if ( sepPos == -1 ) {
+ found = base.getConfigurations().getConfiguration(val);
+
+ } else {
+ final String factoryPid = val.substring(0, sepPos);
+ final String name = val.substring(sepPos + 1);
+
+ found = base.getConfigurations().getFactoryConfiguration(factoryPid, name);
+ }
+ if ( found != null ) {
+ if ( attr == null ) {
+ base.getConfigurations().remove(found);
+ } else {
+ found.getProperties().remove(attr);
+ }
+ }
+ }
+
+ // framework properties
+ for(final String p : i.getFrameworkPropertiesRemovals()) {
+ base.getFrameworkProperties().remove(p);
+ }
+
+ // extensions
+ for(final String name : i.getExtensionRemovals()) {
+ for(final Extension ext : base.getExtensions()) {
+ if ( ext.getName().equals(name) ) {
+ base.getExtensions().remove(ext);
+ break;
+ }
+ }
+ }
+ for(final Map.Entry<String, List<ArtifactId>> entry : i.getArtifactExtensionRemovals().entrySet()) {
+ for(final Extension ext : base.getExtensions()) {
+ if ( ext.getName().equals(entry.getKey()) ) {
+ for(final ArtifactId id : entry.getValue() ) {
+ for(final Artifact a : ext.getArtifacts()) {
+ if ( a.getId().equals(id) ) {
+ ext.getArtifacts().remove(a);
+ break;
+ }
+ }
+ }
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/org/apache/sling/feature/builder/FeatureExtensionHandler.java b/src/main/java/org/apache/sling/feature/builder/FeatureExtensionHandler.java
new file mode 100644
index 0000000..2907f9b
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/builder/FeatureExtensionHandler.java
@@ -0,0 +1,59 @@
+/*
+ * 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.sling.feature.builder;
+
+import org.apache.sling.feature.Feature;
+import org.osgi.annotation.versioning.ConsumerType;
+
+/**
+ * A feature extension handler can merge a feature of a particular type
+ * and also post process the final assembled feature.
+ */
+@ConsumerType
+public interface FeatureExtensionHandler {
+
+ /**
+ * Checks whether this merger can merge extensions with that name
+ * @param extensionName The extension name
+ * @return {@code true} if merger can handle this
+ */
+ boolean canMerge(String extensionName);
+
+ /**
+ * Merge the source extension into the target extension.
+ *
+ * The caller of this method already ensured that both
+ * extensions share the same name and type and that
+ * {@link #canMerge(String)} returned {@code true}.
+ *
+ * @param target The target feature
+ * @param source The source feature
+ * @param extensionName The extension name
+ * @throws IllegalStateException If the extensions can't be merged
+ */
+ void merge(Feature target, Feature source, String extensionName);
+
+ /**
+ * Post process the feature with respect to the extension.
+ * Post processing is invoked after all extensions have been merged.
+ * This method is called regardless whether {@link #canMerge(String)} returned {@code true} or not.
+ * @param feature The feature
+ * @param extensionName The extension name
+ * @throws IllegalStateException If post processing failed
+ */
+ void postProcess(Feature feature, String extensionName);
+}
diff --git a/src/main/java/org/apache/sling/feature/builder/FeatureProvider.java b/src/main/java/org/apache/sling/feature/builder/FeatureProvider.java
new file mode 100644
index 0000000..ace0005
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/builder/FeatureProvider.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.
+ */
+package org.apache.sling.feature.builder;
+
+import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.Feature;
+import org.osgi.annotation.versioning.ConsumerType;
+
+/**
+ * The feature provider is used to find features while assembling using either
+ * {@link ApplicationBuilder} or {@link FeatureBuilder}.
+ */
+@ConsumerType
+public interface FeatureProvider {
+
+ /**
+ * Provide the feature with the given id.
+ * @param id The feature id
+ * @return The feature or {@code null}
+ */
+ Feature provide(ArtifactId id);
+}
diff --git a/src/main/java/org/apache/sling/feature/builder/package-info.java b/src/main/java/org/apache/sling/feature/builder/package-info.java
new file mode 100644
index 0000000..4ecbe60
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/builder/package-info.java
@@ -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.
+ */
+
+@org.osgi.annotation.versioning.Version("1.0.0")
+package org.apache.sling.feature.builder;
+
+
diff --git a/src/test/java/org/apache/sling/feature/builder/BuilderUtilTest.java b/src/test/java/org/apache/sling/feature/builder/BuilderUtilTest.java
new file mode 100644
index 0000000..b5d3570
--- /dev/null
+++ b/src/test/java/org/apache/sling/feature/builder/BuilderUtilTest.java
@@ -0,0 +1,160 @@
+/*
+ * 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.sling.feature.builder;
+
+import org.apache.sling.feature.Artifact;
+import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.Bundles;
+import org.apache.sling.feature.builder.BuilderUtil;
+import org.apache.sling.feature.builder.BuilderUtil.ArtifactMerge;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+public class BuilderUtilTest {
+
+ private List<Map.Entry<Integer, Artifact>> getBundles(final Bundles f) {
+ final List<Map.Entry<Integer, Artifact>> result = new ArrayList<>();
+ for(final Map.Entry<Integer, List<Artifact>> entry : f.getBundlesByStartOrder().entrySet()) {
+ for(final Artifact artifact : entry.getValue()) {
+ result.add(new Map.Entry<Integer, Artifact>() {
+
+ @Override
+ public Integer getKey() {
+ return entry.getKey();
+ }
+
+ @Override
+ public Artifact getValue() {
+ return artifact;
+ }
+
+ @Override
+ public Artifact setValue(Artifact value) {
+ return null;
+ }
+ });
+ }
+ }
+
+ return result;
+ }
+
+ private void assertContains(final List<Map.Entry<Integer, Artifact>> bundles,
+ final int level, final ArtifactId id) {
+ for(final Map.Entry<Integer, Artifact> entry : bundles) {
+ if ( entry.getKey().intValue() == level
+ && entry.getValue().getId().equals(id) ) {
+ return;
+ }
+ }
+ fail(id.toMvnId());
+ }
+
+ @Test public void testMergeBundlesWithAlgHighest() {
+ final Bundles target = new Bundles();
+
+ target.add(createBundle("g/a/1.0", 1));
+ target.add(createBundle("g/b/2.0", 2));
+ target.add(createBundle("g/c/2.5", 3));
+
+ final Bundles source = new Bundles();
+ source.add(createBundle("g/a/1.1", 1));
+ source.add(createBundle("g/b/1.9", 2));
+ source.add(createBundle("g/c/2.5", 3));
+
+ BuilderUtil.mergeBundles(target, source, ArtifactMerge.HIGHEST);
+
+ final List<Map.Entry<Integer, Artifact>> result = getBundles(target);
+ assertEquals(3, result.size());
+ assertContains(result, 1, ArtifactId.parse("g/a/1.1"));
+ assertContains(result, 2, ArtifactId.parse("g/b/2.0"));
+ assertContains(result, 3, ArtifactId.parse("g/c/2.5"));
+ }
+
+ @Test public void testMergeBundlesWithAlgLatest() {
+ final Bundles target = new Bundles();
+
+ target.add(createBundle("g/a/1.0", 1));
+ target.add(createBundle("g/b/2.0", 2));
+ target.add(createBundle("g/c/2.5", 3));
+
+ final Bundles source = new Bundles();
+ source.add(createBundle("g/a/1.1", 1));
+ source.add(createBundle("g/b/1.9", 2));
+ source.add(createBundle("g/c/2.5", 3));
+
+ BuilderUtil.mergeBundles(target, source, ArtifactMerge.LATEST);
+
+ final List<Map.Entry<Integer, Artifact>> result = getBundles(target);
+ assertEquals(3, result.size());
+ assertContains(result, 1, ArtifactId.parse("g/a/1.1"));
+ assertContains(result, 2, ArtifactId.parse("g/b/1.9"));
+ assertContains(result, 3, ArtifactId.parse("g/c/2.5"));
+ }
+
+ @Test public void testMergeBundlesDifferentStartlevel() {
+ final Bundles target = new Bundles();
+
+ target.add(createBundle("g/a/1.0", 1));
+
+ final Bundles source = new Bundles();
+ source.add(createBundle("g/a/1.1", 2));
+
+ BuilderUtil.mergeBundles(target, source, ArtifactMerge.LATEST);
+
+ final List<Map.Entry<Integer, Artifact>> result = getBundles(target);
+ assertEquals(1, result.size());
+ assertContains(result, 2, ArtifactId.parse("g/a/1.1"));
+ }
+
+ @Test public void testMergeBundles() {
+ final Bundles target = new Bundles();
+
+ target.add(createBundle("g/a/1.0", 1));
+ target.add(createBundle("g/b/2.0", 2));
+ target.add(createBundle("g/c/2.5", 3));
+
+ final Bundles source = new Bundles();
+ source.add(createBundle("g/d/1.1", 1));
+ source.add(createBundle("g/e/1.9", 2));
+ source.add(createBundle("g/f/2.5", 3));
+
+ BuilderUtil.mergeBundles(target, source, ArtifactMerge.LATEST);
+
+ final List<Map.Entry<Integer, Artifact>> result = getBundles(target);
+ assertEquals(6, result.size());
+ assertContains(result, 1, ArtifactId.parse("g/a/1.0"));
+ assertContains(result, 2, ArtifactId.parse("g/b/2.0"));
+ assertContains(result, 3, ArtifactId.parse("g/c/2.5"));
+ assertContains(result, 1, ArtifactId.parse("g/d/1.1"));
+ assertContains(result, 2, ArtifactId.parse("g/e/1.9"));
+ assertContains(result, 3, ArtifactId.parse("g/f/2.5"));
+ }
+
+ public static Artifact createBundle(final String id, final int startOrder) {
+ final Artifact a = new Artifact(ArtifactId.parse(id));
+ a.getMetadata().put(Artifact.KEY_START_ORDER, String.valueOf(startOrder));
+
+ return a;
+ }
+}
diff --git a/src/test/java/org/apache/sling/feature/builder/FeatureBuilderTest.java b/src/test/java/org/apache/sling/feature/builder/FeatureBuilderTest.java
new file mode 100644
index 0000000..7b81eab
--- /dev/null
+++ b/src/test/java/org/apache/sling/feature/builder/FeatureBuilderTest.java
@@ -0,0 +1,303 @@
+/*
+ * 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.sling.feature.builder;
+
+import org.apache.felix.utils.resource.CapabilityImpl;
+import org.apache.felix.utils.resource.RequirementImpl;
+import org.apache.sling.feature.Artifact;
+import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.Configuration;
+import org.apache.sling.feature.Extension;
+import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.Include;
+import org.junit.Test;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Requirement;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+public class FeatureBuilderTest {
+
+ private static final Map<String, Feature> FEATURES = new HashMap<>();
+
+ static {
+ final Feature f1 = new Feature(ArtifactId.parse("g/a/1"));
+
+ f1.getFrameworkProperties().put("foo", "2");
+ f1.getFrameworkProperties().put("bar", "X");
+
+ f1.getBundles().add(BuilderUtilTest.createBundle("org.apache.sling/foo-bar/4.5.6", 3));
+ f1.getBundles().add(BuilderUtilTest.createBundle("group/testnewversion_low/2", 5));
+ f1.getBundles().add(BuilderUtilTest.createBundle("group/testnewversion_high/2", 5));
+ f1.getBundles().add(BuilderUtilTest.createBundle("group/testnewstartlevel/1", 5));
+ f1.getBundles().add(BuilderUtilTest.createBundle("group/testnewstartlevelandversion/1", 5));
+
+ final Configuration c1 = new Configuration("org.apache.sling.foo");
+ c1.getProperties().put("prop", "value");
+ f1.getConfigurations().add(c1);
+
+ FEATURES.put(f1.getId().toMvnId(), f1);
+ }
+
+ private final FeatureProvider provider = new FeatureProvider() {
+
+ @Override
+ public Feature provide(final ArtifactId id) {
+ return FEATURES.get(id.getGroupId() + ":" + id.getArtifactId() + ":" + id.getVersion());
+ }
+ };
+
+ private List<Map.Entry<Integer, Artifact>> getBundles(final Feature f) {
+ final List<Map.Entry<Integer, Artifact>> result = new ArrayList<>();
+ for(final Map.Entry<Integer, List<Artifact>> entry : f.getBundles().getBundlesByStartOrder().entrySet()) {
+ for(final Artifact artifact : entry.getValue()) {
+ result.add(new Map.Entry<Integer, Artifact>() {
+
+ @Override
+ public Integer getKey() {
+ return entry.getKey();
+ }
+
+ @Override
+ public Artifact getValue() {
+ return artifact;
+ }
+
+ @Override
+ public Artifact setValue(Artifact value) {
+ return null;
+ }
+ });
+ }
+ }
+
+ return result;
+ }
+
+ private void equals(final Feature expected, final Feature actuals) {
+ assertFalse(expected.isAssembled());
+ assertTrue(actuals.isAssembled());
+
+ assertEquals(expected.getId(), actuals.getId());
+ assertEquals(expected.getTitle(), actuals.getTitle());
+ assertEquals(expected.getDescription(), actuals.getDescription());
+ assertEquals(expected.getVendor(), actuals.getVendor());
+ assertEquals(expected.getLicense(), actuals.getLicense());
+
+ // bundles
+ final List<Map.Entry<Integer, Artifact>> expectedBundles = getBundles(expected);
+ final List<Map.Entry<Integer, Artifact>> actualsBundles = getBundles(actuals);
+ assertEquals(expectedBundles.size(), actualsBundles.size());
+ for(final Map.Entry<Integer, Artifact> entry : expectedBundles) {
+ boolean found = false;
+ for(final Map.Entry<Integer, Artifact> inner : actualsBundles) {
+ if ( inner.getValue().getId().equals(entry.getValue().getId()) ) {
+ found = true;
+ assertEquals("Startlevel of bundle " + entry.getValue(), entry.getKey(), inner.getKey());
+ assertEquals("Metadata of bundle " + entry.getValue(), entry.getValue().getMetadata(), inner.getValue().getMetadata());
+ break;
+ }
+ }
+ assertTrue("Bundle " + entry.getValue() + " in level " + entry.getKey(), found);
+ }
+
+ // configurations
+ assertEquals(expected.getConfigurations().size(), actuals.getConfigurations().size());
+ for(final Configuration cfg : expected.getConfigurations()) {
+ final Configuration found = (cfg.isFactoryConfiguration() ? actuals.getConfigurations().getFactoryConfiguration(cfg.getFactoryPid(), cfg.getName())
+ : actuals.getConfigurations().getConfiguration(cfg.getPid()));
+ assertNotNull("Configuration " + cfg, found);
+ assertEquals("Configuration " + cfg, cfg.getProperties(), found.getProperties());
+ }
+
+ // frameworkProperties
+ assertEquals(expected.getFrameworkProperties(), actuals.getFrameworkProperties());
+
+ // requirements
+ assertEquals(expected.getRequirements().size(), actuals.getRequirements().size());
+ for(final Requirement r : expected.getRequirements()) {
+ boolean found = false;
+ for(final Requirement i : actuals.getRequirements()) {
+ if ( r.equals(i) ) {
+ found = true;
+ break;
+ }
+ }
+ assertTrue(found);
+ }
+
+ // capabilities
+ assertEquals(expected.getCapabilities().size(), actuals.getCapabilities().size());
+ for(final Capability r : expected.getCapabilities()) {
+ boolean found = false;
+ for(final Capability i : actuals.getCapabilities()) {
+ if ( r.equals(i) ) {
+ found = true;
+ break;
+ }
+ }
+ assertTrue(found);
+ }
+
+ // extensions
+ assertEquals(expected.getExtensions().size(), actuals.getExtensions().size());
+ for(final Extension ext : expected.getExtensions()) {
+ final Extension inner = actuals.getExtensions().getByName(ext.getName());
+ assertNotNull(inner);
+ assertEquals(ext.getType(), inner.getType());
+ switch ( ext.getType()) {
+ case JSON : assertEquals(ext.getJSON(), inner.getJSON());
+ break;
+ case TEXT : assertEquals(ext.getText(), inner.getText());
+ break;
+ case ARTIFACTS : assertEquals(ext.getArtifacts().size(), inner.getArtifacts().size());
+ for(final Artifact art : ext.getArtifacts()) {
+ boolean found = false;
+ for(final Artifact i : inner.getArtifacts()) {
+ if ( art.getId().equals(i.getId()) ) {
+ found = true;
+ assertEquals(art.getMetadata(), i.getMetadata());
+ break;
+ }
+ }
+ assertTrue(found);
+ }
+ }
+ }
+
+ // includes should always be empty
+ assertTrue(actuals.getIncludes().isEmpty());
+ }
+
+ @Test public void testNoIncludesNoUpgrade() throws Exception {
+ final Feature base = new Feature(ArtifactId.parse("org.apache.sling/test-feature/1.1"));
+
+ final Requirement r1 = new RequirementImpl(null, "osgi.contract",
+ Collections.singletonMap("filter", "(&(osgi.contract=JavaServlet)(version=3.1))"), null);
+ base.getRequirements().add(r1);
+
+ Map<String, Object> attrs = new HashMap<>();
+ attrs.put("osgi.implementation", "osgi.http");
+ attrs.put("version:Version", "1.1");
+ final Capability c1 = new CapabilityImpl(null, "osgi.implementation",
+ Collections.singletonMap("uses", "javax.servlet,javax.servlet.http,org.osgi.service.http.context,org.osgi.service.http.whiteboard"),
+ attrs);
+ base.getCapabilities().add(c1);
+ final Capability c2 = new CapabilityImpl(null, "osgi.service",
+ Collections.singletonMap("uses", "org.osgi.service.http.runtime,org.osgi.service.http.runtime.dto"),
+ Collections.singletonMap("objectClass:List<String>", "org.osgi.service.http.runtime.HttpServiceRuntime"));
+ base.getCapabilities().add(c2);
+
+ base.getFrameworkProperties().put("foo", "1");
+ base.getFrameworkProperties().put("brave", "something");
+ base.getFrameworkProperties().put("org.apache.felix.scr.directory", "launchpad/scr");
+
+ final Artifact a1 = new Artifact(ArtifactId.parse("org.apache.sling/oak-server/1.0.0"));
+ a1.getMetadata().put(Artifact.KEY_START_ORDER, "1");
+ a1.getMetadata().put("hash", "4632463464363646436");
+ base.getBundles().add(a1);
+ base.getBundles().add(BuilderUtilTest.createBundle("org.apache.sling/application-bundle/2.0.0", 1));
+ base.getBundles().add(BuilderUtilTest.createBundle("org.apache.sling/another-bundle/2.1.0", 1));
+ base.getBundles().add(BuilderUtilTest.createBundle("org.apache.sling/foo-xyz/1.2.3", 2));
+
+ final Configuration co1 = new Configuration("my.pid");
+ co1.getProperties().put("foo", 5L);
+ co1.getProperties().put("bar", "test");
+ co1.getProperties().put("number", 7);
+ base.getConfigurations().add(co1);
+
+ final Configuration co2 = new Configuration("my.factory.pid", "name");
+ co2.getProperties().put("a.value", "yeah");
+ base.getConfigurations().add(co2);
+
+ assertFalse(base.isAssembled());
+
+ final Feature assembled = FeatureBuilder.assemble(base, new BuilderContext(provider));
+
+ equals(base, assembled);
+ }
+
+ @Test public void testSingleInclude() throws Exception {
+ final Feature base = new Feature(ArtifactId.parse("org.apache.sling/test-feature/1.1"));
+ final Include i1 = new Include(ArtifactId.parse("g/a/1"));
+ base.getIncludes().add(i1);
+
+ final Requirement r1 = new RequirementImpl(null, "osgi.contract",
+ Collections.singletonMap("filter", "(&(osgi.contract=JavaServlet)(version=3.1))"), null);
+ base.getRequirements().add(r1);
+
+ Map<String, Object> attrs = new HashMap<>();
+ attrs.put("osgi.implementation", "osgi.http");
+ attrs.put("version:Version", "1.1");
+ final Capability c1 = new CapabilityImpl(null, "osgi.implementation",
+ Collections.singletonMap("uses", "javax.servlet,javax.servlet.http,org.osgi.service.http.context,org.osgi.service.http.whiteboard"),
+ attrs);
+ base.getCapabilities().add(c1);
+
+ base.getFrameworkProperties().put("foo", "1");
+ base.getFrameworkProperties().put("brave", "something");
+ base.getFrameworkProperties().put("org.apache.felix.scr.directory", "launchpad/scr");
+
+ final Artifact a1 = new Artifact(ArtifactId.parse("org.apache.sling/oak-server/1.0.0"));
+ a1.getMetadata().put(Artifact.KEY_START_ORDER, "1");
+ a1.getMetadata().put("hash", "4632463464363646436");
+ base.getBundles().add(a1);
+ base.getBundles().add(BuilderUtilTest.createBundle("org.apache.sling/application-bundle/2.0.0", 1));
+ base.getBundles().add(BuilderUtilTest.createBundle("org.apache.sling/another-bundle/2.1.0", 1));
+ base.getBundles().add(BuilderUtilTest.createBundle("org.apache.sling/foo-xyz/1.2.3", 2));
+ base.getBundles().add(BuilderUtilTest.createBundle("group/testnewversion_low/1", 5));
+ base.getBundles().add(BuilderUtilTest.createBundle("group/testnewversion_high/5", 5));
+ base.getBundles().add(BuilderUtilTest.createBundle("group/testnewstartlevel/1", 10));
+ base.getBundles().add(BuilderUtilTest.createBundle("group/testnewstartlevelandversion/2", 10));
+
+ final Configuration co1 = new Configuration("my.pid");
+ co1.getProperties().put("foo", 5L);
+ co1.getProperties().put("bar", "test");
+ co1.getProperties().put("number", 7);
+ base.getConfigurations().add(co1);
+
+ final Configuration co2 = new Configuration("my.factory.pid", "name");
+ co2.getProperties().put("a.value", "yeah");
+ base.getConfigurations().add(co2);
+
+ assertFalse(base.isAssembled());
+
+ // create the expected result
+ final Feature result = base.copy();
+ result.getIncludes().remove(0);
+ result.getFrameworkProperties().put("bar", "X");
+ result.getBundles().add(BuilderUtilTest.createBundle("org.apache.sling/foo-bar/4.5.6", 3));
+ final Configuration co3 = new Configuration("org.apache.sling.foo");
+ co3.getProperties().put("prop", "value");
+ result.getConfigurations().add(co3);
+
+ // assemble
+ final Feature assembled = FeatureBuilder.assemble(base, new BuilderContext(provider));
+
+ // and test
+ equals(result, assembled);
+ }
+}
--
To stop receiving notification emails like this one, please contact
davidb@apache.org.
[sling-org-apache-sling-feature] 08/22: Change from start level to
start order
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
davidb pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-feature.git
commit a6468fc42e65ec5fd9d3773470f5dcbecac69c57
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Thu Feb 1 14:25:30 2018 +0100
Change from start level to start order
---
src/main/java/org/apache/sling/feature/Bundles.java | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/src/main/java/org/apache/sling/feature/Bundles.java b/src/main/java/org/apache/sling/feature/Bundles.java
index 12b5b52..7bf3f92 100644
--- a/src/main/java/org/apache/sling/feature/Bundles.java
+++ b/src/main/java/org/apache/sling/feature/Bundles.java
@@ -166,6 +166,14 @@ public class Bundles implements Iterable<Artifact> {
return Collections.unmodifiableList(this.bundles).iterator();
}
+ /**
+ * Check whether this container has bundles
+ * @return {@code true} if there are no bundles.
+ */
+ public boolean isEmpty() {
+ return this.bundles.isEmpty();
+ }
+
@Override
public String toString() {
return "Bundles " + this.bundles;
--
To stop receiving notification emails like this one, please contact
davidb@apache.org.