You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@karaf.apache.org by gg...@apache.org on 2017/12/04 10:12:42 UTC

[karaf] 08/12: [KARAF-5376] Using "features processor" in profile builder (overrides, blacklist)

This is an automated email from the ASF dual-hosted git repository.

ggrzybek pushed a commit to branch KARAF-5376-overrides_v2
in repository https://gitbox.apache.org/repos/asf/karaf.git

commit 2e23fb79eb70b8dc5ba7c64bf8b4b640f4708d01
Author: Grzegorz Grzybek <gr...@gmail.com>
AuthorDate: Wed Nov 22 12:08:11 2017 +0100

    [KARAF-5376] Using "features processor" in profile builder (overrides, blacklist)
---
 features/core/pom.xml                              |   1 +
 .../org/apache/karaf/features/FeaturePattern.java  |  15 +-
 .../org/apache/karaf/features/LocationPattern.java |   2 +-
 .../model/processing/FeaturesProcessing.java       |  27 +-
 .../karaf/features/internal/service/Blacklist.java |  25 ++
 .../karaf/features/internal/service/Deployer.java  |  12 +
 .../service/FeaturesProcessingSerializer.java      | 159 ++++++++++
 .../internal/service/FeaturesProcessor.java        |   4 +-
 .../internal/service/FeaturesProcessorImpl.java    | 102 ++++---
 .../internal/service/RepositoryCacheImpl.java      |   2 +-
 .../service/feature-processing-comments.properties |  27 ++
 .../features/karaf-features-processing-1.0.0.xsd   |  22 +-
 .../internal/service/FeaturesProcessorTest.java    |  42 ++-
 .../karaf/profile/assembly/ArtifactInstaller.java  |   9 +-
 .../profile/assembly/AssemblyDeployCallback.java   |  32 +-
 .../org/apache/karaf/profile/assembly/Builder.java | 334 ++++++++++++++-------
 .../org/apache/karaf/tooling/AssemblyMojo.java     |   7 +
 .../java/org/apache/karaf/tooling/VerifyMojo.java  |  14 +-
 .../karaf/util/xml/IndentingXMLEventWriter.java    | 144 +++++++++
 19 files changed, 769 insertions(+), 211 deletions(-)

diff --git a/features/core/pom.xml b/features/core/pom.xml
index 028de45..e5eaa85 100644
--- a/features/core/pom.xml
+++ b/features/core/pom.xml
@@ -156,6 +156,7 @@
                             org.apache.karaf.util.collections,
                             org.apache.karaf.util.json,
                             org.apache.karaf.util.maven,
+                            org.apache.karaf.util.xml,
                             org.eclipse.equinox.internal.region.*;-split-package:=merge-first,
                             org.apache.felix.resolver.*,
                         </Private-Package>
diff --git a/features/core/src/main/java/org/apache/karaf/features/FeaturePattern.java b/features/core/src/main/java/org/apache/karaf/features/FeaturePattern.java
index 06db75a..107993b 100644
--- a/features/core/src/main/java/org/apache/karaf/features/FeaturePattern.java
+++ b/features/core/src/main/java/org/apache/karaf/features/FeaturePattern.java
@@ -41,6 +41,7 @@ public class FeaturePattern {
     public static final String RANGE = "range";
 
     private String originalId;
+    private String nameString;
     private Pattern namePattern;
     private String versionString;
     private Version version;
@@ -51,16 +52,16 @@ public class FeaturePattern {
             throw new IllegalArgumentException("Feature ID to match should not be null");
         }
         originalId = featureId;
-        String name = originalId;
-        if (name.indexOf("/") > 0) {
-            name = originalId.substring(0, originalId.indexOf("/"));
+        nameString = originalId;
+        if (originalId.indexOf("/") > 0) {
+            nameString = originalId.substring(0, originalId.indexOf("/"));
             versionString = originalId.substring(originalId.indexOf("/") + 1);
-        } else if (name.contains(";")) {
+        } else if (originalId.contains(";")) {
             Clause[] c = org.apache.felix.utils.manifest.Parser.parseClauses(new String[] { originalId });
-            name = c[0].getName();
+            nameString = c[0].getName();
             versionString = c[0].getAttribute(RANGE);
         }
-        namePattern = LocationPattern.toRegExp(name);
+        namePattern = LocationPattern.toRegExp(nameString);
 
         if (versionString != null && versionString.length() >= 1) {
             try {
@@ -80,7 +81,7 @@ public class FeaturePattern {
     }
 
     /**
-     * Returns <code>if this feature pattern</code> matches given feature/version
+     * Returns <code>true</code> if this feature pattern matches given feature/version
      * @param featureName
      * @param featureVersion
      * @return
diff --git a/features/core/src/main/java/org/apache/karaf/features/LocationPattern.java b/features/core/src/main/java/org/apache/karaf/features/LocationPattern.java
index 20390ca..428746f 100644
--- a/features/core/src/main/java/org/apache/karaf/features/LocationPattern.java
+++ b/features/core/src/main/java/org/apache/karaf/features/LocationPattern.java
@@ -113,7 +113,7 @@ public class LocationPattern {
      * @param value
      * @return
      */
-    static Pattern toRegExp(String value) {
+    public static Pattern toRegExp(String value) {
         // TODO: escape all RegExp special chars that are valid path characters, only convert '*' into '.*'
         return Pattern.compile(value
                 .replaceAll("\\.", "\\\\\\.")
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/model/processing/FeaturesProcessing.java b/features/core/src/main/java/org/apache/karaf/features/internal/model/processing/FeaturesProcessing.java
index c2a0765..8a3c7b6 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/model/processing/FeaturesProcessing.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/model/processing/FeaturesProcessing.java
@@ -20,6 +20,7 @@ package org.apache.karaf.features.internal.model.processing;
 
 import java.net.MalformedURLException;
 import java.net.URI;
+import java.util.Collection;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
@@ -37,6 +38,7 @@ import org.apache.felix.utils.manifest.Clause;
 import org.apache.felix.utils.manifest.Parser;
 import org.apache.felix.utils.version.VersionCleaner;
 import org.apache.felix.utils.version.VersionRange;
+import org.apache.karaf.features.FeaturePattern;
 import org.apache.karaf.features.internal.service.Blacklist;
 import org.apache.karaf.features.LocationPattern;
 import org.osgi.framework.Version;
@@ -183,17 +185,32 @@ public class FeaturesProcessing {
         // blacklisted bundle from XML to instruction for Blacklist class
         List<String> blacklisted = new LinkedList<>();
         for (String bl : this.getBlacklistedBundles()) {
-            blacklisted.add(bl + ";type=bundle");
+            blacklisted.add(bl + ";" + Blacklist.BLACKLIST_TYPE + "=" + Blacklist.TYPE_BUNDLE);
         }
         // blacklisted features - XML type to String instruction for Blacklist class
         blacklisted.addAll(this.getBlacklistedFeatures().stream()
-                .map(bf -> bf.getName() + ";type=feature" + (bf.getVersion() == null ? "" : ";range=\"" + bf.getVersion() + "\""))
+                .map(bf -> bf.getName() + ";" + Blacklist.BLACKLIST_TYPE + "=" + Blacklist.TYPE_FEATURE + (bf.getVersion() == null ? "" : ";" + FeaturePattern.RANGE + "=\"" + bf.getVersion() + "\""))
                 .collect(Collectors.toList()));
+        // blacklisted repositories
+        for (String bl : this.getBlacklistedRepositories()) {
+            blacklisted.add(bl + ";" + Blacklist.BLACKLIST_TYPE + "=" + Blacklist.TYPE_REPOSITORY);
+        }
 
         this.blacklist = new Blacklist(blacklisted);
         this.blacklist.merge(blacklist);
 
         // etc/overrides.properties (mvn: URIs)
+        bundleReplacements.getOverrideBundles().addAll(parseOverridesClauses(overrides));
+    }
+
+    /**
+     * Changes overrides list (old format) into a list of {@link BundleReplacements.OverrideBundle} definitions.
+     * @param overrides
+     * @return
+     */
+    public static Collection<? extends BundleReplacements.OverrideBundle> parseOverridesClauses(Set<String> overrides) {
+        List<BundleReplacements.OverrideBundle> result = new LinkedList<>();
+
         for (Clause clause : Parser.parseClauses(overrides.toArray(new String[overrides.size()]))) {
             // name of the clause will become a bundle replacement
             String mvnURI = clause.getName();
@@ -210,12 +227,14 @@ public class FeaturesProcessing {
                 override.setOriginalUri(originalUri);
                 try {
                     override.compile();
-                    bundleReplacements.getOverrideBundles().add(override);
+                    result.add(override);
                 } catch (MalformedURLException e) {
                     LOG.warn("Can't parse override URL location pattern: " + originalUri + ". Ignoring.");
                 }
             }
         }
+
+        return result;
     }
 
     /**
@@ -225,7 +244,7 @@ public class FeaturesProcessing {
      * @param range
      * @return
      */
-    private String calculateOverridenURI(String replacement, String range) {
+    private static String calculateOverridenURI(String replacement, String range) {
         try {
             org.apache.karaf.util.maven.Parser parser = new org.apache.karaf.util.maven.Parser(replacement);
             if (parser.getVersion() != null
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/Blacklist.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/Blacklist.java
index 046c4be..dcc1a20 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/service/Blacklist.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/Blacklist.java
@@ -40,6 +40,7 @@ import org.slf4j.LoggerFactory;
 /**
  * Helper class to deal with blacklisted features and bundles. It doesn't process JAXB model at all - it only
  * provides information about repository/feature/bundle being blacklisted.
+ * The task of actual blacklisting (altering JAXB model) is performed in {@link FeaturesProcessor}
  */
 public class Blacklist {
 
@@ -218,4 +219,28 @@ public class Blacklist {
     public void blacklist(Features featuresModel) {
     }
 
+    /**
+     * Directly add {@link LocationPattern} as blacklisted features XML repository URI
+     * @param locationPattern
+     */
+    public void blacklistRepository(LocationPattern locationPattern) {
+        repositoryBlacklist.add(locationPattern);
+    }
+
+    /**
+     * Directly add {@link FeaturePattern} as blacklisted feature ID
+     * @param featurePattern
+     */
+    public void blacklistFeature(FeaturePattern featurePattern) {
+        featureBlacklist.add(featurePattern);
+    }
+
+    /**
+     * Directly add {@link LocationPattern} as blacklisted bundle URI
+     * @param locationPattern
+     */
+    public void blacklistBundle(LocationPattern locationPattern) {
+        bundleBlacklist.add(locationPattern);
+    }
+
 }
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/Deployer.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/Deployer.java
index 61244b6..571e923 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/service/Deployer.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/Deployer.java
@@ -200,6 +200,18 @@ public class Deployer {
         public Map<String, Map<String, FeatureState>> stateChanges;
         public EnumSet<FeaturesService.Option> options;
         public String outputFile;
+
+        public static DeploymentRequest defaultDeploymentRequest() {
+            DeploymentRequest request = new DeploymentRequest();
+            request.bundleUpdateRange = FeaturesService.DEFAULT_BUNDLE_UPDATE_RANGE;
+            request.featureResolutionRange = FeaturesService.DEFAULT_FEATURE_RESOLUTION_RANGE;
+            request.serviceRequirements = FeaturesService.SERVICE_REQUIREMENTS_DEFAULT;
+            request.overrides = new HashSet<>();
+            request.requirements = new HashMap<>();
+            request.stateChanges = new HashMap<>();
+            request.options = EnumSet.noneOf(FeaturesService.Option.class);
+            return request;
+        }
     }
 
     static class Deployment {
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesProcessingSerializer.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesProcessingSerializer.java
new file mode 100644
index 0000000..4a72fe3
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesProcessingSerializer.java
@@ -0,0 +1,159 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.features.internal.service;
+
+import java.io.BufferedWriter;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Marshaller;
+import javax.xml.bind.Unmarshaller;
+import javax.xml.stream.XMLEventFactory;
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.XMLEventWriter;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.events.XMLEvent;
+import javax.xml.transform.stream.StreamResult;
+
+import org.apache.karaf.features.internal.model.processing.FeaturesProcessing;
+import org.apache.karaf.features.internal.model.processing.ObjectFactory;
+import org.apache.karaf.util.xml.IndentingXMLEventWriter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A class to help serialize {@link org.apache.karaf.features.internal.model.processing.FeaturesProcessing} model
+ * but with added template comments for main sections of <code>org.apache.karaf.features.xml</code> file.
+ */
+public class FeaturesProcessingSerializer {
+
+    public static Logger LOG = LoggerFactory.getLogger(FeaturesProcessingSerializer.class);
+
+    private JAXBContext FEATURES_PROCESSING_CONTEXT;
+
+    public FeaturesProcessingSerializer() {
+        try {
+            FEATURES_PROCESSING_CONTEXT = JAXBContext.newInstance(ObjectFactory.class);
+        } catch (JAXBException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Reads {@link FeaturesProcessing features processing model} from input stream
+     * @param stream
+     * @return
+     */
+    public FeaturesProcessing read(InputStream stream) throws JAXBException {
+        Unmarshaller unmarshaller = FEATURES_PROCESSING_CONTEXT.createUnmarshaller();
+        return (FeaturesProcessing) unmarshaller.unmarshal(stream);
+    }
+
+    /**
+     * Writes the model to output stream and adds comments for main sections.
+     * @param model
+     * @param output
+     */
+    public void write(FeaturesProcessing model, OutputStream output) {
+        try {
+            // JAXB model as stream which is next parsed as XMLEvents
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            Marshaller marshaller = FEATURES_PROCESSING_CONTEXT.createMarshaller();
+            marshaller.marshal(model, new StreamResult(baos));
+
+            Map<String, Boolean> emptyElements = new HashMap<>();
+            emptyElements.put("blacklistedRepositories", model.getBlacklistedRepositories().size() == 0);
+            emptyElements.put("blacklistedFeatures", model.getBlacklistedFeatures().size() == 0);
+            emptyElements.put("blacklistedBundles", model.getBlacklistedBundles().size() == 0);
+            emptyElements.put("overrideBundleDependency", model.getOverrideBundleDependency().getRepositories().size()
+                    + model.getOverrideBundleDependency().getFeatures().size()
+                    + model.getOverrideBundleDependency().getBundles().size() == 0);
+            emptyElements.put("bundleReplacements", model.getBundleReplacements().getOverrideBundles().size() == 0);
+            emptyElements.put("featureReplacements", model.getFeatureReplacements().getReplacements().size() == 0);
+
+            // A mix of direct write and stream of XML events. It's not easy (without knowing StAX impl) to
+            // output self closed tags for example.
+            BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(output, "UTF-8"));
+            writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n");
+            writer.write("    Configuration generated by Karaf Assembly Builder\n");
+            writer.write("-->\n");
+            writer.flush();
+
+            Properties props = new Properties();
+            props.load(getClass().getResourceAsStream("feature-processing-comments.properties"));
+
+            XMLEventReader xmlEventReader = XMLInputFactory.newFactory().createXMLEventReader(new ByteArrayInputStream(baos.toByteArray()));
+            XMLEventWriter xmlEventWriter = new IndentingXMLEventWriter(XMLOutputFactory.newFactory().createXMLEventWriter(writer), "    ");
+            XMLEventFactory evFactory = XMLEventFactory.newFactory();
+            int depth = 0;
+            boolean skipClose = false;
+            while (xmlEventReader.hasNext()) {
+                XMLEvent ev = xmlEventReader.nextEvent();
+                int type = ev.getEventType();
+                if (type != XMLEvent.START_DOCUMENT && type != XMLEvent.END_DOCUMENT) {
+                    if (type == XMLEvent.START_ELEMENT) {
+                        skipClose = false;
+                        depth++;
+                        if (depth == 2) {
+                            String tag = ev.asStartElement().getName().getLocalPart();
+                            String comment = props.getProperty(tag);
+                            xmlEventWriter.add(evFactory.createCharacters("\n    "));
+                            xmlEventWriter.add(evFactory.createComment(" " + comment + " "));
+                            if (emptyElements.get(tag) != null && emptyElements.get(tag)) {
+                                skipClose = true;
+                                writer.write("    <" + tag + " />\n");
+                            }
+                        }
+                    } else if (type == XMLEvent.END_ELEMENT) {
+                        skipClose = false;
+                        depth--;
+                        if (depth == 1) {
+                            String tag = ev.asEndElement().getName().getLocalPart();
+                            String comment = props.getProperty(tag);
+                            if (emptyElements.get(tag) != null && emptyElements.get(tag)) {
+                                skipClose = true;
+                            }
+                        }
+                    }
+                    if (type == XMLEvent.END_ELEMENT && depth == 0) {
+                        xmlEventWriter.add(evFactory.createCharacters("\n"));
+                    }
+                    if (!skipClose) {
+                        xmlEventWriter.add(ev);
+                    }
+                    if (type == XMLEvent.START_ELEMENT && depth == 1) {
+                        xmlEventWriter.add(evFactory.createCharacters("\n"));
+                    }
+                }
+            }
+            writer.flush();
+        } catch (Exception e) {
+            LOG.warn(e.getMessage(), e);
+        }
+    }
+
+}
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesProcessor.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesProcessor.java
index 28e805d..7a74fae 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesProcessor.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesProcessor.java
@@ -18,8 +18,6 @@
  */
 package org.apache.karaf.features.internal.service;
 
-import java.net.URI;
-
 import org.apache.karaf.features.Repository;
 import org.apache.karaf.features.internal.model.Features;
 
@@ -33,7 +31,7 @@ public interface FeaturesProcessor {
      * @param uri
      * @return
      */
-    boolean isRepositoryBlacklisted(URI uri);
+    boolean isRepositoryBlacklisted(String uri);
 
     /**
      * Processes original {@link Features JAXB model of features}
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesProcessorImpl.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesProcessorImpl.java
index c14c559..c6ed5a6 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesProcessorImpl.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesProcessorImpl.java
@@ -20,13 +20,10 @@ package org.apache.karaf.features.internal.service;
 
 import java.io.FileNotFoundException;
 import java.io.InputStream;
-import java.net.URI;
+import java.io.OutputStream;
 import java.net.URL;
 import java.util.List;
 import java.util.Set;
-import javax.xml.bind.JAXBContext;
-import javax.xml.bind.JAXBException;
-import javax.xml.bind.Unmarshaller;
 
 import org.apache.karaf.features.BundleInfo;
 import org.apache.karaf.features.LocationPattern;
@@ -36,7 +33,6 @@ import org.apache.karaf.features.internal.model.Feature;
 import org.apache.karaf.features.internal.model.Features;
 import org.apache.karaf.features.internal.model.processing.BundleReplacements;
 import org.apache.karaf.features.internal.model.processing.FeaturesProcessing;
-import org.apache.karaf.features.internal.model.processing.ObjectFactory;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -52,41 +48,22 @@ import org.slf4j.LoggerFactory;
 public class FeaturesProcessorImpl implements FeaturesProcessor {
 
     public static Logger LOG = LoggerFactory.getLogger(FeaturesProcessorImpl.class);
-    private static final JAXBContext FEATURES_PROCESSING_CONTEXT;
 
+    private static FeaturesProcessingSerializer serializer = new FeaturesProcessingSerializer();
     private FeaturesProcessing processing = new FeaturesProcessing();
 
-    static {
-        try {
-            FEATURES_PROCESSING_CONTEXT = JAXBContext.newInstance(ObjectFactory.class);
-        } catch (JAXBException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
     /**
-     * <p>Creates instance of features processor using {@link FeaturesServiceConfig configuration object} where
-     * three files may be specified: overrides.properties, blacklisted.properties and org.apache.karaf.features.xml.</p>
-     * @param configuration
+     * <p>Creates instance of features processor using 1 external URI, additional {@link Blacklist} instance
+     * and additional set of override clauses.</p>
+     * @param featureModificationsURI
+     * @param blacklistDefinitions
+     * @param overrides
      */
-    public FeaturesProcessorImpl(FeaturesServiceConfig configuration) {
-        // org.apache.karaf.features.xml - highest priority
-        String featureModificationsURI = configuration.featureModifications;
-        // blacklisted.properties - if available, adds to main configuration of feature processing
-        String blacklistedURI = configuration.blacklisted;
-        // overrides.properties - if available, adds to main configuration of feature processing
-        String overridesURI = configuration.overrides;
-
-        // these two are not changed - they still may be used, but if etc/org.apache.karaf.features.xml is available
-        // both of the below are merged into single processing configuration
-        Blacklist blacklist = new Blacklist(blacklistedURI);
-        Set<String> overrides = Overrides.loadOverrides(overridesURI);
-
+    public FeaturesProcessorImpl(String featureModificationsURI, Blacklist blacklistDefinitions, Set<String> overrides) {
         if (featureModificationsURI != null) {
             try {
                 try (InputStream stream = new URL(featureModificationsURI).openStream()) {
-                    Unmarshaller unmarshaller = FEATURES_PROCESSING_CONTEXT.createUnmarshaller();
-                    processing = (FeaturesProcessing) unmarshaller.unmarshal(stream);
+                    processing = serializer.read(stream);
                 }
             } catch (FileNotFoundException e) {
                 LOG.warn("Can't find feature processing file (" + featureModificationsURI + ")");
@@ -95,13 +72,49 @@ public class FeaturesProcessorImpl implements FeaturesProcessor {
             }
         }
 
-        processing.postUnmarshall(blacklist, overrides);
+        processing.postUnmarshall(blacklistDefinitions, overrides);
+    }
+
+    /**
+     * <p>Creates instance of features processor using 3 external (optional) URIs.</p>
+     * @param featureModificationsURI
+     * @param blacklistedURI
+     * @param overridesURI
+     */
+    public FeaturesProcessorImpl(String featureModificationsURI, String blacklistedURI, String overridesURI) {
+        this(featureModificationsURI, new Blacklist(blacklistedURI), Overrides.loadOverrides(overridesURI));
+    }
+
+    /**
+     * <p>Creates instance of features processor using {@link FeaturesServiceConfig configuration object} where
+     * three files may be specified: overrides.properties, blacklisted.properties and org.apache.karaf.features.xml.</p>
+     * @param configuration
+     */
+    public FeaturesProcessorImpl(FeaturesServiceConfig configuration) {
+        this(configuration.featureModifications, configuration.blacklisted, configuration.overrides);
+    }
+
+    /**
+     * Writes model to output stream.
+     * @param output
+     */
+    public void writeInstructions(OutputStream output) {
+        serializer.write(processing, output);
     }
 
     public FeaturesProcessing getInstructions() {
         return processing;
     }
 
+    /**
+     * For the purpose of assembly builder, we can configure additional overrides that are read from profiles
+     * @param overrides
+     */
+    public void addOverrides(Set<String> overrides) {
+        processing.getBundleReplacements().getOverrideBundles()
+                .addAll(FeaturesProcessing.parseOverridesClauses(overrides));
+    }
+
     @Override
     public void process(Features features) {
         // blacklisting features
@@ -153,9 +166,9 @@ public class FeaturesProcessorImpl implements FeaturesProcessor {
     }
 
     @Override
-    public boolean isRepositoryBlacklisted(URI uri) {
+    public boolean isRepositoryBlacklisted(String uri) {
         for (LocationPattern lp : processing.getBlacklistedRepositoryLocationPatterns()) {
-            if (lp.matches(uri.toString())) {
+            if (lp.matches(uri)) {
                 return true;
             }
         }
@@ -181,4 +194,23 @@ public class FeaturesProcessorImpl implements FeaturesProcessor {
         return getInstructions().getBlacklist().isBundleBlacklisted(location);
     }
 
+    /**
+     * Checks whether the configuration in this processor contains any instructions (for bundles, repositories,
+     * overrides, ...)
+     * @return
+     */
+    public boolean hasInstructions() {
+        int count = 0;
+        count += getInstructions().getBlacklistedRepositories().size();
+        count += getInstructions().getBlacklistedFeatures().size();
+        count += getInstructions().getBlacklistedBundles().size();
+        count += getInstructions().getOverrideBundleDependency().getRepositories().size();
+        count += getInstructions().getOverrideBundleDependency().getFeatures().size();
+        count += getInstructions().getOverrideBundleDependency().getBundles().size();
+        count += getInstructions().getBundleReplacements().getOverrideBundles().size();
+        count += getInstructions().getFeatureReplacements().getReplacements().size();
+
+        return count > 0;
+    }
+
 }
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/RepositoryCacheImpl.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/RepositoryCacheImpl.java
index 9874a20..0848f55 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/service/RepositoryCacheImpl.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/RepositoryCacheImpl.java
@@ -47,7 +47,7 @@ public class RepositoryCacheImpl implements RepositoryCache {
         RepositoryImpl repository = new RepositoryImpl(uri, validate);
         if (featuresProcessor != null) {
             // maybe it could be done better - first we have to set if entire repo is blacklisted
-            repository.setBlacklisted(featuresProcessor.isRepositoryBlacklisted(uri));
+            repository.setBlacklisted(featuresProcessor.isRepositoryBlacklisted(uri.toString()));
             // processing features will take the above flag into account to blacklist (if needed) the features
             repository.processFeatures(featuresProcessor);
         }
diff --git a/features/core/src/main/resources/org/apache/karaf/features/internal/service/feature-processing-comments.properties b/features/core/src/main/resources/org/apache/karaf/features/internal/service/feature-processing-comments.properties
new file mode 100644
index 0000000..29bb1c9
--- /dev/null
+++ b/features/core/src/main/resources/org/apache/karaf/features/internal/service/feature-processing-comments.properties
@@ -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.
+#
+
+# comments to insert into serialized XML for features processing instructions
+
+blacklistedRepositories = A list of blacklisted features XML repository URIs - they can't be added later
+blacklistedFeatures = A list of blacklisted feature identifiers that can't be installed in Karaf and are not part of the distribution
+blacklistedBundles = A list of blacklisted bundle URIs that are not installed even if they are part of some features
+overrideBundleDependency = A list of repository URIs, feature identifiers and bundle URIs to override "dependency" flag
+bundleReplacements = A list of bundle URI replacements that allows changing external feature definitions
+featureReplacements = A list of feature replacements that allows changing external feature definitions
diff --git a/features/core/src/main/resources/org/apache/karaf/features/karaf-features-processing-1.0.0.xsd b/features/core/src/main/resources/org/apache/karaf/features/karaf-features-processing-1.0.0.xsd
index b82bf08..f25fabe 100644
--- a/features/core/src/main/resources/org/apache/karaf/features/karaf-features-processing-1.0.0.xsd
+++ b/features/core/src/main/resources/org/apache/karaf/features/karaf-features-processing-1.0.0.xsd
@@ -60,17 +60,12 @@ should be specified. In most abstract case, "mvn:*/*" describes all maven URIs.
 
     <xs:complexType name="blacklistedFeatures">
         <xs:annotation>
-            <xs:documentation><![CDATA[A list of feature names (and related OSGi clause attributes) that should be
-blacklisted.
+            <xs:documentation><![CDATA[A list of feature identifiers that should be blacklisted.
 
 Attempt to install such feature will be prohibited, but such feature is still available in `feature:list`
 output and marked as blacklisted. When custom Karaf distribution is assembled, blacklisted features' bundles are not
 taken into account (are not declared in "etc/startup.properties" and "etc/org.apache.karaf.features.cfg" and are not
 copied to "system/" directory).
-
-Feature names may use '*' glob (not RegExp) in names, "range" attribute is used in "etc/blacklisted.properties" file
-and may be used to specify version range, e.g., "*jclouds*;range=[1,3)". When using XML to configure blacklisted
-features, "range" manifest header attribute should be specified in "version" XML attribute.
 ]]></xs:documentation>
         </xs:annotation>
         <xs:sequence>
@@ -102,11 +97,8 @@ When feature is installed, all blacklisted bundles are skipped.
 When custom Karaf distribution is assembled, blacklisted bundles are not taken into account (are not declared in
 "etc/startup.properties" and are not copied to "system/" directory).
 
-Bundle URIs may use '*' globs (not RegExp), e.g., "*jclouds*;url=mvn:...". Both header-like format may be used
-(as in etc/blacklisted.properties, triggered, when ';' is used) or plain URI format for bundle locations.
-
 Bundle URIs should use 'mvn:' scheme, may use '*' glob (not RegExp) for all components except versions and
-version ranges may be specified as maven versions, e.g., "mvn:group/artifact/[3,5)/classifier". At least
+version ranges may be specified as maven versions, e.g., "mvn:group/artifact/[3,5)/type/classifier". At least
 groupId and artifactId should be specified. In most abstract case, "mvn:*/*" describes all maven URIs.
 
 This element may be used instead of "etc/blacklisted.properties" file for bundle blacklisting.
@@ -137,7 +129,8 @@ override this flag for any repository, feature or particular bundles.
     <xs:complexType name="overrideDependency">
         <xs:annotation>
             <xs:documentation><![CDATA[An URI of depedency (bundle or repository) should use 'mvn:' scheme.
-Maven schemes allow parsing version/version ranges.
+Maven schemes allow parsing version/version ranges. "dependency" flag will overwrite dependency attribute of related
+features (in repository) and bundles.
 ]]></xs:documentation>
         </xs:annotation>
         <xs:attribute name="uri" type="xs:anyURI" />
@@ -145,6 +138,11 @@ Maven schemes allow parsing version/version ranges.
     </xs:complexType>
 
     <xs:complexType name="overrideFeatureDependency">
+        <xs:annotation>
+            <xs:documentation><![CDATA[After matching feature by name (may use "*" glob) and version (or range), we
+can overwrite "dependency" attribute on given feature
+]]></xs:documentation>
+        </xs:annotation>
         <xs:attribute name="name" type="xs:string" />
         <xs:attribute name="version" type="xs:string" />
         <xs:attribute name="dependency" type="xs:boolean" default="false" />
@@ -158,8 +156,6 @@ names match. With bundleReplacements element, its possible to do the same and mo
 with totally different bundle (in terms of OSGi's Symbolic-Name and Maven groupId/artifactId/version). That's useful
 especially with JavaEE API bundles, where single API may be provided by different bundles (ServiceMix, Geronimo,
 JBoss, javax.*, ...).
-
-Bundle replacement doesn't use globs - just version ranges to match replacement candidates
 ]]></xs:documentation>
         </xs:annotation>
         <xs:sequence>
diff --git a/features/core/src/test/java/org/apache/karaf/features/internal/service/FeaturesProcessorTest.java b/features/core/src/test/java/org/apache/karaf/features/internal/service/FeaturesProcessorTest.java
index ce96722..63cc100 100644
--- a/features/core/src/test/java/org/apache/karaf/features/internal/service/FeaturesProcessorTest.java
+++ b/features/core/src/test/java/org/apache/karaf/features/internal/service/FeaturesProcessorTest.java
@@ -25,9 +25,12 @@ import javax.xml.bind.Marshaller;
 import org.apache.felix.utils.manifest.Clause;
 import org.apache.felix.utils.version.VersionRange;
 import org.apache.karaf.features.Feature;
+import org.apache.karaf.features.internal.model.Bundle;
 import org.apache.karaf.features.internal.model.processing.BundleReplacements;
+import org.apache.karaf.features.internal.model.processing.FeatureReplacements;
 import org.apache.karaf.features.internal.model.processing.FeaturesProcessing;
 import org.apache.karaf.features.internal.model.processing.ObjectFactory;
+import org.apache.karaf.features.internal.model.processing.OverrideBundleDependency;
 import org.apache.karaf.util.maven.Parser;
 import org.junit.Test;
 import org.slf4j.Logger;
@@ -120,7 +123,7 @@ public class FeaturesProcessorTest {
         assertThat(clauses.length, equalTo(4));
         assertTrue(blacklist.isFeatureBlacklisted("spring", "2.5.6.SEC02"));
         assertFalse(blacklist.isFeatureBlacklisted("spring", "2.5.7.SEC02"));
-        assertTrue(blacklist.isFeatureBlacklisted("jclouds", "42"));
+        assertFalse(blacklist.isFeatureBlacklisted("jclouds", "1"));
 
         assertTrue(blacklist.isBundleBlacklisted("mvn:org.spring/spring-infinity/42"));
         assertFalse(blacklist.isBundleBlacklisted("mvn:org.spring/spring-infinity/41"));
@@ -137,9 +140,9 @@ public class FeaturesProcessorTest {
         RepositoryImpl repo = (RepositoryImpl) new RepositoryCacheImpl(processor).create(uri, true);
         assertThat(repo.getRepositories().length, equalTo(3));
         assertFalse(repo.isBlacklisted());
-        assertFalse(processor.isRepositoryBlacklisted(repo.getRepositories()[0]));
-        assertTrue(processor.isRepositoryBlacklisted(repo.getRepositories()[1]));
-        assertFalse(processor.isRepositoryBlacklisted(repo.getRepositories()[2]));
+        assertFalse(processor.isRepositoryBlacklisted(repo.getRepositories()[0].toString()));
+        assertTrue(processor.isRepositoryBlacklisted(repo.getRepositories()[1].toString()));
+        assertFalse(processor.isRepositoryBlacklisted(repo.getRepositories()[2].toString()));
     }
 
     @Test
@@ -197,4 +200,35 @@ public class FeaturesProcessorTest {
         assertFalse(f1.getConditional().get(0).getBundles().get(1).isOverriden());
     }
 
+    @Test
+    public void serializeWithComments() {
+        FeaturesProcessingSerializer serializer = new FeaturesProcessingSerializer();
+        FeaturesProcessing featuresProcessing = new FeaturesProcessing();
+        featuresProcessing.getBlacklistedRepositories().add("repository 1");
+        OverrideBundleDependency.OverrideDependency d1 = new OverrideBundleDependency.OverrideDependency();
+        d1.setDependency(true);
+        d1.setUri("uri 1");
+        featuresProcessing.getOverrideBundleDependency().getRepositories().add(d1);
+        OverrideBundleDependency.OverrideFeatureDependency d2 = new OverrideBundleDependency.OverrideFeatureDependency();
+        d2.setDependency(false);
+        d2.setName("n");
+        d2.setVersion("1.2.3");
+        featuresProcessing.getOverrideBundleDependency().getFeatures().add(d2);
+        BundleReplacements.OverrideBundle override = new BundleReplacements.OverrideBundle();
+        override.setOriginalUri("original");
+        override.setReplacement("replacement");
+        override.setMode(BundleReplacements.BundleOverrideMode.OSGI);
+        featuresProcessing.getBundleReplacements().getOverrideBundles().add(override);
+        FeatureReplacements.OverrideFeature of = new FeatureReplacements.OverrideFeature();
+        of.setMode(FeatureReplacements.FeatureOverrideMode.REPLACE);
+        org.apache.karaf.features.internal.model.Feature f = new org.apache.karaf.features.internal.model.Feature();
+        f.setName("f1");
+        Bundle b = new Bundle();
+        b.setLocation("location");
+        f.getBundle().add(b);
+        of.setFeature(f);
+        featuresProcessing.getFeatureReplacements().getReplacements().add(of);
+        serializer.write(featuresProcessing, System.out);
+    }
+
 }
diff --git a/profile/src/main/java/org/apache/karaf/profile/assembly/ArtifactInstaller.java b/profile/src/main/java/org/apache/karaf/profile/assembly/ArtifactInstaller.java
index c06ebf2..967a979 100644
--- a/profile/src/main/java/org/apache/karaf/profile/assembly/ArtifactInstaller.java
+++ b/profile/src/main/java/org/apache/karaf/profile/assembly/ArtifactInstaller.java
@@ -16,15 +16,12 @@
  */
 package org.apache.karaf.profile.assembly;
 
-import static org.apache.karaf.features.internal.download.impl.DownloadManagerHelper.stripUrl;
-
 import java.net.MalformedURLException;
 import java.net.URI;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.nio.file.StandardCopyOption;
-import java.util.List;
 
 import org.apache.karaf.features.internal.download.Downloader;
 import org.apache.karaf.features.internal.service.Blacklist;
@@ -32,6 +29,8 @@ import org.apache.karaf.util.maven.Parser;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import static org.apache.karaf.features.internal.download.impl.DownloadManagerHelper.stripUrl;
+
 /**
  * Downloads a maven artifact and installs it into the given system directory.
  * The layout follows the conventions of a maven local repository.
@@ -43,10 +42,10 @@ public class ArtifactInstaller {
     private Downloader downloader;
     private Blacklist blacklist;
 
-    public ArtifactInstaller(Path systemDirectory, Downloader downloader, List<String> blacklisted) {
+    public ArtifactInstaller(Path systemDirectory, Downloader downloader, Blacklist blacklist) {
         this.systemDirectory = systemDirectory;
         this.downloader = downloader;
-        this.blacklist = new Blacklist(blacklisted);
+        this.blacklist = blacklist;
     }
     
     public void installArtifact(String location) throws Exception {
diff --git a/profile/src/main/java/org/apache/karaf/profile/assembly/AssemblyDeployCallback.java b/profile/src/main/java/org/apache/karaf/profile/assembly/AssemblyDeployCallback.java
index 2be1d6f..6e854dc 100644
--- a/profile/src/main/java/org/apache/karaf/profile/assembly/AssemblyDeployCallback.java
+++ b/profile/src/main/java/org/apache/karaf/profile/assembly/AssemblyDeployCallback.java
@@ -51,7 +51,6 @@ import org.apache.karaf.features.internal.util.MapUtils;
 import org.apache.karaf.util.maven.Parser;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleException;
-import org.osgi.framework.InvalidSyntaxException;
 import org.osgi.framework.startlevel.BundleStartLevel;
 import org.osgi.framework.wiring.BundleRevision;
 import org.slf4j.Logger;
@@ -74,12 +73,11 @@ public class AssemblyDeployCallback extends StaticInstallSupport implements Depl
 
     private final Map<String, Bundle> bundles = new HashMap<>();
 
-
-    public AssemblyDeployCallback(DownloadManager manager, Builder builder, BundleRevision systemBundle, Collection<Features> repositories) throws Exception {
+    public AssemblyDeployCallback(DownloadManager manager, Builder builder, BundleRevision systemBundle, Collection<Features> repositories) {
         this.manager = manager;
         this.builder = builder;
-        this.featureBlacklist = new Blacklist(builder.getBlacklistedFeatures());
-        this.bundleBlacklist = new Blacklist(builder.getBlacklistedBundles());
+//        this.featureBlacklist = new Blacklist(builder.getBlacklistedFeatures());
+//        this.bundleBlacklist = new Blacklist(builder.getBlacklistedBundles());
         this.homeDirectory = builder.homeDirectory;
         this.etcDirectory = homeDirectory.resolve("etc");
         this.systemDirectory = homeDirectory.resolve("system");
@@ -122,11 +120,11 @@ public class AssemblyDeployCallback extends StaticInstallSupport implements Depl
     }
 
     @Override
-    public void persistResolveRequest(Deployer.DeploymentRequest request) throws IOException {
+    public void persistResolveRequest(Deployer.DeploymentRequest request) {
     }
 
     @Override
-    public void installConfigs(org.apache.karaf.features.Feature feature) throws IOException, InvalidSyntaxException {
+    public void installConfigs(org.apache.karaf.features.Feature feature) throws IOException {
         assertNotBlacklisted(feature);
         // Install
         Downloader downloader = manager.createDownloader();
@@ -195,11 +193,11 @@ public class AssemblyDeployCallback extends StaticInstallSupport implements Depl
     }
     
     private void assertNotBlacklisted(org.apache.karaf.features.Feature feature) {
-        if (featureBlacklist.isFeatureBlacklisted(feature.getName(), feature.getVersion())) {
-            if (builder.getBlacklistPolicy() == Builder.BlacklistPolicy.Fail) {
-                throw new RuntimeException("Feature " + feature.getId() + " is blacklisted");
-            }
-        }
+//        if (featureBlacklist.isFeatureBlacklisted(feature.getName(), feature.getVersion())) {
+//            if (builder.getBlacklistPolicy() == Builder.BlacklistPolicy.Fail) {
+//                throw new RuntimeException("Feature " + feature.getId() + " is blacklisted");
+//            }
+//        }
     }
 
     @Override
@@ -213,11 +211,11 @@ public class AssemblyDeployCallback extends StaticInstallSupport implements Depl
     @Override
     public Bundle installBundle(String region, String uri, InputStream is) throws BundleException {
         // Check blacklist
-        if (bundleBlacklist.isBundleBlacklisted(uri)) {
-            if (builder.getBlacklistPolicy() == Builder.BlacklistPolicy.Fail) {
-                throw new RuntimeException("Bundle " + uri + " is blacklisted");
-            }
-        }
+//        if (bundleBlacklist.isBundleBlacklisted(uri)) {
+//            if (builder.getBlacklistPolicy() == Builder.BlacklistPolicy.Fail) {
+//                throw new RuntimeException("Bundle " + uri + " is blacklisted");
+//            }
+//        }
         // Install
         LOGGER.info("      adding maven artifact: " + uri);
         try {
diff --git a/profile/src/main/java/org/apache/karaf/profile/assembly/Builder.java b/profile/src/main/java/org/apache/karaf/profile/assembly/Builder.java
index 672d166..960a067 100644
--- a/profile/src/main/java/org/apache/karaf/profile/assembly/Builder.java
+++ b/profile/src/main/java/org/apache/karaf/profile/assembly/Builder.java
@@ -19,18 +19,23 @@ package org.apache.karaf.profile.assembly;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.MalformedURLException;
 import java.net.URI;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.*;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystemNotFoundException;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Dictionary;
-import java.util.EnumSet;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Hashtable;
@@ -49,6 +54,7 @@ import java.util.concurrent.ScheduledExecutorService;
 import java.util.function.Function;
 import java.util.jar.Attributes;
 import java.util.jar.Manifest;
+import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipInputStream;
@@ -76,6 +82,9 @@ import org.apache.karaf.features.internal.repository.BaseRepository;
 import org.apache.karaf.features.internal.resolver.ResourceBuilder;
 import org.apache.karaf.features.internal.service.Blacklist;
 import org.apache.karaf.features.internal.service.Deployer;
+import org.apache.karaf.features.internal.service.FeaturesProcessor;
+import org.apache.karaf.features.internal.service.FeaturesProcessorImpl;
+import org.apache.karaf.features.internal.service.Overrides;
 import org.apache.karaf.features.internal.util.MapUtils;
 import org.apache.karaf.features.internal.util.MultiException;
 import org.apache.karaf.kar.internal.Kar;
@@ -217,6 +226,41 @@ public class Builder {
         }
     }
 
+    /**
+     * Class similar to {@link FeaturePattern} but simplified for profile name matching
+     */
+    private static class ProfileNamePattern {
+        private String name;
+        private Pattern namePattern;
+
+
+        public ProfileNamePattern(String profileName) {
+            if (profileName == null) {
+                throw new IllegalArgumentException("Profile name to match should not be null");
+            }
+            name = profileName;
+            if (name.contains("*")) {
+                namePattern = LocationPattern.toRegExp(name);
+            }
+        }
+
+        /**
+         * Returns <code>if this feature pattern</code> matches given feature/version
+         * @param profileName
+         * @return
+         */
+        public boolean matches(String profileName) {
+            if (profileName == null) {
+                return false;
+            }
+            if (namePattern != null) {
+                return namePattern.matcher(profileName).matches();
+            } else {
+                return name.equals(profileName);
+            }
+        }
+    }
+
     //
     // Input parameters
     //
@@ -229,10 +273,10 @@ public class Builder {
     Map<String, RepositoryInfo> repositories = new LinkedHashMap<>();
     Map<String, Stage> features = new LinkedHashMap<>();
     Map<String, Stage> bundles = new LinkedHashMap<>();
-    List<String> blacklistedProfiles = new ArrayList<>();
-    List<String> blacklistedFeatures = new ArrayList<>();
-    List<String> blacklistedBundles = new ArrayList<>();
-    List<String> blacklistedRepositories = new ArrayList<>();
+    List<String> blacklistedProfileNames = new ArrayList<>();
+    List<String> blacklistedFeatureIdentifiers = new ArrayList<>();
+    List<String> blacklistedBundleURIs = new ArrayList<>();
+    List<String> blacklistedRepositoryURIs = new ArrayList<>();
     BlacklistPolicy blacklistPolicy = BlacklistPolicy.Discard;
     List<String> libraries = new ArrayList<>();
     JavaVersion javase = JavaVersion.Java18;
@@ -259,6 +303,7 @@ public class Builder {
     private KarafPropertyEdits propertyEdits;
     private FeaturesProcessing featuresProcessing = new FeaturesProcessing();
     private Map<String, String> translatedUrls;
+    private Blacklist blacklist;
 
     private Function<MavenResolver, MavenResolver> resolverWrapper = Function.identity();
 
@@ -622,7 +667,7 @@ public class Builder {
      * @return
      */
     public Builder blacklistProfiles(Collection<String> profiles) {
-        this.blacklistedProfiles.addAll(profiles);
+        this.blacklistedProfileNames.addAll(profiles);
         return this;
     }
 
@@ -632,7 +677,7 @@ public class Builder {
      * @return
      */
     public Builder blacklistFeatures(Collection<String> features) {
-        this.blacklistedFeatures.addAll(features);
+        this.blacklistedFeatureIdentifiers.addAll(features);
         return this;
     }
 
@@ -642,7 +687,7 @@ public class Builder {
      * @return
      */
     public Builder blacklistBundles(Collection<String> bundles) {
-        this.blacklistedBundles.addAll(bundles);
+        this.blacklistedBundleURIs.addAll(bundles);
         return this;
     }
 
@@ -652,7 +697,7 @@ public class Builder {
      * @return
      */
     public Builder blacklistRepositories(Collection<String> repositories) {
-        this.blacklistedRepositories.addAll(repositories);
+        this.blacklistedRepositoryURIs.addAll(repositories);
         return this;
     }
 
@@ -724,20 +769,20 @@ public class Builder {
         return this;
     }
 
-    public List<String> getBlacklistedProfiles() {
-        return blacklistedProfiles;
+    public List<String> getBlacklistedProfileNames() {
+        return blacklistedProfileNames;
     }
 
-    public List<String> getBlacklistedFeatures() {
-        return blacklistedFeatures;
+    public List<String> getBlacklistedFeatureIdentifiers() {
+        return blacklistedFeatureIdentifiers;
     }
 
-    public List<String> getBlacklistedBundles() {
-        return blacklistedBundles;
+    public List<String> getBlacklistedBundleURIs() {
+        return blacklistedBundleURIs;
     }
 
-    public List<String> getBlacklistedRepositories() {
-        return blacklistedRepositories;
+    public List<String> getBlacklistedRepositoryURIs() {
+        return blacklistedRepositoryURIs;
     }
 
     public BlacklistPolicy getBlacklistPolicy() {
@@ -807,16 +852,39 @@ public class Builder {
         }
 
         //
+        // Handle blacklist - we'll use SINGLE instance iof Blacklist for all further downloads
+        //
+        blacklist = processBlacklist();
+
+        // we can't yet have full feature processor, because some overrides may be defined in profiles and
+        // profiles are generated after reading features from repositories
+        // so for now, we can only configure blacklisting features processor
+
+        Path existingProcessorDefinition = etcDirectory.resolve("org.apache.karaf.features.xml");
+        String existingProcessorDefinitionURI = null;
+        if (existingProcessorDefinition.toFile().isFile()) {
+            existingProcessorDefinitionURI = existingProcessorDefinition.toFile().toURI().toString();
+        }
+        // now we can configure blacklisting features processor which may have already defined (in XML)
+        // configuration for bundle replacements or feature overrides.
+        // we'll add overrides from profiles later.
+        FeaturesProcessorImpl processor = new FeaturesProcessorImpl(existingProcessorDefinitionURI, blacklist, new HashSet<>());
+
+        //
         // Propagate feature installation from repositories
         //
         LOGGER.info("Loading repositories");
-        Map<String, Features> karRepositories = loadRepositories(manager, repositories.keySet(), false);
+        Map<String, Features> karRepositories = loadRepositories(manager, repositories.keySet(), false, processor);
         for (String repo : repositories.keySet()) {
             RepositoryInfo info = repositories.get(repo);
             if (info.addAll) {
-                LOGGER.info("   adding all features from repository: " + repo + " (stage: " + info.stage + ")");
+                LOGGER.info("   adding all non-blacklisted features from repository: " + repo + " (stage: " + info.stage + ")");
                 for (Feature feature : karRepositories.get(repo).getFeature()) {
-                    features.put(feature.getId(), info.stage);
+                    if (feature.isBlacklisted()) {
+                        LOGGER.info("      feature {}/{} is blacklisted - skipping.", feature.getId(), feature.getVersion());
+                    } else {
+                        features.put(feature.getId(), info.stage);
+                    }
                 }
             }
         }
@@ -868,6 +936,14 @@ public class Builder {
         // so property placeholders are preserved - like ${karaf.base})
         Profile overallEffective = Profiles.getEffective(overallOverlay, false);
 
+        //
+        // Handle overrides - existing (unzipped from KAR) and defined in profile
+        //
+        Set<String> overrides = processOverrides(overallEffective.getOverrides());
+
+        // we can now add overrides from profiles.
+        processor.addOverrides(overrides);
+
         if (writeProfiles) {
             Path profiles = etcDirectory.resolve("profiles");
             LOGGER.info("Adding profiles to {}", homeDirectory.relativize(profiles));
@@ -938,57 +1014,101 @@ public class Builder {
             editor.run();
         }
 
-        //
-        // Handle overrides
-        //
-        if (!overallEffective.getOverrides().isEmpty()) {
-            Path overrides = etcDirectory.resolve("overrides.properties");
-            List<String> lines = new ArrayList<>();
-            lines.add("#");
-            lines.add("# Generated by the karaf assembly builder");
-            lines.add("#");
-            lines.addAll(overallEffective.getOverrides());
-            LOGGER.info("Generating {}", homeDirectory.relativize(overrides));
-            Files.write(overrides, lines, StandardCharsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
-        }
-
-        //
-        // Handle blacklist
-        //
-        if (!blacklistedFeatures.isEmpty() || !blacklistedBundles.isEmpty()) {
-            Path blacklist = etcDirectory.resolve("blacklisted.properties");
-            List<String> lines = new ArrayList<>();
-            lines.add("#");
-            lines.add("# Generated by the karaf assembly builder");
-            lines.add("#");
-            if (!blacklistedFeatures.isEmpty()) {
-                lines.add("");
-                lines.add("# Features");
-                lines.addAll(blacklistedFeatures);
-            }
-            if (!blacklistedBundles.isEmpty()) {
-                lines.add("");
-                lines.add("# Bundles");
-                lines.addAll(blacklistedBundles);
+//        if (!blacklistedFeatures.isEmpty() || !blacklistedBundles.isEmpty()) {
+//            List<String> lines = new ArrayList<>();
+//            lines.add("#");
+//            lines.add("# Generated by the karaf assembly builder");
+//            lines.add("#");
+//            if (!blacklistedFeatures.isEmpty()) {
+//                lines.add("");
+//                lines.add("# Features");
+//                lines.addAll(blacklistedFeatures);
+//            }
+//            if (!blacklistedBundles.isEmpty()) {
+//                lines.add("");
+//                lines.add("# Bundles");
+//                lines.addAll(blacklistedBundles);
+//            }
+//            LOGGER.info("Generating {}", homeDirectory.relativize(blacklist));
+//            Files.write(blacklist, lines, StandardCharsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
+//        }
+
+        // TODO: download overrides, implement fuller override clauses (original->replacement)
+        if (processor.hasInstructions()) {
+            Path featuresProcessingXml = etcDirectory.resolve("org.apache.karaf.features.xml");
+            try (FileOutputStream fos = new FileOutputStream(featuresProcessingXml.toFile())) {
+                processor.writeInstructions(fos);
             }
-            LOGGER.info("Generating {}", homeDirectory.relativize(blacklist));
-            Files.write(blacklist, lines, StandardCharsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
         }
 
         //
         // Startup stage
         //
-        Profile startupEffective = startupStage(startupProfile);
+        Profile startupEffective = startupStage(startupProfile, processor);
 
         //
         // Boot stage
         //
-        Set<Feature> allBootFeatures = bootStage(bootProfile, startupEffective);
+        Set<Feature> allBootFeatures = bootStage(bootProfile, startupEffective, processor);
 
         //
         // Installed stage
         //
-        installStage(installedProfile, allBootFeatures);
+        installStage(installedProfile, allBootFeatures, processor);
+    }
+
+    /**
+     * Checks existing (etc/overrides.properties) and configured (in profiles) overrides definitions
+     * @param profileOverrides
+     * @return
+     */
+    private Set<String> processOverrides(List<String> profileOverrides) {
+        Set<String> result = new LinkedHashSet<>();
+        Path existingOverridesLocation = etcDirectory.resolve("overrides.properties");
+        if (existingOverridesLocation.toFile().isFile()) {
+            LOGGER.warn("Found {} which is deprecated, please use new feature processor configuration.", homeDirectory.relativize(existingOverridesLocation));
+            result.addAll(Overrides.loadOverrides(existingOverridesLocation.toFile().toURI().toString()));
+        }
+        result.addAll(profileOverrides);
+
+        return result;
+    }
+
+    /**
+     * Checks existing and configured blacklisting definitions
+     * @return
+     * @throws IOException
+     */
+    private Blacklist processBlacklist() throws IOException {
+        Blacklist existingBlacklist = null;
+        Blacklist blacklist = new Blacklist();
+        Path existingBLacklistedLocation = etcDirectory.resolve("blacklisted.properties");
+        if (existingBLacklistedLocation.toFile().isFile()) {
+            LOGGER.warn("Found {} which is deprecated, please use new feature processor configuration.", homeDirectory.relativize(existingBLacklistedLocation));
+            existingBlacklist = new Blacklist(Files.readAllLines(existingBLacklistedLocation));
+        }
+        for (String br : blacklistedRepositoryURIs) {
+            try {
+                blacklist.blacklistRepository(new LocationPattern(br));
+            } catch (MalformedURLException e) {
+                LOGGER.warn("Blacklisted features XML repository URI is invalid {}, ignoring", br);
+            }
+        }
+        for (String bf : blacklistedFeatureIdentifiers) {
+            blacklist.blacklistFeature(new FeaturePattern(bf));
+        }
+        for (String bb : blacklistedBundleURIs) {
+            try {
+                blacklist.blacklistBundle(new LocationPattern(bb));
+            } catch (MalformedURLException e) {
+                LOGGER.warn("Blacklisted bundle URI is invalid {}, ignoring", bb);
+            }
+        }
+        if (existingBlacklist != null) {
+            blacklist.merge(existingBlacklist);
+        }
+
+        return blacklist;
     }
 
     private MavenResolver createMavenResolver() {
@@ -1012,6 +1132,8 @@ public class Builder {
      */
     private Map<String, Profile> loadExternalProfiles(List<String> profilesUris) throws IOException, MultiException, InterruptedException {
         Map<String, Profile> profiles = new LinkedHashMap<>();
+        Map<String, Profile> filteredProfiles = new LinkedHashMap<>();
+
         for (String profilesUri : profilesUris) {
             String uri = profilesUri;
             if (uri.startsWith("jar:") && uri.contains("!/")) {
@@ -1035,19 +1157,33 @@ public class Builder {
             }
             profiles.putAll(Profiles.loadProfiles(profilePath));
             // Handle blacklisted profiles
-            if (!blacklistedProfiles.isEmpty()) {
-                if (blacklistPolicy == BlacklistPolicy.Discard) {
-                    // Override blacklisted profiles with empty ones
-                    for (String profile : blacklistedProfiles) {
-                        profiles.put(profile, ProfileBuilder.Factory.create(profile).getProfile());
+            List<ProfileNamePattern> blacklistedProfilePatterns = blacklistedProfileNames.stream()
+                    .map(ProfileNamePattern::new).collect(Collectors.toList());
+
+            for (String profileName : profiles.keySet()) {
+                boolean blacklisted = false;
+                for (ProfileNamePattern pattern : blacklistedProfilePatterns) {
+                    if (pattern.matches(profileName)) {
+                        LOGGER.info("   blacklisting profile {} from {}", profileName, profilePath);
+                        // TODO review blacklist policy options
+                        if (blacklistPolicy == BlacklistPolicy.Discard) {
+                            // Override blacklisted profiles with empty one
+                            filteredProfiles.put(profileName, ProfileBuilder.Factory.create(profileName).getProfile());
+                        } else {
+                            // Remove profile completely
+                        }
+                        // no need to check other patterns
+                        blacklisted = true;
+                        break;
                     }
-                } else {
-                    // Remove profiles completely
-                    profiles.keySet().removeAll(blacklistedProfiles);
+                }
+                if (!blacklisted) {
+                    filteredProfiles.put(profileName, profiles.get(profileName));
                 }
             }
         }
-        return profiles;
+
+        return filteredProfiles;
     }
 
     private void reformatClauses(Properties config, String key) {
@@ -1137,18 +1273,18 @@ public class Builder {
                         if (packages != null) {
                             Clause[] clauses1 = org.apache.felix.utils.manifest.Parser.parseHeader(packages);
                             if (export) {
-                                String val = config.getProperty(Constants.FRAMEWORK_SYSTEMPACKAGES_EXTRA);
+                                StringBuilder val = new StringBuilder(config.getProperty(Constants.FRAMEWORK_SYSTEMPACKAGES_EXTRA));
                                 for (Clause clause1 : clauses1) {
-                                    val += "," + clause1.toString();
+                                    val.append(",").append(clause1.toString());
                                 }
-                                config.setProperty(Constants.FRAMEWORK_SYSTEMPACKAGES_EXTRA, val);
+                                config.setProperty(Constants.FRAMEWORK_SYSTEMPACKAGES_EXTRA, val.toString());
                             }
                             if (delegate) {
-                                String val = config.getProperty(Constants.FRAMEWORK_BOOTDELEGATION);
+                                StringBuilder val = new StringBuilder(config.getProperty(Constants.FRAMEWORK_BOOTDELEGATION));
                                 for (Clause clause1 : clauses1) {
-                                    val += "," + clause1.getName();
+                                    val.append(",").append(clause1.getName());
                                 }
-                                config.setProperty(Constants.FRAMEWORK_BOOTDELEGATION, val);
+                                config.setProperty(Constants.FRAMEWORK_BOOTDELEGATION, val.toString());
                             }
                         }
                     }
@@ -1156,7 +1292,7 @@ public class Builder {
         }
     }
 
-    private void installStage(Profile installedProfile, Set<Feature> allBootFeatures) throws Exception {
+    private void installStage(Profile installedProfile, Set<Feature> allBootFeatures, FeaturesProcessor processor) throws Exception {
         LOGGER.info("Install stage");
         //
         // Handle installed profiles
@@ -1168,7 +1304,7 @@ public class Builder {
 
         // Load startup repositories
         LOGGER.info("   Loading repositories");
-        Map<String, Features> installedRepositories = loadRepositories(manager, installedEffective.getRepositories(), true);
+        Map<String, Features> installedRepositories = loadRepositories(manager, installedEffective.getRepositories(), true, processor);
         // Compute startup feature dependencies
         Set<Feature> allInstalledFeatures = new HashSet<>();
         for (Features repo : installedRepositories.values()) {
@@ -1179,7 +1315,7 @@ public class Builder {
         allInstalledFeatures.addAll(allBootFeatures);
         FeatureSelector selector = new FeatureSelector(allInstalledFeatures);
         Set<Feature> installedFeatures = selector.getMatching(installedEffective.getFeatures());
-        ArtifactInstaller installer = new ArtifactInstaller(systemDirectory, downloader, blacklistedBundles);
+        ArtifactInstaller installer = new ArtifactInstaller(systemDirectory, downloader, blacklist);
         for (Feature feature : installedFeatures) {
             LOGGER.info("   Feature {} is defined as an installed feature", feature.getId());
             for (Bundle bundle : feature.getBundle()) {
@@ -1205,7 +1341,7 @@ public class Builder {
         downloader.await();
     }
 
-    private Set<Feature> bootStage(Profile bootProfile, Profile startupEffective) throws Exception {
+    private Set<Feature> bootStage(Profile bootProfile, Profile startupEffective, FeaturesProcessor processor) throws Exception {
         LOGGER.info("Boot stage");
         //
         // Handle boot profiles
@@ -1214,7 +1350,7 @@ public class Builder {
         Profile bootEffective = Profiles.getEffective(bootOverlay, false);
         // Load startup repositories
         LOGGER.info("   Loading repositories");
-        Map<String, Features> bootRepositories = loadRepositories(manager, bootEffective.getRepositories(), true);
+        Map<String, Features> bootRepositories = loadRepositories(manager, bootEffective.getRepositories(), true, processor);
         // Compute startup feature dependencies
         Set<Feature> allBootFeatures = new HashSet<>();
         for (Features repo : bootRepositories.values()) {
@@ -1276,7 +1412,7 @@ public class Builder {
             prereqs.put("spring:", Arrays.asList("deployer", "spring"));
             prereqs.put("wrap:", Collections.singletonList("wrap"));
             prereqs.put("war:", Collections.singletonList("war"));
-            ArtifactInstaller installer = new ArtifactInstaller(systemDirectory, downloader, blacklistedBundles);
+            ArtifactInstaller installer = new ArtifactInstaller(systemDirectory, downloader, blacklist);
             for (String location : locations) {
                 installer.installArtifact(location);
                 for (Map.Entry<String, List<String>> entry : prereqs.entrySet()) {
@@ -1365,8 +1501,6 @@ public class Builder {
         return allBootFeatures;
     }
 
-
-
     private String getRepos(Features rep) {
         StringBuilder repos = new StringBuilder();
         for (String repo : new HashSet<>(rep.getRepository())) {
@@ -1424,7 +1558,7 @@ public class Builder {
         return dep;
     }
 
-    private Profile startupStage(Profile startupProfile) throws Exception {
+    private Profile startupStage(Profile startupProfile, FeaturesProcessor processor) throws Exception {
         LOGGER.info("Startup stage");
         //
         // Compute startup
@@ -1433,7 +1567,7 @@ public class Builder {
         Profile startupEffective = Profiles.getEffective(startupOverlay, false);
         // Load startup repositories
         LOGGER.info("   Loading repositories");
-        Map<String, Features> startupRepositories = loadRepositories(manager, startupEffective.getRepositories(), false);
+        Map<String, Features> startupRepositories = loadRepositories(manager, startupEffective.getRepositories(), false, processor);
 
         //
         // Resolve
@@ -1511,21 +1645,15 @@ public class Builder {
         return staged;
     }
 
-    private Map<String, Features> loadRepositories(DownloadManager manager, Collection<String> repositories, final boolean install) throws Exception {
+    private Map<String, Features> loadRepositories(DownloadManager manager, Collection<String> repositories, final boolean install, FeaturesProcessor processor) throws Exception {
         final Map<String, Features> loaded = new HashMap<>();
         final Downloader downloader = manager.createDownloader();
-        final List<String> blacklist = new ArrayList<>();
-        blacklist.addAll(blacklistedBundles);
-        blacklist.addAll(blacklistedFeatures);
-        final List<String> blacklistRepos = new ArrayList<>(blacklistedRepositories);
-        final Blacklist blacklistOther = new Blacklist(blacklist);
-        final Blacklist repoBlacklist = new Blacklist(blacklistRepos);
         for (String repository : repositories) {
             downloader.download(repository, new DownloadCallback() {
                 @Override
                 public void downloaded(final StreamProvider provider) throws Exception {
                     String url = provider.getUrl();
-                    if (repoBlacklist.isRepositoryBlacklisted(url)) {
+                    if (processor.isRepositoryBlacklisted(url)) {
                         LOGGER.info("   feature repository " + url + " is blacklisted");
                         return;
                     }
@@ -1541,9 +1669,11 @@ public class Builder {
                             }
                             try (InputStream is = provider.open()) {
                                 Features featuresModel = JaxbUtil.unmarshal(url, is, false);
-                                if (blacklistPolicy == BlacklistPolicy.Discard) {
-                                    blacklistOther.blacklist(featuresModel);
-                                }
+                                // always process according to processor configuration
+                                processor.process(featuresModel);
+                                // TODO consult blacklist policy
+//                                if (blacklistPolicy == BlacklistPolicy.Discard) {
+//                                }
                                 loaded.put(provider.getUrl(), featuresModel);
                                 for (String innerRepository : featuresModel.getRepository()) {
                                     downloader.download(innerRepository, this);
@@ -1598,7 +1728,7 @@ public class Builder {
         Deployer deployer = new Deployer(manager, resolver, callback);
 
         // Install framework
-        Deployer.DeploymentRequest request = createDeploymentRequest();
+        Deployer.DeploymentRequest request = Deployer.DeploymentRequest.defaultDeploymentRequest();
         // Add overrides
         request.overrides.addAll(overrides);
         // Add optional resources
@@ -1638,18 +1768,6 @@ public class Builder {
         return callback.getStartupBundles();
     }
 
-    private Deployer.DeploymentRequest createDeploymentRequest() {
-        Deployer.DeploymentRequest request = new Deployer.DeploymentRequest();
-        request.bundleUpdateRange = FeaturesService.DEFAULT_BUNDLE_UPDATE_RANGE;
-        request.featureResolutionRange = FeaturesService.DEFAULT_FEATURE_RESOLUTION_RANGE;
-        request.serviceRequirements = FeaturesService.SERVICE_REQUIREMENTS_DEFAULT;
-        request.overrides = new HashSet<>();
-        request.requirements = new HashMap<>();
-        request.stateChanges = new HashMap<>();
-        request.options = EnumSet.noneOf(FeaturesService.Option.class);
-        return request;
-    }
-
     @SuppressWarnings("rawtypes")
     private BundleRevision getSystemBundle() throws Exception {
         Path configPropPath = etcDirectory.resolve("config.properties");
diff --git a/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/AssemblyMojo.java b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/AssemblyMojo.java
index f8d78b9..08f41cb 100644
--- a/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/AssemblyMojo.java
+++ b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/AssemblyMojo.java
@@ -149,6 +149,12 @@ public class AssemblyMojo extends MojoSupport {
     private String environment;
 
     /**
+     * Default start level for bundles in features that don't specify it.
+     */
+    @Parameter
+    protected int defaultStartLevel = 30;
+
+    /**
      * List of compile-scope features XML files to be used in startup stage (etc/startup.properties)
      */
     @Parameter
@@ -455,6 +461,7 @@ public class AssemblyMojo extends MojoSupport {
         builder.pidsToExtract(pidsToExtract);
         builder.writeProfiles(writeProfiles);
         builder.environment(environment);
+        builder.defaultStartLevel(defaultStartLevel);
 
         // Set up remote repositories from Maven build, to be used by pax-url-aether resolver
         String remoteRepositories = MavenUtil.remoteRepositoryList(project.getRemoteProjectRepositories());
diff --git a/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/VerifyMojo.java b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/VerifyMojo.java
index d13e786..3aca9d3 100644
--- a/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/VerifyMojo.java
+++ b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/VerifyMojo.java
@@ -420,7 +420,7 @@ public class VerifyMojo extends MojoSupport {
 
 
             // Install framework
-            Deployer.DeploymentRequest request = createDeploymentRequest();
+            Deployer.DeploymentRequest request = Deployer.DeploymentRequest.defaultDeploymentRequest();
 
             for (String fmwk : framework) {
                 MapUtils.addToMapSet(request.requirements, FeaturesService.ROOT_REGION, fmwk);
@@ -491,18 +491,6 @@ public class VerifyMojo extends MojoSupport {
         }
     }
 
-    private static Deployer.DeploymentRequest createDeploymentRequest() {
-        Deployer.DeploymentRequest request = new Deployer.DeploymentRequest();
-        request.bundleUpdateRange = FeaturesService.DEFAULT_BUNDLE_UPDATE_RANGE;
-        request.featureResolutionRange = FeaturesService.DEFAULT_FEATURE_RESOLUTION_RANGE;
-        request.serviceRequirements = FeaturesService.SERVICE_REQUIREMENTS_DEFAULT;
-        request.overrides = new HashSet<>();
-        request.requirements = new HashMap<>();
-        request.stateChanges = new HashMap<>();
-        request.options = EnumSet.noneOf(FeaturesService.Option.class);
-        return request;
-    }
-
     private static String toString(Collection<String> collection) {
         StringBuilder sb = new StringBuilder();
         sb.append("{\n");
diff --git a/util/src/main/java/org/apache/karaf/util/xml/IndentingXMLEventWriter.java b/util/src/main/java/org/apache/karaf/util/xml/IndentingXMLEventWriter.java
new file mode 100755
index 0000000..35735f2
--- /dev/null
+++ b/util/src/main/java/org/apache/karaf/util/xml/IndentingXMLEventWriter.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.karaf.util.xml;
+
+import javax.xml.namespace.NamespaceContext;
+import javax.xml.stream.XMLEventFactory;
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.XMLEventWriter;
+import javax.xml.stream.XMLStreamConstants;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.events.XMLEvent;
+
+public class IndentingXMLEventWriter implements XMLEventWriter {
+
+    private static final XMLEventFactory factory = XMLEventFactory.newFactory();
+
+    private XMLEventWriter wrappedWriter;
+    private int depth = 0;
+
+    private boolean newLineBeforeStartElement = false;
+    private boolean indentBeforeEndElement = false;
+
+    private String indentationString = "    ";
+
+    /**
+     * @param wrappedWriter
+     * @param indentation
+     */
+    public IndentingXMLEventWriter(XMLEventWriter wrappedWriter, String indentation) {
+        this.wrappedWriter = wrappedWriter;
+        this.indentationString = indentation;
+    }
+
+    @Override
+    public void close() throws XMLStreamException {
+        this.wrappedWriter.close();
+    }
+
+    @Override
+    public void add(XMLEvent event) throws XMLStreamException {
+        switch (event.getEventType()) {
+            case XMLStreamConstants.START_DOCUMENT:
+                this.wrappedWriter.add(event);
+                this.wrappedWriter.add(factory.createCharacters("\n"));
+                break;
+            case XMLStreamConstants.START_ELEMENT:
+                if (this.newLineBeforeStartElement)
+                    this.wrappedWriter.add(factory.createCharacters("\n"));
+                this.newLineBeforeStartElement = true;
+                this.indentBeforeEndElement = false;
+                this.possiblyIndent();
+                this.wrappedWriter.add(event);
+                this.depth++;
+                break;
+            case XMLStreamConstants.END_ELEMENT:
+                this.newLineBeforeStartElement = false;
+                this.depth--;
+                if (this.indentBeforeEndElement)
+                    this.possiblyIndent();
+                this.indentBeforeEndElement = true;
+                this.wrappedWriter.add(event);
+                this.wrappedWriter.add(factory.createCharacters("\n"));
+                break;
+            case XMLStreamConstants.COMMENT:
+            case XMLStreamConstants.PROCESSING_INSTRUCTION:
+                this.wrappedWriter.add(event);
+                this.wrappedWriter.add(factory.createCharacters("\n"));
+                this.newLineBeforeStartElement = false;
+                this.indentBeforeEndElement = true;
+                break;
+            default:
+                this.wrappedWriter.add(event);
+        }
+    }
+
+    /**
+     * Indent at non-zero depth
+     * @throws XMLStreamException
+     */
+    private void possiblyIndent() throws XMLStreamException {
+        if (this.depth > 0) {
+            StringBuffer sb = new StringBuffer();
+            for (int i = 0; i < this.depth; i++)
+                sb.append(this.indentationString);
+            this.wrappedWriter.add(factory.createCharacters(sb.toString()));
+        }
+    }
+
+    @Override
+    public void add(XMLEventReader reader) throws XMLStreamException {
+        this.wrappedWriter.add(reader);
+    }
+
+    @Override
+    public String getPrefix(String uri) throws XMLStreamException {
+        return this.wrappedWriter.getPrefix(uri);
+    }
+
+    @Override
+    public void setPrefix(String prefix, String uri) throws XMLStreamException {
+        this.wrappedWriter.setPrefix(prefix, uri);
+    }
+
+    @Override
+    public void setDefaultNamespace(String uri) throws XMLStreamException {
+        this.wrappedWriter.setDefaultNamespace(uri);
+    }
+
+    @Override
+    public void setNamespaceContext(NamespaceContext context) throws XMLStreamException {
+        this.wrappedWriter.setNamespaceContext(context);
+    }
+
+    @Override
+    public NamespaceContext getNamespaceContext() {
+        return this.wrappedWriter.getNamespaceContext();
+    }
+
+    @Override
+    public void flush() throws XMLStreamException {
+        this.wrappedWriter.flush();
+    }
+
+    public void setIndentationString(String indentationString) {
+        this.indentationString = indentationString;
+    }
+
+}

-- 
To stop receiving notification emails like this one, please contact
"commits@karaf.apache.org" <co...@karaf.apache.org>.