diff --git a/assemblies/features/framework/pom.xml b/assemblies/features/framework/pom.xml index efc75bc7065..be7b04f2f17 100644 --- a/assemblies/features/framework/pom.xml +++ b/assemblies/features/framework/pom.xml @@ -186,6 +186,11 @@ org.apache.karaf.features org.apache.karaf.features.core + + org.apache.karaf.maven + org.apache.karaf.maven.resolver.paxurl + ${project.version} + diff --git a/assemblies/features/framework/src/main/feature/feature.xml b/assemblies/features/framework/src/main/feature/feature.xml index 00bfdaaa487..443707f903b 100644 --- a/assemblies/features/framework/src/main/feature/feature.xml +++ b/assemblies/features/framework/src/main/feature/feature.xml @@ -44,6 +44,7 @@ mvn:org.apache.felix/org.apache.felix.configurator/${felix.configurator.version} mvn:org.apache.karaf.config/org.apache.karaf.config.core/${project.version} + mvn:org.apache.karaf.maven/org.apache.karaf.maven.resolver.paxurl/${project.version} mvn:org.apache.karaf.features/org.apache.karaf.features.core/${project.version} mvn:jakarta.xml.bind/jakarta.xml.bind-api/${spec.jaxb-api.version} mvn:org.glassfish.jaxb/jaxb-runtime/${spec.jaxb.version} @@ -76,6 +77,7 @@ mvn:org.apache.felix/org.apache.felix.fileinstall/${felix.fileinstall.version} + mvn:org.apache.karaf.maven/org.apache.karaf.maven.resolver.paxurl/${project.version} mvn:org.apache.karaf.features/org.apache.karaf.features.core/${project.version} mvn:jakarta.xml.bind/jakarta.xml.bind-api/${spec.jaxb-api.version} mvn:org.glassfish.jaxb/jaxb-runtime/${spec.jaxb.version} diff --git a/features/command/src/main/java/org/apache/karaf/features/command/FeatureExport.java b/features/command/src/main/java/org/apache/karaf/features/command/FeatureExport.java index efc4b0db907..7ccbddc7b8e 100644 --- a/features/command/src/main/java/org/apache/karaf/features/command/FeatureExport.java +++ b/features/command/src/main/java/org/apache/karaf/features/command/FeatureExport.java @@ -33,7 +33,7 @@ import org.apache.karaf.shell.api.action.lifecycle.Reference; import org.apache.karaf.shell.api.action.lifecycle.Service; import org.apache.karaf.shell.support.completers.FileCompleter; -import org.ops4j.pax.url.mvn.MavenResolver; +import org.apache.karaf.features.spi.MavenResolver; /** * Simple {@link FeaturesCommandSupport} implementation that allows a user in diff --git a/features/core/pom.xml b/features/core/pom.xml index 23a089cbade..61f4c36442d 100644 --- a/features/core/pom.xml +++ b/features/core/pom.xml @@ -129,24 +129,6 @@ provided - - org.ops4j.pax.url - pax-url-aether - - - - org.ops4j.base - ops4j-base-util-property - - - org.ops4j.base - ops4j-base-util-collections - - - org.ops4j.pax.swissbox - pax-swissbox-property - - jakarta.activation jakarta.activation-api @@ -158,12 +140,6 @@ test - - org.ops4j.pax.tinybundles - tinybundles - test - - org.hamcrest hamcrest @@ -214,6 +190,7 @@ org.apache.karaf.features, + org.apache.karaf.features.spi, org.apache.karaf.features.management, org.apache.karaf.features.management.codec;-noimport:=true, org.osgi.service.repository, @@ -248,9 +225,6 @@ org.apache.karaf.util.xml, org.eclipse.equinox.internal.region.*;-split-package:=merge-first, org.apache.felix.resolver.*, - org.ops4j.pax.swissbox.*, - org.ops4j.util.*, - org.ops4j.lang.*, com.fasterxml.jackson* diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/download/DownloadManagers.java b/features/core/src/main/java/org/apache/karaf/features/internal/download/DownloadManagers.java index 59d37ead3b7..f7d10eecd27 100644 --- a/features/core/src/main/java/org/apache/karaf/features/internal/download/DownloadManagers.java +++ b/features/core/src/main/java/org/apache/karaf/features/internal/download/DownloadManagers.java @@ -19,7 +19,7 @@ import java.util.concurrent.ScheduledExecutorService; import org.apache.karaf.features.internal.download.impl.MavenDownloadManager; -import org.ops4j.pax.url.mvn.MavenResolver; +import org.apache.karaf.features.spi.MavenResolver; public final class DownloadManagers { diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/MavenDownloadManager.java b/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/MavenDownloadManager.java index ede3337e7de..1f73bf2ac48 100644 --- a/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/MavenDownloadManager.java +++ b/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/MavenDownloadManager.java @@ -29,7 +29,7 @@ import org.apache.karaf.features.internal.download.Downloader; import org.apache.karaf.features.internal.download.StreamProvider; import org.apache.karaf.features.internal.util.MultiException; -import org.ops4j.pax.url.mvn.MavenResolver; +import org.apache.karaf.features.spi.MavenResolver; public class MavenDownloadManager implements DownloadManager { diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/MavenDownloadTask.java b/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/MavenDownloadTask.java index 41a1e2b04e3..76d8b481808 100644 --- a/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/MavenDownloadTask.java +++ b/features/core/src/main/java/org/apache/karaf/features/internal/download/impl/MavenDownloadTask.java @@ -22,7 +22,7 @@ import java.util.Objects; import org.apache.karaf.util.maven.Parser; -import org.ops4j.pax.url.mvn.MavenResolver; +import org.apache.karaf.features.spi.MavenResolver; public class MavenDownloadTask extends AbstractRetryableDownloadTask { diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/osgi/Activator.java b/features/core/src/main/java/org/apache/karaf/features/internal/osgi/Activator.java index 6640b6d699f..9214aec524c 100644 --- a/features/core/src/main/java/org/apache/karaf/features/internal/osgi/Activator.java +++ b/features/core/src/main/java/org/apache/karaf/features/internal/osgi/Activator.java @@ -57,6 +57,7 @@ import org.apache.karaf.features.internal.service.StateStorage; import org.apache.karaf.features.internal.util.SystemExitManager; import org.apache.karaf.util.ThreadUtils; +import org.apache.karaf.features.spi.MavenResolverFactory; import org.apache.karaf.util.tracker.BaseActivator; import org.apache.karaf.util.tracker.annotation.ProvideService; import org.apache.karaf.util.tracker.annotation.RequireService; @@ -84,6 +85,7 @@ @Services( requires = { @RequireService(ConfigurationAdmin.class), + @RequireService(MavenResolverFactory.class), @RequireService(value = URLStreamHandlerService.class, filter = "(url.handler.protocol=mvn)") }, provides = { @@ -200,6 +202,7 @@ protected void doStart() throws Exception { configurationAdmin, installSupport, cfg); + simpleFeaturesService.setMavenResolverFactory(getTrackedService(MavenResolverFactory.class)); try { EventAdminListener eventAdminListener = new EventAdminListener(bundleContext); simpleFeaturesService.registerListener(eventAdminListener); @@ -216,6 +219,7 @@ protected void doStart() throws Exception { installSupport, globalRepository, cfg); + featuresService.setMavenResolverFactory(getTrackedService(MavenResolverFactory.class)); try { EventAdminListener eventAdminListener = new EventAdminListener(bundleContext); featuresService.registerListener(eventAdminListener); diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesProcessingSerializer.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesProcessingSerializer.java index e89bb349d62..78398b94eb7 100644 --- a/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesProcessingSerializer.java +++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesProcessingSerializer.java @@ -46,9 +46,8 @@ import org.apache.karaf.features.internal.model.processing.FeaturesProcessing; import org.apache.karaf.features.internal.model.processing.ObjectFactory; import org.apache.karaf.util.xml.IndentingXMLEventWriter; -import org.ops4j.pax.swissbox.property.BundleContextPropertyResolver; -import org.ops4j.util.property.DictionaryPropertyResolver; -import org.ops4j.util.property.PropertyResolver; +import org.apache.karaf.features.internal.util.PropertyResolver; +import org.apache.karaf.features.internal.util.PropertySubstitutor; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.FrameworkUtil; @@ -106,9 +105,9 @@ public FeaturesProcessing read(InputStream stream, Properties versions) throws E Unmarshaller unmarshaller = FEATURES_PROCESSING_CONTEXT.createUnmarshaller(); UnmarshallerHandler handler = unmarshaller.getUnmarshallerHandler(); - // BundleContextPropertyResolver gives access to e.g., ${karaf.base} - final PropertyResolver resolver = bundleContext == null ? new DictionaryPropertyResolver(versions) - : new DictionaryPropertyResolver(versions, new BundleContextPropertyResolver(bundleContext)); + // the bundle context resolver gives access to e.g., ${karaf.base} + final PropertyResolver resolver = bundleContext == null ? PropertyResolver.forDictionary(versions) + : PropertyResolver.forDictionary(versions, PropertyResolver.forBundleContext(bundleContext)); // indirect unmarshaling with property resolution inside XML attribute values and CDATA SAXParserFactory spf = SAXParserFactory.newInstance(); @@ -314,7 +313,7 @@ private void flushBuffer(boolean resolve) throws SAXException { } private String resolve(String value) { - String resolved = org.ops4j.util.collections.PropertyResolver.resolve(properties, value); + String resolved = PropertySubstitutor.substitute(properties, value); if (resolved.contains("${")) { // there are still unresolved properties - just log warning LOG.warn("Value {} has unresolved properties, please check configuration.", value); diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java index 80c08631ab8..8eebd73146a 100644 --- a/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java +++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java @@ -75,8 +75,9 @@ import org.apache.karaf.util.json.JsonWriter; import org.apache.karaf.util.collections.CopyOnWriteArrayIdentityList; import org.eclipse.equinox.region.RegionDigraph; -import org.ops4j.pax.url.mvn.MavenResolver; -import org.ops4j.pax.url.mvn.MavenResolvers; +import org.apache.karaf.features.spi.MavenResolver; +import org.apache.karaf.features.spi.MavenResolverFactory; +import org.apache.karaf.features.spi.MavenResolvers; import org.osgi.framework.Bundle; import org.osgi.framework.BundleException; import org.osgi.framework.InvalidSyntaxException; @@ -112,6 +113,8 @@ public class FeaturesServiceImpl implements FeaturesService, BootManaged, Deploy private final FeatureRepoFinder featureFinder; private final ConfigurationAdmin configurationAdmin; private final Resolver resolver; + + private volatile MavenResolverFactory mavenResolverFactory; private final BundleInstallSupport installSupport; private final FeaturesServiceConfig cfg; private RepositoryCache repositories; @@ -1086,18 +1089,29 @@ private void doProvision(Map> requirements, / } } + /** + * Set the {@link MavenResolverFactory} to use. When left unset, the factory is looked up with + * {@link MavenResolvers#factory()}, which is how non-OSGi embedders get one. + * + * @param mavenResolverFactory the factory to use. + */ + public void setMavenResolverFactory(MavenResolverFactory mavenResolverFactory) { + this.mavenResolverFactory = mavenResolverFactory; + } + protected DownloadManager createDownloadManager() throws IOException { - Dictionary props = getMavenConfig(); - MavenResolver resolver = MavenResolvers.createMavenResolver(props, "org.ops4j.pax.url.mvn"); + MavenResolverFactory factory = mavenResolverFactory != null ? mavenResolverFactory : MavenResolvers.factory(); + Dictionary props = getMavenConfig(factory.getConfigurationPid()); + MavenResolver resolver = factory.create(props); ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(cfg.downloadThreads, ThreadUtils.namedThreadFactory("downloader")); executor.setMaximumPoolSize(cfg.downloadThreads); return DownloadManagers.createDownloadManager(resolver, executor, cfg.scheduleDelay, cfg.scheduleMaxRun); } - private Dictionary getMavenConfig() throws IOException { + private Dictionary getMavenConfig(String pid) throws IOException { Hashtable props = new Hashtable<>(); if (configurationAdmin != null) { - Configuration config = configurationAdmin.getConfiguration("org.ops4j.pax.url.mvn", null); + Configuration config = configurationAdmin.getConfiguration(pid, null); if (config != null) { Dictionary cfg = config.getProcessedProperties(null); if (cfg != null) { diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/SimpleFeaturesServiceImpl.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/SimpleFeaturesServiceImpl.java index 1e33492b477..6f2e4aefc71 100644 --- a/features/core/src/main/java/org/apache/karaf/features/internal/service/SimpleFeaturesServiceImpl.java +++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/SimpleFeaturesServiceImpl.java @@ -65,8 +65,9 @@ import org.apache.karaf.features.internal.model.JaxbUtil; import org.apache.karaf.util.ThreadUtils; import org.apache.karaf.util.collections.CopyOnWriteArrayIdentityList; -import org.ops4j.pax.url.mvn.MavenResolver; -import org.ops4j.pax.url.mvn.MavenResolvers; +import org.apache.karaf.features.spi.MavenResolver; +import org.apache.karaf.features.spi.MavenResolverFactory; +import org.apache.karaf.features.spi.MavenResolvers; import org.osgi.framework.Bundle; import org.osgi.framework.BundleException; import org.osgi.framework.InvalidSyntaxException; @@ -108,6 +109,8 @@ public class SimpleFeaturesServiceImpl implements FeaturesService, BootManaged, private final ConfigurationAdmin configurationAdmin; private final BundleInstallSupport installSupport; private final FeaturesServiceConfig cfg; + + private volatile MavenResolverFactory mavenResolverFactory; private RepositoryCache repositories; private FeaturesProcessor featuresProcessor; @@ -842,18 +845,29 @@ private void doProvision(Map> requirements, } } + /** + * Set the {@link MavenResolverFactory} to use. When left unset, the factory is looked up with + * {@link MavenResolvers#factory()}, which is how non-OSGi embedders get one. + * + * @param mavenResolverFactory the factory to use. + */ + public void setMavenResolverFactory(MavenResolverFactory mavenResolverFactory) { + this.mavenResolverFactory = mavenResolverFactory; + } + protected DownloadManager createDownloadManager() throws IOException { - Dictionary props = getMavenConfig(); - MavenResolver resolver = MavenResolvers.createMavenResolver(props, "org.ops4j.pax.url.mvn"); + MavenResolverFactory factory = mavenResolverFactory != null ? mavenResolverFactory : MavenResolvers.factory(); + Dictionary props = getMavenConfig(factory.getConfigurationPid()); + MavenResolver resolver = factory.create(props); ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(cfg.downloadThreads, ThreadUtils.namedThreadFactory("downloader")); executor.setMaximumPoolSize(cfg.downloadThreads); return DownloadManagers.createDownloadManager(resolver, executor, cfg.scheduleDelay, cfg.scheduleMaxRun); } - private Dictionary getMavenConfig() throws IOException { + private Dictionary getMavenConfig(String pid) throws IOException { Hashtable props = new Hashtable<>(); if (configurationAdmin != null) { - Configuration config = configurationAdmin.getConfiguration("org.ops4j.pax.url.mvn", null); + Configuration config = configurationAdmin.getConfiguration(pid, null); if (config != null) { Dictionary cfg = config.getProcessedProperties(null); if (cfg != null) { diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/util/PropertyResolver.java b/features/core/src/main/java/org/apache/karaf/features/internal/util/PropertyResolver.java new file mode 100644 index 00000000000..77f90934293 --- /dev/null +++ b/features/core/src/main/java/org/apache/karaf/features/internal/util/PropertyResolver.java @@ -0,0 +1,89 @@ +/* + * 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.util; + +import java.util.Dictionary; + +import org.osgi.framework.BundleContext; + +/** + * Resolves a property by name, optionally delegating to a fallback resolver. + * + *

A blank value is treated as no value, so resolution falls through to the fallback.

+ */ +@FunctionalInterface +public interface PropertyResolver { + + /** + * Resolve a property. + * + * @param key the property name. + * @return the property value, or null if unknown. + */ + String get(String key); + + /** + * A resolver backed by a dictionary. + * + * @param properties the properties to read, may be null. + * @return the resolver. + */ + static PropertyResolver forDictionary(Dictionary properties) { + return forDictionary(properties, null); + } + + /** + * A resolver backed by a dictionary, falling back to another resolver. + * + * @param properties the properties to read, may be null. + * @param fallback the resolver to consult when the dictionary has no value, may be null. + * @return the resolver. + */ + static PropertyResolver forDictionary(Dictionary properties, PropertyResolver fallback) { + return key -> { + String value = null; + if (properties != null) { + Object raw = properties.get(key); + if (raw instanceof String) { + value = (String) raw; + } + } + if (value != null && value.trim().isEmpty()) { + value = null; + } + if (value == null && fallback != null) { + value = fallback.get(key); + } + return value; + }; + } + + /** + * A resolver backed by the framework properties, which themselves fall back to system properties. + * It gives access to e.g. ${karaf.base}. + * + * @param bundleContext the bundle context to read, may be null. + * @return the resolver. + */ + static PropertyResolver forBundleContext(BundleContext bundleContext) { + return key -> { + String value = bundleContext == null ? null : bundleContext.getProperty(key); + return value != null && value.trim().isEmpty() ? null : value; + }; + } + +} diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/util/PropertySubstitutor.java b/features/core/src/main/java/org/apache/karaf/features/internal/util/PropertySubstitutor.java new file mode 100644 index 00000000000..6e0a76354a8 --- /dev/null +++ b/features/core/src/main/java/org/apache/karaf/features/internal/util/PropertySubstitutor.java @@ -0,0 +1,104 @@ +/* + * 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.util; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.Properties; +import java.util.StringTokenizer; + +/** + * Substitutes ${...} placeholders in a string. + * + *

System properties take precedence over the supplied properties. Placeholders that can't be + * resolved are left untouched, so callers can detect and report them. Nesting + * (${a${b}}) is supported.

+ */ +public final class PropertySubstitutor { + + private static final String MARKER = "${"; + + private PropertySubstitutor() { } + + /** + * Substitute the placeholders found in value. + * + * @param properties the properties to resolve placeholders against. + * @param value the value to substitute, may be null. + * @return the substituted value. + */ + public static String substitute(Properties properties, String value) { + if (value == null || value.indexOf('$') < 0 || !value.contains(MARKER)) { + return value; + } + Deque stack = new ArrayDeque<>(); + StringTokenizer tokenizer = new StringTokenizer(value, "${}", true); + while (tokenizer.hasMoreTokens()) { + processToken(tokenizer.nextToken(), stack, properties); + } + StringBuilder result = new StringBuilder(); + while (!stack.isEmpty()) { + result.insert(0, stack.pop()); + } + return result.toString(); + } + + private static void processToken(String token, Deque stack, Properties properties) { + if ("}".equals(token)) { + if (stack.size() < 2) { + // unbalanced closing brace - keep it verbatim + push(stack, token); + return; + } + String name = stack.pop(); + String marker = stack.pop(); + if (MARKER.equals(marker)) { + startProperty(name, properties, stack); + } else { + push(stack, marker); + push(stack, MARKER + name + "}"); + } + } else if ("$".equals(token)) { + stack.push(token); + } else { + push(stack, token); + } + } + + private static void startProperty(String name, Properties properties, Deque stack) { + String value = System.getProperty(name); + if (value == null && properties != null) { + value = properties.getProperty(name); + } + push(stack, value == null ? MARKER + name + "}" : value); + } + + private static void push(Deque stack, String value) { + if (stack.isEmpty()) { + stack.push(value); + return; + } + String top = stack.pop(); + if (MARKER.equals(top)) { + stack.push(top); + stack.push(value); + } else { + stack.push(top + value); + } + } + +} diff --git a/features/core/src/main/java/org/apache/karaf/features/spi/MavenResolver.java b/features/core/src/main/java/org/apache/karaf/features/spi/MavenResolver.java new file mode 100644 index 00000000000..eadb8972c25 --- /dev/null +++ b/features/core/src/main/java/org/apache/karaf/features/spi/MavenResolver.java @@ -0,0 +1,108 @@ +/* + * 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.spi; + +import java.io.Closeable; +import java.io.File; +import java.io.IOException; + +/** + * Resolves mvn: URIs to local files. + * + *

This is the only contract the features service requires from a Maven artifact + * provider. Implementations are looked up through {@link MavenResolvers}.

+ */ +public interface MavenResolver extends Closeable { + + /** + * How likely a failed resolution is to succeed when retried. + */ + enum RetryChance { + /** Retrying will never help. */ + NEVER, + /** Retrying may help, but is unlikely to. */ + LOW, + /** Retrying is likely to help. */ + HIGH, + /** Not enough information to tell. */ + UNKNOWN + } + + /** + * Resolve a mvn: URI to a local file. + * + * @param url the URI to resolve. + * @return the resolved file. + * @throws IOException if the artifact can't be resolved. + */ + File resolve(String url) throws IOException; + + /** + * Resolve a mvn: URI to a local file, carrying over the failure of a + * previous attempt so the implementation can adjust its behaviour (repository + * ordering, update policies, ...). + * + * @param url the URI to resolve. + * @param previousException the exception thrown by the previous attempt, or null. + * @return the resolved file. + * @throws IOException if the artifact can't be resolved. + */ + File resolve(String url, Exception previousException) throws IOException; + + /** + * Resolve a Maven artifact from its coordinates. + * + *

The default implementation builds the equivalent mvn: URI and delegates to + * {@link #resolve(String)}.

+ * + * @param groupId the group id. + * @param artifactId the artifact id. + * @param classifier the classifier, or null/empty for none. + * @param extension the extension (packaging/type), or null/empty for the default. + * @param version the version. + * @return the resolved file. + * @throws IOException if the artifact can't be resolved. + */ + default File resolve(String groupId, String artifactId, String classifier, String extension, String version) + throws IOException { + StringBuilder uri = new StringBuilder("mvn:") + .append(groupId).append('/').append(artifactId).append('/').append(version); + boolean hasClassifier = classifier != null && !classifier.isEmpty(); + if (hasClassifier || (extension != null && !extension.isEmpty())) { + uri.append('/').append(extension == null ? "" : extension); + } + if (hasClassifier) { + uri.append('/').append(classifier); + } + return resolve(uri.toString()); + } + + /** + * Tell whether a failed resolution is worth retrying. + * + * @param exception the exception thrown by {@link #resolve(String, Exception)}. + * @return the chance that a retry succeeds. + */ + default RetryChance isRetryableException(Exception exception) { + return RetryChance.UNKNOWN; + } + + @Override + default void close() throws IOException { + } + +} diff --git a/features/core/src/main/java/org/apache/karaf/features/spi/MavenResolverFactory.java b/features/core/src/main/java/org/apache/karaf/features/spi/MavenResolverFactory.java new file mode 100644 index 00000000000..a6b7ec69c4d --- /dev/null +++ b/features/core/src/main/java/org/apache/karaf/features/spi/MavenResolverFactory.java @@ -0,0 +1,57 @@ +/* + * 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.spi; + +import java.util.Dictionary; + +/** + * Creates {@link MavenResolver} instances. + * + *

Providers are discovered with {@link java.util.ServiceLoader} (see + * {@link MavenResolvers}), so a provider must declare itself in + * META-INF/services/org.apache.karaf.features.spi.MavenResolverFactory.

+ */ +public interface MavenResolverFactory { + + /** + * The ConfigAdmin PID holding this provider's configuration. It is also the default + * prefix of the configuration properties. + * + * @return the configuration PID. + */ + String getConfigurationPid(); + + /** + * Create a resolver. + * + * @param configuration the resolver configuration. + * @param propertyPrefix the prefix of the keys to read from configuration. + * @return a new resolver. + */ + MavenResolver create(Dictionary configuration, String propertyPrefix); + + /** + * Create a resolver, reading the configuration under {@link #getConfigurationPid()}. + * + * @param configuration the resolver configuration. + * @return a new resolver. + */ + default MavenResolver create(Dictionary configuration) { + return create(configuration, getConfigurationPid()); + } + +} diff --git a/features/core/src/main/java/org/apache/karaf/features/spi/MavenResolvers.java b/features/core/src/main/java/org/apache/karaf/features/spi/MavenResolvers.java new file mode 100644 index 00000000000..72eca212adc --- /dev/null +++ b/features/core/src/main/java/org/apache/karaf/features/spi/MavenResolvers.java @@ -0,0 +1,62 @@ +/* + * 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.spi; + +import java.util.Iterator; +import java.util.ServiceConfigurationError; +import java.util.ServiceLoader; + +/** + * Looks up the {@link MavenResolverFactory} provider to use. + */ +public final class MavenResolvers { + + private MavenResolvers() { } + + /** + * Look up the factory provided by the class loader of this class. + * + * @return the factory. + * @throws IllegalStateException if no provider is available. + */ + public static MavenResolverFactory factory() { + return factory(MavenResolvers.class.getClassLoader()); + } + + /** + * Look up the factory provided by the given class loader. + * + * @param classLoader the class loader to search for providers. + * @return the factory. + * @throws IllegalStateException if no provider is available. + */ + public static MavenResolverFactory factory(ClassLoader classLoader) { + try { + Iterator factories = + ServiceLoader.load(MavenResolverFactory.class, classLoader).iterator(); + if (factories.hasNext()) { + return factories.next(); + } + } catch (ServiceConfigurationError e) { + throw new IllegalStateException("Unable to load a " + MavenResolverFactory.class.getName() + + " provider. Make sure a Maven resolver provider is available.", e); + } + throw new IllegalStateException("No " + MavenResolverFactory.class.getName() + + " provider found. Make sure a Maven resolver provider is available."); + } + +} diff --git a/features/core/src/test/java/org/apache/karaf/features/internal/maven/TestMavenResolverFactory.java b/features/core/src/test/java/org/apache/karaf/features/internal/maven/TestMavenResolverFactory.java new file mode 100644 index 00000000000..25cc057b900 --- /dev/null +++ b/features/core/src/test/java/org/apache/karaf/features/internal/maven/TestMavenResolverFactory.java @@ -0,0 +1,54 @@ +/* + * 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.maven; + +import java.io.File; +import java.io.IOException; +import java.util.Dictionary; + +import org.apache.karaf.features.spi.MavenResolver; +import org.apache.karaf.features.spi.MavenResolverFactory; + +/** + * {@link MavenResolverFactory} for the tests of this module, which never resolve mvn: + * URIs - they use their own URL handlers. Any actual resolution is a test bug and fails loudly. + */ +public class TestMavenResolverFactory implements MavenResolverFactory { + + public static final String PID = "org.apache.karaf.features.test.mvn"; + + @Override + public String getConfigurationPid() { + return PID; + } + + @Override + public MavenResolver create(Dictionary configuration, String propertyPrefix) { + return new MavenResolver() { + @Override + public File resolve(String url) throws IOException { + throw new IOException("No Maven resolver is configured for the features core tests: " + url); + } + + @Override + public File resolve(String url, Exception previousException) throws IOException { + return resolve(url); + } + }; + } + +} diff --git a/features/core/src/test/resources/META-INF/services/org.apache.karaf.features.spi.MavenResolverFactory b/features/core/src/test/resources/META-INF/services/org.apache.karaf.features.spi.MavenResolverFactory new file mode 100644 index 00000000000..5c8e0b923b7 --- /dev/null +++ b/features/core/src/test/resources/META-INF/services/org.apache.karaf.features.spi.MavenResolverFactory @@ -0,0 +1,17 @@ +##--------------------------------------------------------------------------- +## 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. +##--------------------------------------------------------------------------- +org.apache.karaf.features.internal.maven.TestMavenResolverFactory diff --git a/itests/test/pom.xml b/itests/test/pom.xml index ae41411c472..7c47724e476 100644 --- a/itests/test/pom.xml +++ b/itests/test/pom.xml @@ -55,6 +55,12 @@ common ${project.version}
+ + + org.ops4j.pax.url + pax-url-aether + test + jakarta.annotation diff --git a/maven/pom.xml b/maven/pom.xml index b0caee874c9..0875614361d 100644 --- a/maven/pom.xml +++ b/maven/pom.xml @@ -35,6 +35,7 @@ core + resolver-paxurl diff --git a/maven/resolver-paxurl/pom.xml b/maven/resolver-paxurl/pom.xml new file mode 100644 index 00000000000..0bf6bae2593 --- /dev/null +++ b/maven/resolver-paxurl/pom.xml @@ -0,0 +1,103 @@ + + + + + + 4.0.0 + + + org.apache.karaf + karaf + 4.5.0-SNAPSHOT + ../../pom.xml + + + org.apache.karaf.maven + org.apache.karaf.maven.resolver.paxurl + bundle + Apache Karaf :: Maven :: Resolver :: Pax URL + Provides the Karaf MavenResolver SPI on top of pax-url-aether. + + + + + org.apache.karaf + karaf-bom + ${project.version} + pom + import + + + + + + + org.osgi + org.osgi.framework + + + org.osgi + org.osgi.service.cm + + + org.apache.karaf.features + org.apache.karaf.features.core + provided + + + + org.ops4j.pax.url + pax-url-aether + + + + + + + org.apache.felix + maven-bundle-plugin + + + ${project.artifactId} + + org.apache.karaf.maven.resolver.paxurl.Activator + + The Apache Software Foundation + + org.osgi.framework;version="[1,3)", + org.apache.karaf.features.spi, + org.ops4j.pax.url.mvn, + org.osgi.service.cm;resolution:=optional, + * + + + org.apache.karaf.maven.resolver.paxurl + + + org.apache.karaf.features.spi.MavenResolverFactory, + org.apache.karaf.features.spi.MavenResolver + + + + + + + + diff --git a/maven/resolver-paxurl/src/main/java/org/apache/karaf/maven/resolver/paxurl/Activator.java b/maven/resolver-paxurl/src/main/java/org/apache/karaf/maven/resolver/paxurl/Activator.java new file mode 100644 index 00000000000..12e5f001bea --- /dev/null +++ b/maven/resolver-paxurl/src/main/java/org/apache/karaf/maven/resolver/paxurl/Activator.java @@ -0,0 +1,71 @@ +/* + * 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.maven.resolver.paxurl; + +import java.io.IOException; +import java.util.Hashtable; + +import org.apache.karaf.features.spi.MavenResolver; +import org.apache.karaf.features.spi.MavenResolverFactory; +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceRegistration; + +/** + * Registers the pax-url backed {@link MavenResolverFactory}, so that the features service can + * resolve mvn: URIs, along with a ready to use {@link MavenResolver} for the + * components that just want to resolve an artifact. + */ +public class Activator implements BundleActivator { + + private ServiceRegistration factoryRegistration; + private ServiceRegistration resolverRegistration; + private LazyMavenResolver resolver; + + @Override + public void start(BundleContext context) { + Hashtable properties = new Hashtable<>(); + properties.put("provider", "pax-url-aether"); + + MavenResolverFactory factory = new PaxUrlMavenResolverFactory(); + factoryRegistration = context.registerService(MavenResolverFactory.class, factory, properties); + + resolver = new LazyMavenResolver(context, factory); + resolverRegistration = context.registerService(MavenResolver.class, resolver, properties); + } + + @Override + public void stop(BundleContext context) { + if (resolverRegistration != null) { + resolverRegistration.unregister(); + resolverRegistration = null; + } + if (factoryRegistration != null) { + factoryRegistration.unregister(); + factoryRegistration = null; + } + if (resolver != null) { + try { + resolver.close(); + } catch (IOException e) { + // the resolver is going away anyway + } + resolver = null; + } + } + +} diff --git a/maven/resolver-paxurl/src/main/java/org/apache/karaf/maven/resolver/paxurl/LazyMavenResolver.java b/maven/resolver-paxurl/src/main/java/org/apache/karaf/maven/resolver/paxurl/LazyMavenResolver.java new file mode 100644 index 00000000000..6199444e3c3 --- /dev/null +++ b/maven/resolver-paxurl/src/main/java/org/apache/karaf/maven/resolver/paxurl/LazyMavenResolver.java @@ -0,0 +1,127 @@ +/* + * 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.maven.resolver.paxurl; + +import java.io.File; +import java.io.IOException; +import java.util.Dictionary; +import java.util.Enumeration; +import java.util.Hashtable; + +import org.apache.karaf.features.spi.MavenResolver; +import org.apache.karaf.features.spi.MavenResolverFactory; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceReference; +import org.osgi.service.cm.Configuration; +import org.osgi.service.cm.ConfigurationAdmin; + +/** + * A {@link MavenResolver} that builds its delegate on first use, from the ConfigAdmin + * configuration of the factory's PID. + * + *

Creating it lazily means this bundle does not have to wait for ConfigAdmin to start, and + * configuration changes made before the first resolution are picked up.

+ */ +public class LazyMavenResolver implements MavenResolver { + + private final BundleContext bundleContext; + private final MavenResolverFactory factory; + + private volatile MavenResolver delegate; + + public LazyMavenResolver(BundleContext bundleContext, MavenResolverFactory factory) { + this.bundleContext = bundleContext; + this.factory = factory; + } + + private MavenResolver delegate() throws IOException { + MavenResolver resolver = delegate; + if (resolver == null) { + synchronized (this) { + resolver = delegate; + if (resolver == null) { + resolver = factory.create(readConfiguration()); + delegate = resolver; + } + } + } + return resolver; + } + + private Dictionary readConfiguration() throws IOException { + Hashtable properties = new Hashtable<>(); + ServiceReference reference = + bundleContext.getServiceReference(ConfigurationAdmin.class); + if (reference == null) { + return properties; + } + try { + ConfigurationAdmin configurationAdmin = bundleContext.getService(reference); + if (configurationAdmin == null) { + return properties; + } + Configuration configuration = configurationAdmin.getConfiguration(factory.getConfigurationPid(), null); + if (configuration != null) { + Dictionary cfg = configuration.getProcessedProperties(null); + if (cfg != null) { + for (Enumeration keys = cfg.keys(); keys.hasMoreElements(); ) { + String key = keys.nextElement(); + Object value = cfg.get(key); + if (key != null && value != null) { + properties.put(key, value.toString()); + } + } + } + } + return properties; + } finally { + bundleContext.ungetService(reference); + } + } + + @Override + public File resolve(String url) throws IOException { + return delegate().resolve(url); + } + + @Override + public File resolve(String url, Exception previousException) throws IOException { + return delegate().resolve(url, previousException); + } + + @Override + public File resolve(String groupId, String artifactId, String classifier, String extension, String version) + throws IOException { + return delegate().resolve(groupId, artifactId, classifier, extension, version); + } + + @Override + public RetryChance isRetryableException(Exception exception) { + MavenResolver resolver = delegate; + return resolver == null ? RetryChance.UNKNOWN : resolver.isRetryableException(exception); + } + + @Override + public void close() throws IOException { + MavenResolver resolver = delegate; + delegate = null; + if (resolver != null) { + resolver.close(); + } + } + +} diff --git a/maven/resolver-paxurl/src/main/java/org/apache/karaf/maven/resolver/paxurl/PaxUrlMavenResolver.java b/maven/resolver-paxurl/src/main/java/org/apache/karaf/maven/resolver/paxurl/PaxUrlMavenResolver.java new file mode 100644 index 00000000000..c178d422dfc --- /dev/null +++ b/maven/resolver-paxurl/src/main/java/org/apache/karaf/maven/resolver/paxurl/PaxUrlMavenResolver.java @@ -0,0 +1,74 @@ +/* + * 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.maven.resolver.paxurl; + +import java.io.File; +import java.io.IOException; +import java.util.Objects; + +import org.apache.karaf.features.spi.MavenResolver; + +/** + * Adapts a pax-url-aether resolver to the Karaf {@link MavenResolver} SPI. + * + *

This bundle is the only place in Karaf that links against org.ops4j.pax.url.mvn.

+ */ +public class PaxUrlMavenResolver implements MavenResolver { + + private final org.ops4j.pax.url.mvn.MavenResolver delegate; + + public PaxUrlMavenResolver(org.ops4j.pax.url.mvn.MavenResolver delegate) { + this.delegate = Objects.requireNonNull(delegate, "delegate"); + } + + @Override + public File resolve(String url) throws IOException { + return delegate.resolve(url); + } + + @Override + public File resolve(String url, Exception previousException) throws IOException { + return delegate.resolve(url, previousException); + } + + @Override + public File resolve(String groupId, String artifactId, String classifier, String extension, String version) + throws IOException { + return delegate.resolve(groupId, artifactId, classifier, extension, version); + } + + @Override + public RetryChance isRetryableException(Exception exception) { + switch (delegate.isRetryableException(exception)) { + case NEVER: + return RetryChance.NEVER; + case LOW: + return RetryChance.LOW; + case HIGH: + return RetryChance.HIGH; + case UNKNOWN: + default: + return RetryChance.UNKNOWN; + } + } + + @Override + public void close() throws IOException { + delegate.close(); + } + +} diff --git a/maven/resolver-paxurl/src/main/java/org/apache/karaf/maven/resolver/paxurl/PaxUrlMavenResolverFactory.java b/maven/resolver-paxurl/src/main/java/org/apache/karaf/maven/resolver/paxurl/PaxUrlMavenResolverFactory.java new file mode 100644 index 00000000000..3b754ccbf39 --- /dev/null +++ b/maven/resolver-paxurl/src/main/java/org/apache/karaf/maven/resolver/paxurl/PaxUrlMavenResolverFactory.java @@ -0,0 +1,42 @@ +/* + * 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.maven.resolver.paxurl; + +import java.util.Dictionary; + +import org.apache.karaf.features.spi.MavenResolver; +import org.apache.karaf.features.spi.MavenResolverFactory; +import org.ops4j.pax.url.mvn.MavenResolvers; + +/** + * {@link MavenResolverFactory} backed by pax-url-aether. + */ +public class PaxUrlMavenResolverFactory implements MavenResolverFactory { + + public static final String PID = "org.ops4j.pax.url.mvn"; + + @Override + public String getConfigurationPid() { + return PID; + } + + @Override + public MavenResolver create(Dictionary configuration, String propertyPrefix) { + return new PaxUrlMavenResolver(MavenResolvers.createMavenResolver(configuration, propertyPrefix)); + } + +} diff --git a/maven/resolver-paxurl/src/main/resources/META-INF/services/org.apache.karaf.features.spi.MavenResolverFactory b/maven/resolver-paxurl/src/main/resources/META-INF/services/org.apache.karaf.features.spi.MavenResolverFactory new file mode 100644 index 00000000000..9a4f90e3bdc --- /dev/null +++ b/maven/resolver-paxurl/src/main/resources/META-INF/services/org.apache.karaf.features.spi.MavenResolverFactory @@ -0,0 +1,17 @@ +##--------------------------------------------------------------------------- +## 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. +##--------------------------------------------------------------------------- +org.apache.karaf.maven.resolver.paxurl.PaxUrlMavenResolverFactory diff --git a/profile/pom.xml b/profile/pom.xml index c038a0c0966..c1db488ec42 100644 --- a/profile/pom.xml +++ b/profile/pom.xml @@ -100,6 +100,13 @@ org.apache.karaf.features.core true
+ + + org.apache.karaf.maven + org.apache.karaf.maven.resolver.paxurl + ${project.version} + true + org.eclipse.platform 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 76bc5d403cf..df4820d12d4 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 @@ -93,8 +93,9 @@ import org.apache.karaf.util.Version; import org.apache.karaf.util.config.PropertiesLoader; import org.apache.karaf.util.maven.Parser; -import org.ops4j.pax.url.mvn.MavenResolver; -import org.ops4j.pax.url.mvn.MavenResolvers; +import org.apache.karaf.features.spi.MavenResolver; +import org.apache.karaf.features.spi.MavenResolverFactory; +import org.apache.karaf.features.spi.MavenResolvers; import org.osgi.framework.Constants; import org.osgi.framework.wiring.BundleRevision; import org.osgi.resource.Resource; @@ -126,6 +127,11 @@ public class Builder { private static final String LIBRARY_CLAUSE_DELEGATE = "delegate"; private static final String START_LEVEL = "start-level"; + /** + * @deprecated the configuration PID is now provided by the + * {@link MavenResolverFactory} in use, see {@link MavenResolverFactory#getConfigurationPid()}. + */ + @Deprecated public static final String ORG_OPS4J_PAX_URL_MVN_PID = "org.ops4j.pax.url.mvn"; /** @@ -678,7 +684,7 @@ public Builder ignoreDependencyFlag(boolean ignoreDependencyFlag) { } /** - * Configures builder to use offline pax-url-aether resolver + * Configures builder to use an offline Maven resolver * @return */ public Builder offline() { @@ -686,7 +692,7 @@ public Builder offline() { } /** - * Configures whether pax-url-aether resolver should work in offline mode + * Configures whether the Maven resolver should work in offline mode * @param offline * @return */ @@ -913,8 +919,8 @@ private void doGenerateAssembly() throws Exception { LOGGER.info("Generating Karaf assembly: " + homeDirectory); // - // Create download manager - combination of pax-url-aether and a resolver wrapper that may - // alter the way pax-url-aether resolver works + // Create download manager - combination of the configured MavenResolver provider and a + // resolver wrapper that may alter the way that resolver works // MavenResolver resolver = createMavenResolver(); manager = new CustomDownloadManager(resolver, executor, null, translatedUrls); @@ -1503,18 +1509,19 @@ private Blacklist processBlacklist(Profile initialProfile) throws IOException { } private MavenResolver createMavenResolver() { + MavenResolverFactory factory = MavenResolvers.factory(); + String pid = factory.getConfigurationPid(); Dictionary props = new Hashtable<>(); if (offline) { - props.put(ORG_OPS4J_PAX_URL_MVN_PID + "offline", "true"); + props.put(pid + "offline", "true"); } if (localRepository != null) { - props.put(ORG_OPS4J_PAX_URL_MVN_PID + ".localRepository", localRepository); + props.put(pid + ".localRepository", localRepository); } if (mavenRepositories != null) { - props.put(ORG_OPS4J_PAX_URL_MVN_PID + ".repositories", mavenRepositories); + props.put(pid + ".repositories", mavenRepositories); } - MavenResolver resolver = MavenResolvers.createMavenResolver(props, ORG_OPS4J_PAX_URL_MVN_PID); - return resolverWrapper.apply(resolver); + return resolverWrapper.apply(factory.create(props, pid)); } /** diff --git a/profile/src/main/java/org/apache/karaf/profile/assembly/CustomDownloadManager.java b/profile/src/main/java/org/apache/karaf/profile/assembly/CustomDownloadManager.java index ff49f1159cf..60c770ba864 100644 --- a/profile/src/main/java/org/apache/karaf/profile/assembly/CustomDownloadManager.java +++ b/profile/src/main/java/org/apache/karaf/profile/assembly/CustomDownloadManager.java @@ -23,7 +23,7 @@ import org.apache.karaf.features.internal.download.impl.AbstractDownloadTask; import org.apache.karaf.features.internal.download.impl.MavenDownloadManager; import org.apache.karaf.profile.Profile; -import org.ops4j.pax.url.mvn.MavenResolver; +import org.apache.karaf.features.spi.MavenResolver; public class CustomDownloadManager extends MavenDownloadManager { diff --git a/tooling/karaf-maven-plugin/pom.xml b/tooling/karaf-maven-plugin/pom.xml index 2981d1a788b..3357b636ac1 100644 --- a/tooling/karaf-maven-plugin/pom.xml +++ b/tooling/karaf-maven-plugin/pom.xml @@ -208,6 +208,12 @@ org.ops4j.pax.url pax-url-aether + + + org.apache.karaf.maven + org.apache.karaf.maven.resolver.paxurl + ${project.version} + org.ops4j.pax.url pax-url-war 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 88da22a5b66..a13bc88b45e 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 @@ -93,8 +93,8 @@ import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.project.MavenProject; import org.eclipse.aether.repository.WorkspaceReader; -import org.ops4j.pax.url.mvn.MavenResolver; -import org.ops4j.pax.url.mvn.MavenResolvers; +import org.apache.karaf.features.spi.MavenResolver; +import org.apache.karaf.features.spi.MavenResolvers; import org.osgi.framework.Bundle; import org.osgi.framework.BundleException; import org.osgi.framework.Constants; @@ -200,7 +200,7 @@ public void execute() throws MojoExecutionException { } // TODO: add more configuration bits ? - resolver = new ReactorMavenResolver(reactor, MavenResolvers.createMavenResolver(config, "maven")); + resolver = new ReactorMavenResolver(reactor, MavenResolvers.factory().create(config, "maven")); doExecute(); } diff --git a/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/utils/ReactorMavenResolver.java b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/utils/ReactorMavenResolver.java index c704da1bb98..779017adcc4 100644 --- a/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/utils/ReactorMavenResolver.java +++ b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/utils/ReactorMavenResolver.java @@ -20,12 +20,11 @@ import java.io.IOException; import java.net.MalformedURLException; +import org.apache.karaf.features.spi.MavenResolver; +import org.apache.karaf.util.maven.Parser; import org.eclipse.aether.artifact.Artifact; import org.eclipse.aether.artifact.DefaultArtifact; import org.eclipse.aether.repository.WorkspaceReader; -import org.ops4j.pax.url.mvn.MavenResolver; -import org.ops4j.pax.url.mvn.ServiceConstants; -import org.ops4j.pax.url.mvn.internal.Parser; /** * {@link MavenResolver} that may look up artifacts inside Maven reactor @@ -41,7 +40,7 @@ public ReactorMavenResolver(WorkspaceReader reactor, MavenResolver fallback) { } private Artifact toArtifact(String url) throws MalformedURLException { - if (url.startsWith(ServiceConstants.PROTOCOL + ":")) { + if (url.startsWith("mvn:")) { url = url.substring(4); } Parser parser = new Parser(url); @@ -64,35 +63,10 @@ public File resolve(String url, Exception previousException) throws IOException } @Override - public File resolve(String groupId, String artifactId, String classifier, String extension, String version) throws IOException { + public File resolve(String groupId, String artifactId, String classifier, String extension, String version) + throws IOException { File file = reactor.findArtifact(new DefaultArtifact(groupId, artifactId, classifier, extension, version)); - return file == null ? fallback.resolve(String.format("mvn:%s/%s/%s/%s/%s", groupId, artifactId, version, extension, classifier)) : file; - } - - @Override - public File resolve(String groupId, String artifactId, String classifier, String extension, String version, Exception previousException) throws IOException { - File file = reactor.findArtifact(new DefaultArtifact(groupId, artifactId, classifier, extension, version)); - return file == null ? fallback.resolve(String.format("mvn:%s/%s/%s/%s/%s", groupId, artifactId, version, extension, classifier), previousException) : file; - } - - @Override - public File resolveMetadata(String groupId, String artifactId, String type, String version) throws IOException { - return fallback.resolveMetadata(groupId, artifactId, type, version); - } - - @Override - public File resolveMetadata(String groupId, String artifactId, String type, String version, Exception previousException) throws IOException { - return fallback.resolveMetadata(groupId, artifactId, type, version, previousException); - } - - @Override - public void upload(String groupId, String artifactId, String classifier, String extension, String version, File artifact) throws IOException { - fallback.upload(groupId, artifactId, classifier, extension, version, artifact); - } - - @Override - public void uploadMetadata(String groupId, String artifactId, String type, String version, File artifact) throws IOException { - fallback.uploadMetadata(groupId, artifactId, type, version, artifact); + return file == null ? fallback.resolve(groupId, artifactId, classifier, extension, version) : file; } @Override