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/09 18:46:56 UTC

[karaf] 13/19: [KARAF-5376] Use etc/versions.properties (configurable) to resolve placeholders in etc/org.apache.karaf.features.xml

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 5b5a0e8ed6dfa43209da6d4586de8caae88f03d1
Author: Grzegorz Grzybek <gr...@gmail.com>
AuthorDate: Wed Dec 6 10:37:43 2017 +0100

    [KARAF-5376] Use etc/versions.properties (configurable) to resolve placeholders in etc/org.apache.karaf.features.xml
---
 .../resources/etc/org.apache.karaf.features.cfg    |   9 ++
 features/core/pom.xml                              |  16 ++
 .../model/processing/FeaturesProcessing.java       |   2 +-
 .../karaf/features/internal/osgi/Activator.java    |   2 +
 .../service/FeaturesProcessingSerializer.java      | 171 ++++++++++++++++++++-
 .../internal/service/FeaturesProcessorImpl.java    |  27 +++-
 .../internal/service/FeaturesServiceConfig.java    |  27 +++-
 .../features/internal/service/BlacklistTest.java   |   2 +-
 .../internal/service/FeaturesProcessorTest.java    |  32 +++-
 .../karaf/features/internal/service/fpi03.xml      |  30 ++++
 pom.xml                                            |  16 ++
 .../org/apache/karaf/profile/assembly/Builder.java |   2 +-
 12 files changed, 310 insertions(+), 26 deletions(-)

diff --git a/assemblies/features/base/src/main/filtered-resources/resources/etc/org.apache.karaf.features.cfg b/assemblies/features/base/src/main/filtered-resources/resources/etc/org.apache.karaf.features.cfg
index 57b92b4..c0c6594 100644
--- a/assemblies/features/base/src/main/filtered-resources/resources/etc/org.apache.karaf.features.cfg
+++ b/assemblies/features/base/src/main/filtered-resources/resources/etc/org.apache.karaf.features.cfg
@@ -62,3 +62,12 @@ featuresBootAsynchronous=false
 # Store cfg file for config element in feature
 #
 #configCfgStore=true
+
+#
+# Configuration of features processing mechanism (overrides, blacklisting, modification of features)
+# XML file defines instructions related to features processing
+# versions.properties may declare properties to resolve placeholders in XML file
+# both files are relative to ${karaf.etc}
+#
+#featureProcessing=org.apache.karaf.features.xml
+#featureProcessingVersions=versions.properties
diff --git a/features/core/pom.xml b/features/core/pom.xml
index e5eaa85..a2e9729 100644
--- a/features/core/pom.xml
+++ b/features/core/pom.xml
@@ -88,6 +88,19 @@
         </dependency>
 
         <dependency>
+            <groupId>org.ops4j.base</groupId>
+            <artifactId>ops4j-base-util-property</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.ops4j.base</groupId>
+            <artifactId>ops4j-base-util-collections</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.ops4j.pax.swissbox</groupId>
+            <artifactId>pax-swissbox-property</artifactId>
+        </dependency>
+
+        <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-log4j12</artifactId>
             <scope>test</scope>
@@ -159,6 +172,9 @@
                             org.apache.karaf.util.xml,
                             org.eclipse.equinox.internal.region.*;-split-package:=merge-first,
                             org.apache.felix.resolver.*,
+                            org.ops4j.pax.swissbox.*,
+                            org.ops4j.util.*,
+                            org.ops4j.lang.*
                         </Private-Package>
                         <Embed-Dependency>
                             org.apache.karaf.util;inline="org/apache/karaf/util/XmlUtils*.class"
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 3f5050b..537057e 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
@@ -280,7 +280,7 @@ public class FeaturesProcessing {
                     return null;
                 }
                 Version vfloor = new Version(v.getMajor(), v.getMinor(), 0, null);
-                parser.setVersion(new VersionRange(false, vfloor, v, true).toString());
+                parser.setVersion(new VersionRange(false, vfloor, v, v.compareTo(vfloor) > 0).toString());
             }
             return parser.toMvnURI();
         } catch (MalformedURLException e) {
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/osgi/Activator.java b/features/core/src/main/java/org/apache/karaf/features/internal/osgi/Activator.java
index a950f90..5ef8870 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/osgi/Activator.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/osgi/Activator.java
@@ -92,6 +92,7 @@ public class Activator extends BaseActivator {
 
     public static final String FEATURES_SERVICE_CONFIG_FILE = "org.apache.karaf.features.cfg";
     public static final String FEATURES_SERVICE_PROCESSING_FILE = "org.apache.karaf.features.xml";
+    public static final String FEATURES_SERVICE_PROCESSING_VERSIONS_FILE = "versions.properties";
 
     private static final String STATE_FILE = "state.json";
 
@@ -238,6 +239,7 @@ public class Activator extends BaseActivator {
             getInt("scheduleMaxRun", FeaturesService.DEFAULT_SCHEDULE_MAX_RUN),
             getString("blacklisted", new File(karafEtc, "blacklisted.properties").toURI().toString()),
             getString("featureProcessing", new File(karafEtc, FEATURES_SERVICE_PROCESSING_FILE).toURI().toString()),
+            getString("featureProcessingVersions", new File(karafEtc, FEATURES_SERVICE_PROCESSING_VERSIONS_FILE).toURI().toString()),
             getString("serviceRequirements", FeaturesService.ServiceRequirementsBehavior.Default.getValue()));
     }
 
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
index 4a72fe3..6fcbbcb 100644
--- 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
@@ -24,13 +24,15 @@ import java.io.ByteArrayOutputStream;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.OutputStreamWriter;
+import java.io.StringWriter;
 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.bind.UnmarshallerHandler;
+import javax.xml.parsers.SAXParserFactory;
 import javax.xml.stream.XMLEventFactory;
 import javax.xml.stream.XMLEventReader;
 import javax.xml.stream.XMLEventWriter;
@@ -42,8 +44,21 @@ 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.ops4j.pax.swissbox.property.BundleContextPropertyResolver;
+import org.ops4j.util.property.DictionaryPropertyResolver;
+import org.ops4j.util.property.PropertyResolver;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.InputSource;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.AttributesImpl;
 
 /**
  * A class to help serialize {@link org.apache.karaf.features.internal.model.processing.FeaturesProcessing} model
@@ -53,12 +68,15 @@ public class FeaturesProcessingSerializer {
 
     public static Logger LOG = LoggerFactory.getLogger(FeaturesProcessingSerializer.class);
 
+    private final BundleContext bundleContext;
     private JAXBContext FEATURES_PROCESSING_CONTEXT;
 
     public FeaturesProcessingSerializer() {
+        Bundle bundle = FrameworkUtil.getBundle(this.getClass());
+        this.bundleContext = bundle == null ? null : bundle.getBundleContext();
         try {
             FEATURES_PROCESSING_CONTEXT = JAXBContext.newInstance(ObjectFactory.class);
-        } catch (JAXBException e) {
+        } catch (Exception e) {
             throw new RuntimeException(e);
         }
     }
@@ -68,9 +86,43 @@ public class FeaturesProcessingSerializer {
      * @param stream
      * @return
      */
-    public FeaturesProcessing read(InputStream stream) throws JAXBException {
+    public FeaturesProcessing read(InputStream stream) throws Exception {
+        return this.read(stream, null);
+    }
+
+    /**
+     * Reads {@link FeaturesProcessing features processing model} from input stream
+     * @param stream
+     * @param versions additional properties to resolve placeholders in features processing XML
+     * @return
+     */
+    public FeaturesProcessing read(InputStream stream, Properties versions) throws Exception {
         Unmarshaller unmarshaller = FEATURES_PROCESSING_CONTEXT.createUnmarshaller();
-        return (FeaturesProcessing) unmarshaller.unmarshal(stream);
+        UnmarshallerHandler handler = unmarshaller.getUnmarshallerHandler();
+
+        // BundleContextPropertyResolver gives access to e.g., ${karaf.base}
+        final PropertyResolver resolver = bundleContext == null ? new DictionaryPropertyResolver(versions)
+                : new DictionaryPropertyResolver(versions, new BundleContextPropertyResolver(bundleContext));
+
+        // indirect unmarshaling with property resolution inside XML attribute values and CDATA
+        SAXParserFactory spf = SAXParserFactory.newInstance();
+        spf.setNamespaceAware(true);
+        XMLReader xmlReader = spf.newSAXParser().getXMLReader();
+        xmlReader.setContentHandler(new ResolvingContentHandler(new Properties() {
+            @Override
+            public String getProperty(String key) {
+                return resolver.get(key);
+            }
+
+            @Override
+            public String getProperty(String key, String defaultValue) {
+                String value = resolver.get(key);
+                return value == null ? defaultValue : value;
+            }
+        }, handler));
+        xmlReader.parse(new InputSource(stream));
+
+        return (FeaturesProcessing) handler.getResult();
     }
 
     /**
@@ -156,4 +208,115 @@ public class FeaturesProcessingSerializer {
         }
     }
 
+    private static class ResolvingContentHandler implements ContentHandler {
+
+        public static Logger LOG = LoggerFactory.getLogger(ResolvingContentHandler.class);
+
+        private Properties properties;
+        private ContentHandler target;
+
+        private boolean inElement = false;
+        private StringWriter sw = new StringWriter();
+
+        public ResolvingContentHandler(Properties properties, ContentHandler target) {
+            this.properties = properties;
+            this.target = target;
+        }
+
+        @Override
+        public void setDocumentLocator(Locator locator) {
+            target.setDocumentLocator(locator);
+        }
+
+        @Override
+        public void startDocument() throws SAXException {
+            target.startDocument();
+        }
+
+        @Override
+        public void endDocument() throws SAXException {
+            target.endDocument();
+        }
+
+        @Override
+        public void startPrefixMapping(String prefix, String uri) throws SAXException {
+            target.startPrefixMapping(prefix, uri);
+        }
+
+        @Override
+        public void endPrefixMapping(String prefix) throws SAXException {
+            target.endPrefixMapping(prefix);
+        }
+
+        @Override
+        public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
+            AttributesImpl resolvedAttributes = new AttributesImpl(atts);
+            for (int i = 0; i < atts.getLength(); i++) {
+                resolvedAttributes.setAttribute(i, atts.getURI(i), atts.getLocalName(i), atts.getQName(i),
+                        atts.getType(i), resolve(atts.getValue(i)));
+            }
+            if (inElement) {
+                flushBuffer(false);
+            }
+            inElement = true;
+            target.startElement(uri, localName, qName, resolvedAttributes);
+        }
+
+        @Override
+        public void endElement(String uri, String localName, String qName) throws SAXException {
+            if (inElement) {
+                flushBuffer(true);
+                inElement = false;
+            }
+            target.endElement(uri, localName, qName);
+        }
+
+        @Override
+        public void characters(char[] ch, int start, int length) throws SAXException {
+            if (inElement) {
+                sw.append(new String(ch, start, length));
+            } else {
+                target.characters(ch, start, length);
+            }
+        }
+
+        @Override
+        public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
+            // only elements without PCDATA in DTD have whitespace passed to this method. so ignore
+            target.ignorableWhitespace(ch, start, length);
+        }
+
+        @Override
+        public void processingInstruction(String target, String data) throws SAXException {
+            this.target.processingInstruction(target, data);
+        }
+
+        @Override
+        public void skippedEntity(String name) throws SAXException {
+            target.skippedEntity(name);
+        }
+
+        /**
+         * Pass collected characters to target {@link ContentHandler}
+         * @param resolve whether to expect placeholders in collected text
+         */
+        private void flushBuffer(boolean resolve) throws SAXException {
+            String value = sw.toString();
+            String resolved = resolve ? resolve(value) : value;
+
+            target.characters(resolved.toCharArray(), 0, resolved.length());
+            sw = new StringWriter();
+        }
+
+        private String resolve(String value) {
+            String resolved = org.ops4j.util.collections.PropertyResolver.resolve(properties, value);
+            if (resolved.contains("${")) {
+                // there are still unresolved properties - just log warning
+                LOG.warn("Value {} has unresolved properties, please check configuration.", value);
+            }
+            return resolved;
+        }
+
+    }
+
 }
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 4e3fa04..87d26e6 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
@@ -18,11 +18,13 @@
  */
 package org.apache.karaf.features.internal.service;
 
+import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.net.URL;
 import java.util.List;
+import java.util.Properties;
 import java.util.Set;
 
 import org.apache.karaf.features.BundleInfo;
@@ -49,7 +51,7 @@ public class FeaturesProcessorImpl implements FeaturesProcessor {
 
     public static Logger LOG = LoggerFactory.getLogger(FeaturesProcessorImpl.class);
 
-    private static FeaturesProcessingSerializer serializer = new FeaturesProcessingSerializer();
+    private FeaturesProcessingSerializer serializer = new FeaturesProcessingSerializer();
 
     // empty, but fully functional features processing configuration
     private FeaturesProcessing processing = new FeaturesProcessing();
@@ -58,14 +60,25 @@ public class FeaturesProcessorImpl implements FeaturesProcessor {
      * <p>Creates instance of features processor using 1 external URI, additional {@link Blacklist} instance
      * and additional set of override clauses.</p>
      * @param featureModificationsURI
+     * @param featureProcessingVersions
      * @param blacklistDefinitions
      * @param overrides
      */
-    public FeaturesProcessorImpl(String featureModificationsURI, Blacklist blacklistDefinitions, Set<String> overrides) {
+    public FeaturesProcessorImpl(String featureModificationsURI, String featureProcessingVersions,
+                                 Blacklist blacklistDefinitions, Set<String> overrides) {
         if (featureModificationsURI != null) {
             try {
                 try (InputStream stream = new URL(featureModificationsURI).openStream()) {
-                    processing = serializer.read(stream);
+                    Properties versions = new Properties();
+                    if (featureProcessingVersions != null) {
+                        File versionsProperties = new File(new URL(featureProcessingVersions).getPath());
+                        if (versionsProperties.isFile()) {
+                            try (InputStream propsStream = new URL(featureProcessingVersions).openStream()) {
+                                versions.load(propsStream);
+                            }
+                        }
+                    }
+                    processing = serializer.read(stream, versions);
                 }
             } catch (FileNotFoundException e) {
                 LOG.debug("Can't find feature processing file (" + featureModificationsURI + "), skipping");
@@ -80,11 +93,13 @@ public class FeaturesProcessorImpl implements FeaturesProcessor {
     /**
      * <p>Creates instance of features processor using 3 external (optional) URIs.</p>
      * @param featureModificationsURI
+     * @param featureProcessingVersions
      * @param blacklistedURI
      * @param overridesURI
      */
-    public FeaturesProcessorImpl(String featureModificationsURI, String blacklistedURI, String overridesURI) {
-        this(featureModificationsURI, new Blacklist(blacklistedURI), Overrides.loadOverrides(overridesURI));
+    public FeaturesProcessorImpl(String featureModificationsURI, String featureProcessingVersions,
+                                 String blacklistedURI, String overridesURI) {
+        this(featureModificationsURI, featureProcessingVersions, new Blacklist(blacklistedURI), Overrides.loadOverrides(overridesURI));
     }
 
     /**
@@ -93,7 +108,7 @@ public class FeaturesProcessorImpl implements FeaturesProcessor {
      * @param configuration
      */
     public FeaturesProcessorImpl(FeaturesServiceConfig configuration) {
-        this(configuration.featureModifications, configuration.blacklisted, configuration.overrides);
+        this(configuration.featureModifications, configuration.featureProcessingVersions, configuration.blacklisted, configuration.overrides);
     }
 
     /**
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceConfig.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceConfig.java
index 7fb0f26..5ea4fea 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceConfig.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceConfig.java
@@ -65,25 +65,32 @@ public class FeaturesServiceConfig {
     public final String featureModifications;
 
     /**
+     * Location of <code>etc/versions.properties</code> to read properties to resolve placeholders in
+     * {@link #featureModifications}
+     */
+    public final String featureProcessingVersions;
+
+    /**
      * Location of <code>etc/overrides.properties</code>
      */
     @Deprecated
     public final String overrides;
 
     public FeaturesServiceConfig() {
-        this(null, null, null);
+        this(null, null, null, null);
     }
 
-    public FeaturesServiceConfig(String featureModifications) {
-        this(null, FeaturesService.DEFAULT_FEATURE_RESOLUTION_RANGE, FeaturesService.DEFAULT_BUNDLE_UPDATE_RANGE, null, 1, 0, 0, null, featureModifications, null);
+    public FeaturesServiceConfig(String featureModifications, String featureProcessingVersions) {
+        this(null, FeaturesService.DEFAULT_FEATURE_RESOLUTION_RANGE, FeaturesService.DEFAULT_BUNDLE_UPDATE_RANGE, null, 1, 0, 0, null, featureModifications, featureProcessingVersions, null);
     }
 
     @Deprecated
-    public FeaturesServiceConfig(String overrides, String blacklisted, String featureModifications) {
-        this(overrides, FeaturesService.DEFAULT_FEATURE_RESOLUTION_RANGE, FeaturesService.DEFAULT_BUNDLE_UPDATE_RANGE, null, 1, 0, 0, blacklisted, featureModifications, null);
+    public FeaturesServiceConfig(String overrides, String blacklisted, String featureModifications, String featureProcessingVersions) {
+        this(overrides, FeaturesService.DEFAULT_FEATURE_RESOLUTION_RANGE, FeaturesService.DEFAULT_BUNDLE_UPDATE_RANGE, null, 1, 0, 0, blacklisted, featureModifications, featureProcessingVersions, null);
     }
 
-    public FeaturesServiceConfig(String featureResolutionRange, String bundleUpdateRange, String updateSnapshots, int downloadThreads, long scheduleDelay, int scheduleMaxRun, String featureModifications, String serviceRequirements) {
+    public FeaturesServiceConfig(String featureResolutionRange, String bundleUpdateRange, String updateSnapshots, int downloadThreads, long scheduleDelay, int scheduleMaxRun,
+                                 String featureModifications, String featureProcessingVersions, String serviceRequirements) {
         this.overrides = null;
         this.featureResolutionRange = featureResolutionRange;
         this.bundleUpdateRange = bundleUpdateRange;
@@ -93,11 +100,16 @@ public class FeaturesServiceConfig {
         this.scheduleMaxRun = scheduleMaxRun;
         this.blacklisted = null;
         this.featureModifications = featureModifications;
+        this.featureProcessingVersions = featureProcessingVersions;
         this.serviceRequirements = serviceRequirements;
     }
 
     @Deprecated
-    public FeaturesServiceConfig(String overrides, String featureResolutionRange, String bundleUpdateRange, String updateSnapshots, int downloadThreads, long scheduleDelay, int scheduleMaxRun, String blacklisted, String featureModifications, String serviceRequirements) {
+    public FeaturesServiceConfig(String overrides, String featureResolutionRange, String bundleUpdateRange,
+                                 String updateSnapshots, int downloadThreads, long scheduleDelay, int scheduleMaxRun,
+                                 String blacklisted,
+                                 String featureModifications, String featureProcessingVersions,
+                                 String serviceRequirements) {
         this.overrides = overrides;
         this.featureResolutionRange = featureResolutionRange;
         this.bundleUpdateRange = bundleUpdateRange;
@@ -107,6 +119,7 @@ public class FeaturesServiceConfig {
         this.scheduleMaxRun = scheduleMaxRun;
         this.blacklisted = blacklisted;
         this.featureModifications = featureModifications;
+        this.featureProcessingVersions = featureProcessingVersions;
         this.serviceRequirements = serviceRequirements;
     }
 
diff --git a/features/core/src/test/java/org/apache/karaf/features/internal/service/BlacklistTest.java b/features/core/src/test/java/org/apache/karaf/features/internal/service/BlacklistTest.java
index 6c5d7e2..34fc55f 100644
--- a/features/core/src/test/java/org/apache/karaf/features/internal/service/BlacklistTest.java
+++ b/features/core/src/test/java/org/apache/karaf/features/internal/service/BlacklistTest.java
@@ -83,7 +83,7 @@ public class BlacklistTest {
             fos.write(blacklistClause.getBytes("UTF-8"));
         }
         RepositoryImpl features = new RepositoryImpl(uri, true);
-        FeaturesServiceConfig config = new FeaturesServiceConfig(null, FeaturesService.DEFAULT_FEATURE_RESOLUTION_RANGE, FeaturesService.DEFAULT_BUNDLE_UPDATE_RANGE, null, 1, 0, 0, blacklistedProperties.toURI().toString(), null, null);
+        FeaturesServiceConfig config = new FeaturesServiceConfig(null, FeaturesService.DEFAULT_FEATURE_RESOLUTION_RANGE, FeaturesService.DEFAULT_BUNDLE_UPDATE_RANGE, null, 1, 0, 0, blacklistedProperties.toURI().toString(), null, null, null);
         features.processFeatures(new FeaturesProcessorImpl(config));
         return Arrays.stream(features.getFeatures());
     }
diff --git a/features/core/src/test/java/org/apache/karaf/features/internal/service/FeaturesProcessorTest.java b/features/core/src/test/java/org/apache/karaf/features/internal/service/FeaturesProcessorTest.java
index e73e7d1..45955b2 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
@@ -18,7 +18,9 @@
  */
 package org.apache.karaf.features.internal.service;
 
+import java.io.FileWriter;
 import java.net.URI;
+import java.util.Properties;
 import javax.xml.bind.JAXBContext;
 import javax.xml.bind.Marshaller;
 
@@ -89,7 +91,7 @@ public class FeaturesProcessorTest {
     public void readingLegacyOverrides() {
         FeaturesProcessorImpl processor = new FeaturesProcessorImpl(new FeaturesServiceConfig(
                 "file:src/test/resources/org/apache/karaf/features/internal/service/overrides2.properties",
-                null, null));
+                null, null, null));
 
         FeaturesProcessing instructions = processor.getInstructions();
         BundleReplacements bundleReplacements = instructions.getBundleReplacements();
@@ -116,7 +118,7 @@ public class FeaturesProcessorTest {
         FeaturesProcessorImpl processor = new FeaturesProcessorImpl(new FeaturesServiceConfig(
                 null,
                 "file:src/test/resources/org/apache/karaf/features/internal/service/blacklisted2.properties",
-                null));
+                null, null));
 
         FeaturesProcessing instructions = processor.getInstructions();
         Blacklist blacklist = instructions.getBlacklist();
@@ -136,7 +138,7 @@ public class FeaturesProcessorTest {
     public void blacklistingRepositories() {
         FeaturesProcessorImpl processor = new FeaturesProcessorImpl(new FeaturesServiceConfig(
                 null, null,
-                "file:src/test/resources/org/apache/karaf/features/internal/service/fpi01.xml"));
+                "file:src/test/resources/org/apache/karaf/features/internal/service/fpi01.xml", null));
         URI uri = URI.create("file:src/test/resources/org/apache/karaf/features/internal/service/fp01.xml");
         RepositoryImpl repo = (RepositoryImpl) new RepositoryCacheImpl(processor).create(uri, true);
         assertThat(repo.getRepositories().length, equalTo(3));
@@ -150,7 +152,7 @@ public class FeaturesProcessorTest {
     public void blacklistingFeatures() {
         FeaturesProcessorImpl processor = new FeaturesProcessorImpl(new FeaturesServiceConfig(
                 null, null,
-                "file:src/test/resources/org/apache/karaf/features/internal/service/fpi01.xml"));
+                "file:src/test/resources/org/apache/karaf/features/internal/service/fpi01.xml", null));
         URI uri = URI.create("file:src/test/resources/org/apache/karaf/features/internal/service/fp02.xml");
         RepositoryImpl repo = (RepositoryImpl) new RepositoryCacheImpl(processor).create(uri, true);
 
@@ -166,7 +168,7 @@ public class FeaturesProcessorTest {
     public void blacklistingBundles() {
         FeaturesProcessorImpl processor = new FeaturesProcessorImpl(new FeaturesServiceConfig(
                 null, null,
-                "file:src/test/resources/org/apache/karaf/features/internal/service/fpi01.xml"));
+                "file:src/test/resources/org/apache/karaf/features/internal/service/fpi01.xml", null));
         URI uri = URI.create("file:src/test/resources/org/apache/karaf/features/internal/service/fp03.xml");
         RepositoryImpl repo = (RepositoryImpl) new RepositoryCacheImpl(processor).create(uri, true);
 
@@ -182,7 +184,7 @@ public class FeaturesProcessorTest {
     public void overridingBundles() {
         FeaturesProcessorImpl processor = new FeaturesProcessorImpl(new FeaturesServiceConfig(
                 null, null,
-                "file:src/test/resources/org/apache/karaf/features/internal/service/fpi02.xml"));
+                "file:src/test/resources/org/apache/karaf/features/internal/service/fpi02.xml", null));
         URI uri = URI.create("file:src/test/resources/org/apache/karaf/features/internal/service/fp03.xml");
         RepositoryImpl repo = (RepositoryImpl) new RepositoryCacheImpl(processor).create(uri, true);
 
@@ -202,6 +204,24 @@ public class FeaturesProcessorTest {
     }
 
     @Test
+    public void resolvePlaceholders() throws Exception {
+        Properties props = new Properties();
+        props.put("version.jclouds", "1.9");
+        props.put("version.commons-io", "2.5");
+        props.store(new FileWriter("target/versions.properties"), null);
+
+        FeaturesProcessorImpl processor = new FeaturesProcessorImpl(new FeaturesServiceConfig(
+                null, null,
+                "file:src/test/resources/org/apache/karaf/features/internal/service/fpi03.xml",
+                "file:target/versions.properties"));
+
+        assertThat(processor.getInstructions().getBlacklistedRepositories().get(0),
+                equalTo("mvn:org.jclouds/jclouds-features/1.9/xml/features"));
+        assertThat(processor.getInstructions().getBundleReplacements().getOverrideBundles().get(0).getReplacement(),
+                equalTo("mvn:commons-io/commons-io/2.5"));
+    }
+
+    @Test
     public void serializeWithComments() {
         FeaturesProcessingSerializer serializer = new FeaturesProcessingSerializer();
         FeaturesProcessing featuresProcessing = new FeaturesProcessing();
diff --git a/features/core/src/test/resources/org/apache/karaf/features/internal/service/fpi03.xml b/features/core/src/test/resources/org/apache/karaf/features/internal/service/fpi03.xml
new file mode 100644
index 0000000..4528fbd
--- /dev/null
+++ b/features/core/src/test/resources/org/apache/karaf/features/internal/service/fpi03.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+-->
+<featuresProcessing xmlns="http://karaf.apache.org/xmlns/features-processing/v1.0.0">
+
+    <blacklistedRepositories>
+        <repository>mvn:org.jclouds/jclouds-features/${version.jclouds}/xml/features</repository>
+    </blacklistedRepositories>
+
+    <bundleReplacements>
+        <bundle replacement="mvn:commons-io/commons-io/${version.commons-io}" />
+    </bundleReplacements>
+
+</featuresProcessing>
diff --git a/pom.xml b/pom.xml
index 4f218d5..81106e9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -273,6 +273,7 @@
         <pax.exam.version>4.11.0</pax.exam.version>
         <pax.logging.version>1.10.1</pax.logging.version>
         <pax.base.version>1.5.0</pax.base.version>
+        <pax.swissbox.version>1.8.2</pax.swissbox.version>
         <pax.url.version>2.5.3</pax.url.version>
         <pax.web.version>6.1.0-SNAPSHOT</pax.web.version>
         <pax.tinybundle.version>2.1.1</pax.tinybundle.version>
@@ -1246,6 +1247,16 @@
                 <version>${pax.base.version}</version>
             </dependency>
             <dependency>
+                <groupId>org.ops4j.base</groupId>
+                <artifactId>ops4j-base-util-property</artifactId>
+                <version>${pax.base.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.ops4j.base</groupId>
+                <artifactId>ops4j-base-util-collections</artifactId>
+                <version>${pax.base.version}</version>
+            </dependency>
+            <dependency>
                 <groupId>org.ops4j.pax.url</groupId>
                 <artifactId>pax-url-aether</artifactId>
                 <version>${pax.url.version}</version>
@@ -1305,6 +1316,11 @@
                 </exclusions>
             </dependency>
             <dependency>
+                <groupId>org.ops4j.pax.swissbox</groupId>
+                <artifactId>pax-swissbox-property</artifactId>
+                <version>${pax.swissbox.version}</version>
+            </dependency>
+            <dependency>
                 <groupId>org.ops4j.pax.url</groupId>
                 <artifactId>pax-url-wrap</artifactId>
                 <version>${pax.url.version}</version>
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 274d535..4a5603c 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
@@ -906,7 +906,7 @@ public class Builder {
         // 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<>());
+        FeaturesProcessorImpl processor = new FeaturesProcessorImpl(existingProcessorDefinitionURI, null, blacklist, new HashSet<>());
 
         //
         // Propagate feature installation from repositories

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