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>.