Skip to content

feat(features): decouple features service from Pax URL - #2928

Open
jbonofre wants to merge 1 commit into
apache:mainfrom
jbonofre:feature/decouple-pax-url-spi
Open

jbonofre wants to merge 1 commit into
apache:mainfrom
jbonofre:feature/decouple-pax-url-spi

Conversation

@jbonofre

Copy link
Copy Markdown
Member

Why

org.apache.karaf.features.core linked directly against org.ops4j.pax.url.mvn, and used ops4j-base + pax-swissbox for property resolution. That made Pax URL a hard dependency of the core provisioning path, so a distribution without Pax URL was not possible and the resolver could not be refactored independently.

This removes that coupling without changing anything users see. Pax URL stays in the standard distribution, same bundle, same configuration, same behaviour.

Relates to #2212.

What

A Karaf owned resolver contractorg.apache.karaf.features.spi, exported by features core:

public interface MavenResolver extends Closeable {
    enum RetryChance { NEVER, LOW, HIGH, UNKNOWN }
    File resolve(String url) throws IOException;
    File resolve(String url, Exception previousException) throws IOException;
    default File resolve(String groupId, String artifactId, String classifier,
                         String extension, String version) throws IOException { ... }
    default RetryChance isRetryableException(Exception exception) { ... }
}

public interface MavenResolverFactory {
    String getConfigurationPid();
    MavenResolver create(Dictionary<String, String> configuration, String propertyPrefix);
}

Providers are found as an OSGi service inside the framework, and with ServiceLoader outside it, so the assembly Builder and the karaf-maven-plugin keep working unchanged.

The Pax URL binding moved out into a new bundle, org.apache.karaf.maven.resolver.paxurl. It registers a MavenResolverFactory and a MavenResolver built lazily from the org.ops4j.pax.url.mvn PID, so it does not have to wait for ConfigAdmin and configuration changes made before the first resolution are still picked up. It ships in the framework feature at start level 14.

No new startup ordering machinery. The features Activator already declared its requirements, so the factory is just another @RequireService:

org.apache.karaf.features.spi.MavenResolverFactory=
org.osgi.service.cm.ConfigurationAdmin=
org.osgi.service.url.URLStreamHandlerService=(url.handler.protocol\=mvn)

BaseActivator holds doStart() until a provider is present. All three requirements are Karaf or OSGi contracts — none names Pax URL.

Property resolution in FeaturesProcessingSerializer no longer uses ops4j-base / pax-swissbox. PropertyResolver and PropertySubstitutor reimplement the semantics the serializer depends on: system properties take precedence over the supplied dictionary, blank means absent, and unresolved ${...} are left untouched so the existing warning still fires.

The unused tinybundles test dependency is dropped, and feature:export-bundles now consumes the MavenResolver service the provider registers.

Result

org.apache.karaf.features.core and org.apache.karaf.features.command have no org.ops4j dependency, import or private package. Across the whole boot set, exactly one bundle imports org.ops4j.pax.url:

IMPORTS pax.url -> org.apache.karaf.maven.resolver.paxurl

A Pax URL free distribution now only needs a bundle registering a MavenResolverFactory and an mvn: URLStreamHandlerService, plus a framework feature that lists it instead of pax-url-aether + resolver-paxurl. Nothing in the features service has to change.

Behaviour changes

Two, both in previously broken paths:

  • Malformed ${...} placeholders in org.apache.karaf.features.xml are kept verbatim instead of raising EmptyStackException. Well formed input never reaches that path.
  • ReactorMavenResolver implements 3 methods instead of 9 and uses org.apache.karaf.util.maven.Parser rather than the Pax URL internal one.

Optional dependencies are not transitive, so profile, karaf-maven-plugin and itests/test now declare explicitly what they used to inherit from features core.

Testing

  • Full reactor build green.
  • Unit tests green: features core (139), features command (16), profile (13), karaf-maven-plugin (24).
  • The assembled distribution boots, the features service starts, and boot features are provisioned through the new path, including resolving and installing mvn:org.ops4j.pax.url/pax-url-wrap/3.0.3/jar/uber.
  • Generated etc/startup.properties confirms the layering:
mvn:org.ops4j.pax.url/pax-url-aether/3.0.3                        = 5
mvn:org.apache.karaf.maven/org.apache.karaf.maven.resolver.paxurl = 14
mvn:org.apache.karaf.features/org.apache.karaf.features.core      = 15

The features service linked directly against org.ops4j.pax.url.mvn, plus
ops4j-base and pax-swissbox for property resolution. That made Pax URL a hard
dependency of the core provisioning path and blocked assembling a distribution
without it.

Introduce org.apache.karaf.features.spi, a Karaf owned resolver contract
(MavenResolver, MavenResolverFactory, MavenResolvers), and consume it
everywhere the features service resolves mvn: URIs. Providers are discovered as
an OSGi service inside the framework and with ServiceLoader outside it, so the
assembly builder and the karaf-maven-plugin keep working unchanged.

The Pax URL binding moves to a new bundle, org.apache.karaf.maven.resolver.paxurl,
which registers a MavenResolverFactory and a MavenResolver built lazily from the
org.ops4j.pax.url.mvn PID. It ships in the standard distribution at start level
14, so behaviour and configuration are unchanged; it is now the only bundle in
the boot set that imports org.ops4j.pax.url.

The features Activator declares the factory with @RequireService, so the
features service simply waits for a provider instead of building a resolver
itself - no new startup ordering machinery.

Property resolution in FeaturesProcessingSerializer no longer uses ops4j-base
and pax-swissbox. PropertyResolver and PropertySubstitutor reimplement the
semantics the serializer relies on: system properties take precedence over the
supplied dictionary, blank means absent, and unresolved ${...} placeholders are
left untouched so the existing warning still fires. Malformed placeholders are
now kept verbatim rather than raising EmptyStackException.

org.apache.karaf.features.core and org.apache.karaf.features.command no longer
have any org.ops4j dependency, import or private package.

Relates to apache#2212
@github-actions

Copy link
Copy Markdown

Test Results

  522 files   - 204    522 suites   - 204   10m 1s ⏱️ - 1h 5m 19s
  777 tests  - 208    741 ✅  - 196   34 💤  - 14  0 ❌ ±0  2 🔥 +2 
2 331 runs   - 624  2 223 ✅  - 588  102 💤  - 42  0 ❌ ±0  6 🔥 +6 

For more details on these errors, see this check.

Results for commit 86564e0. ± Comparison against base commit a110f75.

This pull request removes 210 and adds 2 tests. Note that renamed tests count towards both.
org.apache.karaf.examples.itests.ExampleITest ‑ listBundleCommand
org.apache.karaf.examples.itests.ExampleWithConfigITest ‑ simpleTest
org.apache.karaf.itests.BundleTest ‑ capabilitiesCommand
org.apache.karaf.itests.BundleTest ‑ classesCommand
org.apache.karaf.itests.BundleTest ‑ diagCommand
org.apache.karaf.itests.BundleTest ‑ findClassCommand
org.apache.karaf.itests.BundleTest ‑ headersCommand
org.apache.karaf.itests.BundleTest ‑ infoCommand
org.apache.karaf.itests.BundleTest ‑ installNonOsgiBundle
org.apache.karaf.itests.BundleTest ‑ installUninstallCommand
…
org.apache.karaf.examples.itests.ExampleITest ‑ Unknown test
org.apache.karaf.examples.itests.ExampleWithConfigITest ‑ Unknown test

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant