Author: cziegeler
Date: Tue May 30 13:26:08 2017
New Revision: 1796861

URL: http://svn.apache.org/viewvc?rev=1796861&view=rev
Log:
Start implementing extensions

Modified:
    
sling/whiteboard/cziegeler/feature/src/main/java/org/apache/sling/feature/ArtifactId.java
    
sling/whiteboard/cziegeler/feature/src/main/java/org/apache/sling/feature/Bundles.java
    
sling/whiteboard/cziegeler/feature/src/main/java/org/apache/sling/feature/Extension.java
    
sling/whiteboard/cziegeler/feature/src/main/java/org/apache/sling/feature/Include.java
    
sling/whiteboard/cziegeler/feature/src/main/java/org/apache/sling/feature/json/FeatureJSONReader.java
    
sling/whiteboard/cziegeler/feature/src/main/java/org/apache/sling/feature/json/FeatureJSONWriter.java
    
sling/whiteboard/cziegeler/feature/src/main/java/org/apache/sling/feature/json/JSONConstants.java
    
sling/whiteboard/cziegeler/feature/src/main/java/org/apache/sling/feature/process/ApplicationBuilder.java
    
sling/whiteboard/cziegeler/feature/src/main/java/org/apache/sling/feature/process/FeatureBuilder.java

Modified: 
sling/whiteboard/cziegeler/feature/src/main/java/org/apache/sling/feature/ArtifactId.java
URL: 
http://svn.apache.org/viewvc/sling/whiteboard/cziegeler/feature/src/main/java/org/apache/sling/feature/ArtifactId.java?rev=1796861&r1=1796860&r2=1796861&view=diff
==============================================================================
--- 
sling/whiteboard/cziegeler/feature/src/main/java/org/apache/sling/feature/ArtifactId.java
 (original)
+++ 
sling/whiteboard/cziegeler/feature/src/main/java/org/apache/sling/feature/ArtifactId.java
 Tue May 30 13:26:08 2017
@@ -75,7 +75,7 @@ public class ArtifactId implements Compa
 
     /**
      * Create a new artifact from a maven url,
-     * 'mvn:' [ repository-url '!' ] group-id '/' artifact-id [ '/' [version] 
[ '/' [type] [ '/' classifier ] ] ] ]
+     * 'mvn:' group-id '/' artifact-id [ '/' [version] [ '/' [type] [ '/' 
classifier ] ] ] ]
      * @param url The url
      * @return A new artifact
      * @throws IllegalArgumentException If the url is not valid
@@ -88,19 +88,17 @@ public class ArtifactId implements Compa
     }
 
     /**
-     * Create a new artifact from a maven url,
-     * [ repository-url '!' ] group-id '/' artifact-id [ '/' [version] [ '/' 
[type] [ '/' classifier ] ] ] ]
+     * Create a new artifact from a maven id,
+     * group-id '/' artifact-id [ '/' [version] [ '/' [type] [ '/' classifier 
] ] ] ]
      * @param content The id
      * @return A new artifact
      * @throws IllegalArgumentException If the id is not valid
      */
-    public static ArtifactId fromMvnId(final String content) {
-        // ignore repository url
-        int pos = content.indexOf('!');
-        if ( pos != -1 ) {
+    public static ArtifactId fromMvnId(final String coordinates) {
+        // throw if repository url is included
+        if ( coordinates.indexOf('!') != -1 ) {
             throw new IllegalArgumentException("Repository url is not 
supported for Maven artifacts at the moment.");
         }
-        final String coordinates = (pos == -1 ? content : 
content.substring(pos + 1));
         String gId = null;
         String aId = null;
         String version = null;
@@ -109,7 +107,7 @@ public class ArtifactId implements Compa
         int part = 0;
         String value = coordinates;
         while ( value != null ) {
-            pos = value.indexOf('/');
+            final int pos = value.indexOf('/');
             final String current;
             if ( pos == -1 ) {
                 current = value;
@@ -149,10 +147,20 @@ public class ArtifactId implements Compa
         return toId(new StringBuilder("mvn:"));
     }
 
+    /**
+     * Return a mvn id
+     * @return The mvn id
+     * #see {@link #fromMvnId(String)}
+     */
     public String toMvnId() {
         return toId(new StringBuilder());
     }
 
+    /**
+     * Internal method to create the mvn url/id
+     * @param sb A string builder
+     * @return The resulting id
+     */
     private String toId(final StringBuilder sb) {
         sb.append(this.groupId);
         sb.append('/');
@@ -187,6 +195,22 @@ public class ArtifactId implements Compa
     }
 
     /**
+     * Return the optional classifier.
+     * @return The classifier or null.
+     */
+    public String getClassifier() {
+        return classifier;
+    }
+
+    /**
+     * Return the type.
+     * @return The type.
+     */
+    public String getType() {
+        return type;
+    }
+
+    /**
      * Return the version.
      * @return The version.
      */
@@ -194,6 +218,31 @@ public class ArtifactId implements Compa
         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
+     * @throws IllegalArgumentException If the version is no valid OSGi version
+     */
     public Version getOSGiVersion() {
         final int qualifier = this.version.indexOf('-');
         if ( qualifier == -1 ) {
@@ -216,24 +265,8 @@ public class ArtifactId implements Compa
     }
 
     /**
-     * Return the optional classifier.
-     * @return The classifier or null.
-     */
-    public String getClassifier() {
-        return classifier;
-    }
-
-    /**
-     * Return the type.
-     * @return The type.
-     */
-    public String getType() {
-        return type;
-    }
-
-    /**
      * Create a Maven like relative repository path.
-     * @return A relative repository path.
+     * @return A relative repository path. The path does not start with a 
slash.
      */
     public String toMvnPath() {
         final StringBuilder sb = new StringBuilder();

Modified: 
sling/whiteboard/cziegeler/feature/src/main/java/org/apache/sling/feature/Bundles.java
URL: 
http://svn.apache.org/viewvc/sling/whiteboard/cziegeler/feature/src/main/java/org/apache/sling/feature/Bundles.java?rev=1796861&r1=1796860&r2=1796861&view=diff
==============================================================================
--- 
sling/whiteboard/cziegeler/feature/src/main/java/org/apache/sling/feature/Bundles.java
 (original)
+++ 
sling/whiteboard/cziegeler/feature/src/main/java/org/apache/sling/feature/Bundles.java
 Tue May 30 13:26:08 2017
@@ -53,7 +53,7 @@ public class Bundles {
         list.add(bundle);
     }
 
-    public boolean remove(final ArtifactId id) {
+    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)) {
@@ -68,10 +68,52 @@ public class Bundles {
         return false;
     }
 
+    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;
+    }
+
     public void clear() {
         this.startLevelMap.clear();
     }
 
+    public Map.Entry<Integer, Artifact> getSame(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)) {
+                    return new Map.Entry<Integer, Artifact>() {
+
+                        @Override
+                        public Integer getKey() {
+                            return entry.getKey();
+                        }
+
+                        @Override
+                        public Artifact getValue() {
+                            return artifact;
+                        }
+
+                        @Override
+                        public Artifact setValue(final Artifact value) {
+                            throw new IllegalStateException();
+                        }
+                    };
+                }
+            }
+        }
+        return null;
+    }
+
     public boolean contains(final ArtifactId id) {
         for(final Map.Entry<Integer, List<Artifact>> entry : 
this.startLevelMap.entrySet()) {
             for(final Artifact artifact : entry.getValue()) {

Modified: 
sling/whiteboard/cziegeler/feature/src/main/java/org/apache/sling/feature/Extension.java
URL: 
http://svn.apache.org/viewvc/sling/whiteboard/cziegeler/feature/src/main/java/org/apache/sling/feature/Extension.java?rev=1796861&r1=1796860&r2=1796861&view=diff
==============================================================================
--- 
sling/whiteboard/cziegeler/feature/src/main/java/org/apache/sling/feature/Extension.java
 (original)
+++ 
sling/whiteboard/cziegeler/feature/src/main/java/org/apache/sling/feature/Extension.java
 Tue May 30 13:26:08 2017
@@ -95,4 +95,25 @@ public class Extension {
     public boolean isOptional() {
         return !this.isRequired();
     }
+
+    @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 + "]";
+    }
 }

Modified: 
sling/whiteboard/cziegeler/feature/src/main/java/org/apache/sling/feature/Include.java
URL: 
http://svn.apache.org/viewvc/sling/whiteboard/cziegeler/feature/src/main/java/org/apache/sling/feature/Include.java?rev=1796861&r1=1796860&r2=1796861&view=diff
==============================================================================
--- 
sling/whiteboard/cziegeler/feature/src/main/java/org/apache/sling/feature/Include.java
 (original)
+++ 
sling/whiteboard/cziegeler/feature/src/main/java/org/apache/sling/feature/Include.java
 Tue May 30 13:26:08 2017
@@ -34,9 +34,9 @@ public class Include implements Comparab
 
     private final List<String> frameworkPropertiesRemovals = new ArrayList<>();
 
-    private final List<String> textExtensionRemovals = new ArrayList<>();
+    private final List<String> extensionRemovals = new ArrayList<>();
 
-    private final Map<String, List<Artifact>> artifactExtensionRemovals = new 
HashMap<>();
+    private final Map<String, List<ArtifactId>> artifactExtensionRemovals = 
new HashMap<>();
 
     /**
      * Construct a new Include.
@@ -70,11 +70,11 @@ public class Include implements Comparab
         return frameworkPropertiesRemovals;
     }
 
-    public List<String> getTextExtensionRemovals() {
-        return textExtensionRemovals;
+    public List<String> getExtensionRemovals() {
+        return extensionRemovals;
     }
 
-    public Map<String, List<Artifact>> getArtifactExtensionRemovals() {
+    public Map<String, List<ArtifactId>> getArtifactExtensionRemovals() {
         return artifactExtensionRemovals;
     }
 

Modified: 
sling/whiteboard/cziegeler/feature/src/main/java/org/apache/sling/feature/json/FeatureJSONReader.java
URL: 
http://svn.apache.org/viewvc/sling/whiteboard/cziegeler/feature/src/main/java/org/apache/sling/feature/json/FeatureJSONReader.java?rev=1796861&r1=1796860&r2=1796861&view=diff
==============================================================================
--- 
sling/whiteboard/cziegeler/feature/src/main/java/org/apache/sling/feature/json/FeatureJSONReader.java
 (original)
+++ 
sling/whiteboard/cziegeler/feature/src/main/java/org/apache/sling/feature/json/FeatureJSONReader.java
 Tue May 30 13:26:08 2017
@@ -29,7 +29,11 @@ import java.util.Map;
 import java.util.Set;
 
 import javax.json.Json;
+import javax.json.JsonArrayBuilder;
 import javax.json.JsonObject;
+import javax.json.JsonObjectBuilder;
+import javax.json.JsonStructure;
+import javax.json.JsonWriter;
 
 import org.apache.felix.configurator.impl.conversion.TypeConverter;
 import org.apache.felix.configurator.impl.json.JSMin;
@@ -131,7 +135,6 @@ public class FeatureJSONReader {
 
         this.readExtensions(map);
 
-        // check duplicates in configurations, includes
         return feature;
     }
 
@@ -175,16 +178,81 @@ public class FeatureJSONReader {
             final boolean opt = Boolean.valueOf(optional).booleanValue();
 
             final Extension ext = new Extension(extType, name, opt);
+            final Object value = map.get(key);
             switch ( extType ) {
-                case ARTIFACTS : // TODO
-                case JSON : // TODO
-                case TEXT : // TODO
+                case ARTIFACTS : final List<Artifact> list = new ArrayList<>();
+                                 readArtifacts("Extension " + name, 
"artifact", list, value);
+                                 for(final Artifact a : list) {
+                                     if ( ext.getArtifacts().contains(a) ) {
+                                         throw new IOException(exceptionPrefix 
+ "Duplicate artifact in extension " + name + " : " + a.getId().toMvnId());
+                                     }
+                                     ext.getArtifacts().add(a);
+                                 }
+                                 break;
+                case JSON : checkType("JSON Extension " + name, value, 
Map.class, List.class);
+                            final JsonStructure struct = build(value);
+                            try ( final StringWriter w = new StringWriter()) {
+                                final JsonWriter jw = Json.createWriter(w);
+                                jw.write(struct);
+                                w.flush();
+                                ext.setJSON(w.toString());
+                            }
+                            break;
+                case TEXT : checkType("Text Extension " + name, value, 
String.class);
+                            ext.setText(value.toString());
+                            break;
             }
 
             this.feature.getExtensions().add(ext);
         }
     }
 
+    private JsonStructure build(final Object value) {
+        if ( value instanceof List ) {
+            @SuppressWarnings("unchecked")
+            final List<Object> list = (List<Object>)value;
+            final JsonArrayBuilder builder = Json.createArrayBuilder();
+            for(final Object obj : list) {
+                if ( obj instanceof String ) {
+                    builder.add(obj.toString());
+                } else if ( obj instanceof Long ) {
+                    builder.add((Long)obj);
+                } else if ( obj instanceof Double ) {
+                    builder.add((Double)obj);
+                } else if (obj instanceof Boolean ) {
+                    builder.add((Boolean)obj);
+                } else if ( obj instanceof Map ) {
+                    builder.add(build(obj));
+                } else if ( obj instanceof List ) {
+                    builder.add(build(obj));
+                }
+
+            }
+            return builder.build();
+        } else if ( value instanceof Map ) {
+            @SuppressWarnings("unchecked")
+            final Map<String, Object> map = (Map<String, Object>)value;
+            final JsonObjectBuilder builder = Json.createObjectBuilder();
+            for(final Map.Entry<String, Object> entry : map.entrySet()) {
+                if ( entry.getValue() instanceof String ) {
+                    builder.add(entry.getKey(), entry.getValue().toString());
+                } else if ( entry.getValue() instanceof Long ) {
+                    builder.add(entry.getKey(), (Long)entry.getValue());
+                } else if ( entry.getValue() instanceof Double ) {
+                    builder.add(entry.getKey(), (Double)entry.getValue());
+                } else if ( entry.getValue() instanceof Boolean ) {
+                    builder.add(entry.getKey(), (Boolean)entry.getValue());
+                } else if ( entry.getValue() instanceof Map ) {
+                    builder.add(entry.getKey(), build(entry.getValue()));
+                } else if ( entry.getValue() instanceof List ) {
+                    builder.add(entry.getKey(), build(entry.getValue()));
+                }
+            }
+            return builder.build();
+        }
+        return null;
+    }
+
     private void readIncludes(final Map<String, Object> map) throws 
IOException {
         if ( map.containsKey(JSONConstants.FEATURE_INCLUDES)) {
             final Object includesObj = map.get(JSONConstants.FEATURE_INCLUDES);
@@ -194,9 +262,10 @@ public class FeatureJSONReader {
             final List<Object> includes = (List<Object>)includesObj;
             for(final Object inc : includes) {
                 checkType("Include", inc, Map.class, String.class);
+                final Include include;
                 if ( inc instanceof String ) {
                     final ArtifactId id = ArtifactId.fromMvnId(inc.toString());
-                    this.feature.getIncludes().add(new Include(id));
+                    include = new Include(id);
                 } else {
                     @SuppressWarnings("unchecked")
                     final Map<String, Object> obj = (Map<String, Object>) inc;
@@ -205,8 +274,7 @@ public class FeatureJSONReader {
                     }
                     checkType("Include " + JSONConstants.ARTIFACT_ID, 
obj.get(JSONConstants.ARTIFACT_ID), String.class);
                     final ArtifactId id = 
ArtifactId.fromMvnId(obj.get(JSONConstants.ARTIFACT_ID).toString());
-                    final Include include = new Include(id);
-                    feature.getIncludes().add(include);
+                    include = new Include(id);
 
                     if ( obj.containsKey(JSONConstants.INCLUDE_REMOVALS) ) {
                         checkType("Include removals", 
obj.get(JSONConstants.INCLUDE_REMOVALS), Map.class);
@@ -238,11 +306,45 @@ public class FeatureJSONReader {
                                 checkType("Include removal bundles", val, 
String.class);
                                 
include.getFrameworkPropertiesRemovals().add(val.toString());
                             }
-
                         }
-                        // TODO removal of extensions
+                        if ( 
removalObj.containsKey(JSONConstants.INCLUDE_EXTENSION_REMOVALS) ) {
+                            checkType("Include removal extensions", 
removalObj.get(JSONConstants.INCLUDE_EXTENSION_REMOVALS), List.class);
+                            @SuppressWarnings("unchecked")
+                            final List<Object> list = 
(List<Object>)removalObj.get(JSONConstants.INCLUDE_EXTENSION_REMOVALS);
+                            for(final Object val : list) {
+                                checkType("Include removal extension", val, 
String.class, Map.class);
+                                if ( val instanceof String ) {
+                                    
include.getExtensionRemovals().add(val.toString());
+                                } else {
+                                    @SuppressWarnings("unchecked")
+                                    final Map<String, Object> removalMap = 
(Map<String, Object>)val;
+                                    final Object nameObj = 
removalMap.get("name");
+                                    checkType("Include removal extension", 
nameObj, String.class);
+                                    if ( removalMap.containsKey("artifacts") ) 
{
+                                        checkType("Include removal extension 
artifacts", removalMap.get("artifacts"), List.class);
+                                        @SuppressWarnings("unchecked")
+                                        final List<Object> artifactList = 
(List<Object>)removalMap.get("artifacts");
+                                        final List<ArtifactId> ids = new 
ArrayList<>();
+                                        for(final Object aid : artifactList) {
+                                            checkType("Include removal 
extension artifact", aid, String.class);
+                                            
ids.add(ArtifactId.fromMvnId(aid.toString()));
+                                        }
+                                        
include.getArtifactExtensionRemovals().put(nameObj.toString(), ids);
+                                    } else {
+                                        
include.getExtensionRemovals().add(nameObj.toString());
+                                    }
+                                }
+                            }
+                        }
+
                     }
                 }
+                for(final Include i : feature.getIncludes()) {
+                    if ( i.getId().equals(include.getId()) ) {
+                        throw new IOException(exceptionPrefix + "Duplicate 
include of " + include.getId());
+                    }
+                }
+                feature.getIncludes().add(include);
             }
         }
     }
@@ -393,79 +495,62 @@ public class FeatureJSONReader {
                 }
                 if ( 
bundleObj.containsKey(JSONConstants.FEATURE_CONFIGURATIONS) ) {
                     checkType(artifactType + " configurations", 
bundleObj.get(JSONConstants.FEATURE_CONFIGURATIONS), Map.class);
-                    final JSONUtil.Report report = new JSONUtil.Report();
-                    @SuppressWarnings("unchecked")
-                    final List<Config> configs = 
JSONUtil.readConfigurationsJSON(new TypeConverter(null),
-                        0, "", (Map<String, 
?>)bundleObj.get(JSONConstants.FEATURE_CONFIGURATIONS), report);
-                    if ( !report.errors.isEmpty() || 
!report.warnings.isEmpty() ) {
-                        final StringBuilder builder = new 
StringBuilder(this.exceptionPrefix);
-                        builder.append("Errors in configurations:");
-                        for(final String w : report.warnings) {
-                            builder.append("\n");
-                            builder.append(w);
-                        }
-                        for(final String e : report.errors) {
-                            builder.append("\n");
-                            builder.append(e);
-                        }
-                        throw new IOException(builder.toString());
-                    }
-                    for(final Config c : configs) {
-                        if ( c.getEnvironments() != null ) {
-                            throw new IOException(this.exceptionPrefix + 
"Environments for configurations are not supported");
-                        }
-                        final int pos = c.getPid().indexOf('~');
-                        final Configuration config;
-                        if ( pos != -1 ) {
-                            config = new Configuration(c.getPid().substring(0, 
pos), c.getPid().substring(pos + 1));
-                        } else {
-                            config = new Configuration(c.getPid());
-                        }
-                        
config.getProperties().put(Configuration.PROP_ARTIFACT, 
artifact.getId().toMvnId());
-                        feature.getConfigurations().add(config);
-                    }
+                    addConfigurations(bundleObj, artifact);
                 }
             }
             artifacts.add(artifact);
         }
     }
 
+    private void addConfigurations(final Map<String, Object> map,
+            final Artifact artifact) throws IOException {
+        final JSONUtil.Report report = new JSONUtil.Report();
+        @SuppressWarnings("unchecked")
+        final List<Config> configs = JSONUtil.readConfigurationsJSON(new 
TypeConverter(null),
+                0, "", (Map<String, 
?>)map.get(JSONConstants.FEATURE_CONFIGURATIONS), report);
+        if ( !report.errors.isEmpty() || !report.warnings.isEmpty() ) {
+            final StringBuilder builder = new 
StringBuilder(this.exceptionPrefix);
+            builder.append("Errors in configurations:");
+            for(final String w : report.warnings) {
+                builder.append("\n");
+                builder.append(w);
+            }
+            for(final String e : report.errors) {
+                builder.append("\n");
+                builder.append(e);
+            }
+            throw new IOException(builder.toString());
+        }
+        for(final Config c : configs) {
+            if ( c.getEnvironments() != null ) {
+                throw new IOException(this.exceptionPrefix + "Environments for 
configurations are not supported");
+            }
+            final int pos = c.getPid().indexOf('~');
+            final Configuration config;
+            if ( pos != -1 ) {
+                config = new Configuration(c.getPid().substring(0, pos), 
c.getPid().substring(pos + 1));
+            } else {
+                config = new Configuration(c.getPid());
+            }
+            if ( config.getProperties().get(Configuration.PROP_ARTIFACT) != 
null ) {
+                throw new IOException(this.exceptionPrefix + "Configuration 
must not define property " + Configuration.PROP_ARTIFACT);
+            }
+            if ( artifact != null ) {
+                config.getProperties().put(Configuration.PROP_ARTIFACT, 
artifact.getId().toMvnId());
+            }
+            for(final Configuration current : 
this.feature.getConfigurations()) {
+                if ( current.equals(config) ) {
+                    throw new IOException(this.exceptionPrefix + "Duplicate 
configuration " + config);
+                }
+            }
+            feature.getConfigurations().add(config);
+        }
+
+    }
     private void readConfigurations(final Map<String, Object> map) throws 
IOException {
         if ( map.containsKey(JSONConstants.FEATURE_CONFIGURATIONS) ) {
             checkType(JSONConstants.FEATURE_CONFIGURATIONS, 
map.get(JSONConstants.FEATURE_CONFIGURATIONS), Map.class);
-            final JSONUtil.Report report = new JSONUtil.Report();
-            @SuppressWarnings("unchecked")
-            final List<Config> configs = JSONUtil.readConfigurationsJSON(new 
TypeConverter(null),
-                    0, "", (Map<String, 
?>)map.get(JSONConstants.FEATURE_CONFIGURATIONS), report);
-            if ( !report.errors.isEmpty() || !report.warnings.isEmpty() ) {
-                final StringBuilder builder = new 
StringBuilder(this.exceptionPrefix);
-                builder.append("Errors in configurations:");
-                for(final String w : report.warnings) {
-                    builder.append("\n");
-                    builder.append(w);
-                }
-                for(final String e : report.errors) {
-                    builder.append("\n");
-                    builder.append(e);
-                }
-                throw new IOException(builder.toString());
-            }
-            for(final Config c : configs) {
-                if ( c.getEnvironments() != null ) {
-                    throw new IOException(this.exceptionPrefix + "Environments 
for configurations are not supported");
-                }
-                final int pos = c.getPid().indexOf('~');
-                final Configuration config;
-                if ( pos != -1 ) {
-                    config = new Configuration(c.getPid().substring(0, pos), 
c.getPid().substring(pos + 1));
-                } else {
-                    config = new Configuration(c.getPid());
-                }
-                if ( config.getProperties().get(Configuration.PROP_ARTIFACT) 
!= null ) {
-                    throw new IOException(this.exceptionPrefix + 
"Configuration must not define property " + Configuration.PROP_ARTIFACT);
-                }
-                this.feature.getConfigurations().add(config);
-            }
+            addConfigurations(map, null);
         }
     }
 

Modified: 
sling/whiteboard/cziegeler/feature/src/main/java/org/apache/sling/feature/json/FeatureJSONWriter.java
URL: 
http://svn.apache.org/viewvc/sling/whiteboard/cziegeler/feature/src/main/java/org/apache/sling/feature/json/FeatureJSONWriter.java?rev=1796861&r1=1796860&r2=1796861&view=diff
==============================================================================
--- 
sling/whiteboard/cziegeler/feature/src/main/java/org/apache/sling/feature/json/FeatureJSONWriter.java
 (original)
+++ 
sling/whiteboard/cziegeler/feature/src/main/java/org/apache/sling/feature/json/FeatureJSONWriter.java
 Tue May 30 13:26:08 2017
@@ -17,6 +17,7 @@
 package org.apache.sling.feature.json;
 
 import java.io.IOException;
+import java.io.StringReader;
 import java.io.Writer;
 import java.util.ArrayList;
 import java.util.Enumeration;
@@ -24,12 +25,15 @@ import java.util.List;
 import java.util.Map;
 
 import javax.json.Json;
+import javax.json.JsonStructure;
 import javax.json.stream.JsonGenerator;
 
 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.ExtensionType;
 import org.apache.sling.feature.Feature;
 import org.apache.sling.feature.Include;
 import org.apache.sling.feature.Requirement;
@@ -70,8 +74,24 @@ public class FeatureJSONWriter {
                     w.write(JSONConstants.ARTIFACT_ID, inc.getId().toMvnId());
                     w.write(JSONConstants.INCLUDE_REMOVALS);
                     w.writeStartObject();
-                    if ( !inc.getArtifactExtensionRemovals().isEmpty() ) {
-                        // TODO
+                    if ( !inc.getArtifactExtensionRemovals().isEmpty()
+                         || inc.getExtensionRemovals().isEmpty() ) {
+                        w.write(JSONConstants.INCLUDE_EXTENSION_REMOVALS);
+                        w.writeStartArray();
+                        for(final String id : inc.getExtensionRemovals()) {
+                            w.write(id);
+                        }
+                        for(final Map.Entry<String, List<ArtifactId>> entry : 
inc.getArtifactExtensionRemovals().entrySet()) {
+                            w.write(entry.getKey());
+                            w.writeStartObject();
+                            w.writeStartArray();
+                            for(final ArtifactId id : entry.getValue()) {
+                                w.write(id.toMvnId());
+                            }
+                            w.writeEnd();
+                            w.writeEnd();
+                        }
+                        w.writeEnd();
                     }
                     if ( !inc.getConfigurationRemovals().isEmpty() ) {
                         w.write(JSONConstants.FEATURE_CONFIGURATIONS);
@@ -219,7 +239,46 @@ public class FeatureJSONWriter {
             w.writeEnd();
         }
 
-        // TODO extensions
+        // extensions
+        for(final Extension ext : feature.getExtensions()) {
+            final String key = ext.getName() + ":" + ext.getType().name() + 
"|" + ext.isOptional();
+            if ( ext.getType() == ExtensionType.JSON ) {
+                final JsonStructure struct;
+                try ( final StringReader reader = new 
StringReader(ext.getJSON()) ) {
+                    struct = Json.createReader(reader).read();
+                }
+                w.write(key, struct);
+            } else if ( ext.getType() == ExtensionType.TEXT ) {
+                w.write(key, ext.getText());
+            } else {
+                w.write(key);
+                w.writeStartArray();
+                for(final Artifact artifact : ext.getArtifacts()) {
+                    final List<Configuration> artifactCfgs = new ArrayList<>();
+                    for(final Configuration cfg : feature.getConfigurations()) 
{
+                        final String artifactProp = 
(String)cfg.getProperties().get(Configuration.PROP_ARTIFACT);
+                        if (  artifact.getId().toMvnId().equals(artifactProp) 
) {
+                            artifactCfgs.add(cfg);
+                        }
+                    }
+                    if ( artifact.getMetadata().isEmpty() && 
artifactCfgs.isEmpty() ) {
+                        w.write(artifact.getId().toMvnId());
+                    } else {
+                        w.writeStartObject();
+                        w.write(JSONConstants.ARTIFACT_ID, 
artifact.getId().toMvnId());
+
+                        for(final Map.Entry<String, String> me : 
artifact.getMetadata()) {
+                            w.write(me.getKey(), me.getValue());
+                        }
+
+                        writeConfigurations(w, artifactCfgs);
+                        w.writeEnd();
+                    }
+                }
+                w.writeEnd();
+            }
+        }
+
         w.writeEnd();
         w.flush();
     }

Modified: 
sling/whiteboard/cziegeler/feature/src/main/java/org/apache/sling/feature/json/JSONConstants.java
URL: 
http://svn.apache.org/viewvc/sling/whiteboard/cziegeler/feature/src/main/java/org/apache/sling/feature/json/JSONConstants.java?rev=1796861&r1=1796860&r2=1796861&view=diff
==============================================================================
--- 
sling/whiteboard/cziegeler/feature/src/main/java/org/apache/sling/feature/json/JSONConstants.java
 (original)
+++ 
sling/whiteboard/cziegeler/feature/src/main/java/org/apache/sling/feature/json/JSONConstants.java
 Tue May 30 13:26:08 2017
@@ -53,6 +53,8 @@ public abstract class JSONConstants {
 
     public static final String INCLUDE_REMOVALS = "removals";
 
+    public static final String INCLUDE_EXTENSION_REMOVALS = "extensions";
+
     public static final String REQCAP_NAMESPACE = "namespace";
     public static final String REQCAP_ATTRIBUTES = "attributes";
     public static final String REQCAP_DIRECTIVES = "directives";

Modified: 
sling/whiteboard/cziegeler/feature/src/main/java/org/apache/sling/feature/process/ApplicationBuilder.java
URL: 
http://svn.apache.org/viewvc/sling/whiteboard/cziegeler/feature/src/main/java/org/apache/sling/feature/process/ApplicationBuilder.java?rev=1796861&r1=1796860&r2=1796861&view=diff
==============================================================================
--- 
sling/whiteboard/cziegeler/feature/src/main/java/org/apache/sling/feature/process/ApplicationBuilder.java
 (original)
+++ 
sling/whiteboard/cziegeler/feature/src/main/java/org/apache/sling/feature/process/ApplicationBuilder.java
 Tue May 30 13:26:08 2017
@@ -16,16 +16,25 @@
  */
 package org.apache.sling.feature.process;
 
+import java.io.StringReader;
 import java.util.ArrayList;
 import java.util.Collections;
 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.ArtifactId;
 import org.apache.sling.feature.Configuration;
+import org.apache.sling.feature.Extension;
 import org.apache.sling.feature.Feature;
 
 /**
@@ -94,8 +103,16 @@ public class ApplicationBuilder {
         // bundles
         for(final Map.Entry<Integer, List<Artifact>> entry : 
source.getBundles().getBundlesByStartLevel().entrySet()) {
             for(final Artifact a : entry.getValue()) {
-                target.getBundles().remove(a.getId());
-                target.getBundles().add(entry.getKey(), a);
+                // version handling - highest version wins
+                final Map.Entry<Integer, Artifact> existing = 
target.getBundles().getSame(a.getId());
+                boolean replace = true;
+                if ( existing != null && 
existing.getValue().getId().getOSGiVersion().compareTo(a.getId().getOSGiVersion())
 > 0 ) {
+                    replace = false;
+                }
+                if ( replace ) {
+                    target.getBundles().removeSame(a.getId());
+                    target.getBundles().add(entry.getKey(), a);
+                }
             }
         }
 
@@ -121,6 +138,96 @@ public class ApplicationBuilder {
         // framework properties
         
target.getFrameworkProperties().putAll(source.getFrameworkProperties());
 
-        // TODO - extensions
+        // extensions
+        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());
+                    }
+                    switch ( current.getType() ) {
+                        case TEXT : // simply append
+                                    current.setText(current.getText() + "\n" + 
ext.getText());
+                                    break;
+                        case JSON : final JsonStructure struct1;
+                                    try ( final StringReader reader = new 
StringReader(current.getJSON()) ) {
+                                        struct1 = 
Json.createReader(reader).read();
+                                    }
+                                    final JsonStructure struct2;
+                                    try ( final StringReader reader = new 
StringReader(ext.getJSON()) ) {
+                                        struct2 = 
Json.createReader(reader).read();
+                                    }
+
+                                    if ( struct1.getValueType() != 
struct2.getValueType() ) {
+                                        throw new IllegalStateException("Found 
different JSON types for extension " + current.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 : 
ext.getArtifacts()) {
+                                             // remove same artifact - latest 
version wins (not highest)
+                                             boolean add = true;
+                                             for(final Artifact targetArtifact 
: current.getArtifacts()) {
+                                                 if ( 
targetArtifact.getId().isSame(a.getId()) ) {
+                                                     if ( 
targetArtifact.getId().getOSGiVersion().compareTo(a.getId().getOSGiVersion()) > 
0 ) {
+                                                         add = false;
+                                                     } else {
+                                                         
current.getArtifacts().remove(targetArtifact);
+                                                     }
+                                                     break;
+                                                 }
+                                             }
+                                             if ( add ) {
+                                                 current.getArtifacts().add(a);
+                                             }
+                                         }
+                                         break;
+
+                    }
+                }
+            }
+            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());
+                }
+            }
+        }
     }
 }

Modified: 
sling/whiteboard/cziegeler/feature/src/main/java/org/apache/sling/feature/process/FeatureBuilder.java
URL: 
http://svn.apache.org/viewvc/sling/whiteboard/cziegeler/feature/src/main/java/org/apache/sling/feature/process/FeatureBuilder.java?rev=1796861&r1=1796860&r2=1796861&view=diff
==============================================================================
--- 
sling/whiteboard/cziegeler/feature/src/main/java/org/apache/sling/feature/process/FeatureBuilder.java
 (original)
+++ 
sling/whiteboard/cziegeler/feature/src/main/java/org/apache/sling/feature/process/FeatureBuilder.java
 Tue May 30 13:26:08 2017
@@ -16,15 +16,24 @@
  */
 package org.apache.sling.feature.process;
 
+import java.io.StringReader;
 import java.util.Enumeration;
 import java.util.Iterator;
 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.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;
@@ -58,8 +67,9 @@ public class FeatureBuilder {
                 final Feature af = assemble(feature, provider);
 
                 // process removals
+                // bundles
                 for(final ArtifactId a : i.getBundleRemovals()) {
-                    af.getBundles().remove(a);
+                    af.getBundles().removeExact(a);
                     final Iterator<Configuration> iter = 
af.getConfigurations().iterator();
                     while ( iter.hasNext() ) {
                         final Configuration cfg = iter.next();
@@ -69,6 +79,7 @@ public class FeatureBuilder {
                         }
                     }
                 }
+                // configurations
                 for(final String c : i.getConfigurationRemovals()) {
                     final int attrPos = c.indexOf('@');
                     final String val = (attrPos == -1 ? c : c.substring(0, 
attrPos));
@@ -105,11 +116,36 @@ public class FeatureBuilder {
                         }
                     }
                 }
+
+                // framework properties
                 for(final String p : i.getFrameworkPropertiesRemovals()) {
                     af.getFrameworkProperties().remove(p);
                 }
 
-                // TODO process feature removals
+                // extensions
+                for(final String name : i.getExtensionRemovals()) {
+                    for(final Extension ext : af.getExtensions()) {
+                        if ( ext.getName().equals(name) ) {
+                            af.getExtensions().remove(ext);
+                            break;
+                        }
+                    }
+                }
+                for(final Map.Entry<String, List<ArtifactId>> entry : 
i.getArtifactExtensionRemovals().entrySet()) {
+                    for(final Extension ext : af.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;
+                        }
+                    }
+                }
 
                 // and now merge
                 merge(result, af);
@@ -123,7 +159,8 @@ public class FeatureBuilder {
         // bundles
         for(final Map.Entry<Integer, List<Artifact>> entry : 
source.getBundles().getBundlesByStartLevel().entrySet()) {
             for(final Artifact a : entry.getValue()) {
-                target.getBundles().remove(a.getId());
+                // latest version wins (not highest!)
+                target.getBundles().removeSame(a.getId());
                 target.getBundles().add(entry.getKey(), a);
             }
         }
@@ -134,6 +171,7 @@ public class FeatureBuilder {
             for(final Configuration current : target.getConfigurations()) {
                 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();
@@ -147,19 +185,102 @@ public class FeatureBuilder {
             }
         }
 
-        // framework properties
+        // framework properties (add/merge)
         
target.getFrameworkProperties().putAll(source.getFrameworkProperties());
 
-        // requirements
+        // requirements (add)
         for(final Requirement req : source.getRequirements()) {
             target.getRequirements().add(req);
         }
 
-        // capabilities
+        // capabilities (add)
         for(final Capability cap : source.getCapabilities()) {
             target.getCapabilities().add(cap);
         }
 
-        // TODO - extensions
+        // extensions (add/merge)
+        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());
+                    }
+                    switch ( current.getType() ) {
+                        case TEXT : // simply append
+                                    current.setText(current.getText() + "\n" + 
ext.getText());
+                                    break;
+                        case JSON : final JsonStructure struct1;
+                                    try ( final StringReader reader = new 
StringReader(current.getJSON()) ) {
+                                        struct1 = 
Json.createReader(reader).read();
+                                    }
+                                    final JsonStructure struct2;
+                                    try ( final StringReader reader = new 
StringReader(ext.getJSON()) ) {
+                                        struct2 = 
Json.createReader(reader).read();
+                                    }
+
+                                    if ( struct1.getValueType() != 
struct2.getValueType() ) {
+                                        throw new IllegalStateException("Found 
different JSON types for extension " + current.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 : 
ext.getArtifacts()) {
+                                             // remove same artifact - latest 
version wins (not highest)
+                                             for(final Artifact targetArtifact 
: current.getArtifacts()) {
+                                                 if ( 
targetArtifact.getId().isSame(a.getId()) ) {
+                                                     
current.getArtifacts().remove(targetArtifact);
+                                                     break;
+                                                 }
+                                             }
+                                             current.getArtifacts().add(a);
+                                         }
+                                         break;
+
+                    }
+                }
+            }
+            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());
+                }
+            }
+        }
     }
 }


Reply via email to