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 <[email protected]> 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 "[email protected]" <[email protected]>.
