FELIX-6759 - PROTOTYPE: use plurl for URL handler factory multiplexing - #552
FELIX-6759 - PROTOTYPE: use plurl for URL handler factory multiplexing#552paulrutter wants to merge 21 commits into
Conversation
Not for merge. This explores the suggestion in #433 (comment) that Felix and Equinox should share a cooperative way to manage the java.net.URL factory singletons. URLHandlers currently claims the JVM-wide URLStreamHandlerFactory by reflectively swapping a private static field on java.net.URL (SecureAction.swapStaticFieldIfNotClass). Getting a MethodHandles.Lookup trusted enough to do that is the only remaining reason the framework uses sun.misc.Unsafe, and it means the last framework to install itself wins the singleton, so two frameworks cannot coexist in one JVM without clobbering each other. Plurl installs a single cooperative router through the supported URL.setURLStreamHandlerFactory API and lets any number of parties register with it; each registered factory answers shouldHandle(Class) to claim the calling class. That maps onto what URLHandlers.getFrameworkFromContext() already does by hand, and it needs no Unsafe. - Vendors the 11 plurl sources from eclipse-osgi-technology/plurl (commit 6581777) into org.apache.felix.framework.plurl. The only change is the package rename; every license header and copyright notice is preserved verbatim. This mirrors eclipse-equinox/equinox#848, which vendored the same files into org.eclipse.equinox.plurl. - Adds PlurlURLHandlers, adapting URLHandlers to PlurlStreamHandlerFactory and PlurlContentHandlerFactory, with shouldHandle(Class) recognising this framework's bundle class loaders. UNRESOLVED LICENSING QUESTION: every vendored file declares "SPDX-License-Identifier: EPL-2.0" and "Copyright (c) 2025 IBM Corporation", while plurl's own LICENSE and pom.xml declare Apache-2.0. EPL-2.0 is Category B at the ASF and may not be included in an Apache source release, so this cannot be merged as it stands. See framework/src/main/java/org/apache/felix/framework/plurl/README.md. Verified on JDK 25: the framework compiles and its tests are unchanged (the same 8 pre-existing platform specific failures as before this commit). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The pull_request trigger filters on the BASE branch, and only accepted master. A stacked pull request - one opened against another feature branch rather than master - therefore matched neither trigger and got no CI at all, which is what happened to PR #552. Accept feature/** and maintenance/** as a base as well, so stacked work is built before it is merged down. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CI is red on purpose — Apache RAT rejects the vendored sourcesNow that stacked PRs get CI (the All 12 flagged files are the vendored plurl sources — the 11 This is the licensing question answering itself. The failure is being left in place deliberately. It could be silenced with a RAT exclusion for So the technical side is done and the remaining blocker is purely licensing:
@tjwatson — if the EPL headers are carried over from the original Equinox code rather than intended, correcting them upstream to match plurl's own Apache-2.0 LICENSE and pom would turn this red build green and unblock the approach for Felix and anyone else wanting to adopt the library. If they are intentionally EPL-2.0, then Felix needs one of the alternatives listed in the PR description instead. 🤖 Generated with Claude Code |
|
The intention when I moved the source to Somehow that was overlooked when setting up the source in https://github.com/eclipse-osgi-technology/plurl It should be a non-issue to get the headers updated. But what I ultimately wanted to do was have a release out of the osgi-technology project for plurl that would be used by both Equinox and Felix unchanged, in the original package there. |
Thanks for your quick reply! Let's first see if we can get consensus on the JDK 25 PR, then we can revisit this one. Would it be possible to get plurl released as a separate artifact, with the right headers, so we can use it without copying the code into Felix? Apart from that, does this PR make sense to you? |
I've updated the source headers to use Apache-2.0 with: eclipse-osgi-technology/plurl#45 Note that the source also needed an upstream fix I had put in Equinox with PR: eclipse-osgi-technology/plurl#55 I am not sure how long it will take to get a release out of the osgi-technology project because I have not done a release out of this particular project yet. I'll ask existing members of the project about what steps are needed to get a release done. But I don't want that to block you on progress. With the current state of the source I think it should be fine for you to proceed with copying in the source now. |
|
Thanks, that's great! I will pull in the latest version and use it verbatim except the package names. Once the JDK 25 PR lands, this one is next on the list. Once a proper release is out, we can then switch to that artifact instead and remove the vendored sources. |
|
I don't see where you are calling |
| return name.startsWith("org.apache.felix.framework.BundleWiringImpl$BundleClassLoader") | ||
| || name.startsWith("org.apache.felix.framework.ModuleImpl$ModuleClassLoader") | ||
| || name.equals("org.apache.felix.framework.searchpolicy.ContentClassLoader"); |
There was a problem hiding this comment.
For Equinox I do an additional check to make sure the class loader is for a bundle that is actually installed in this framework instance. I assume you would want the same thing here. Otherwise one felix framework may handle lookups for a bundle resolved in another felix framework instance.
Looking into this, i stopped working on the draft when i noticed the license. Will continue with it now that's resolved 👍🏻 |
I should mention that the intention was to use a |
Addresses the review on PR #552 and makes plurl the mechanism rather than an illustration alongside the existing one. Vendored sources refreshed from upstream. Every file is now Apache-2.0 (eclipse-osgi-technology/plurl#45 corrected headers that were EPL-2.0 by oversight, which apache-rat rightly rejected) and includes the fix from eclipse-osgi-technology/plurl#55. Only the package rename is applied. The prototype could not have worked as written: - Plurl.install(..) was never called. The static Plurl.add(..) helpers operate through a plurl: URL, so without installing the router first they fail with "unknown protocol: plurl". Nothing exercised the adapter, so it compiled and the tests passed while being dead code. - shouldHandle only checked that a class came from some Felix bundle class loader. Two frameworks in one JVM both load classes that way, so one framework could answer lookups for a bundle resolved in another. It now also requires the owning framework to match, as Equinox does. Plurl is now the only mechanism, matching Equinox rather than keeping a fallback: - URLHandlers no longer swaps the java.net.URL and URLConnection static fields, and its singleton is no longer constructed. Keeping that as a fallback would have kept the sun.misc.Unsafe path alive, which is what adopting plurl is meant to remove. Equinox does not fall back either: if plurl registration fails it logs and that framework contributes no URL handlers. - Removing the swap turned out to be necessary rather than merely tidy. With URLHandlers taking the singletons first, plurl found them occupied and required --add-opens java.base/java.net to replace them. Installing into a clean JVM uses the supported API and needs no flag. - URLHandlersBundleStreamHandler and URLHandlersStreamHandlerProxy now extend PlurlStreamHandlerBase, exactly as Equinox changed BundleResourceHandler and URLStreamHandlerProxy. Otherwise plurl has to reflect into java.net to drive them. - The router is installed once per JVM and reference counted. Uninstalling it per framework would tear it down while another framework was still using it. - The bundle: handler is deliberately not bound to one framework. The JVM caches one handler per protocol, so a pinned handler is also used for another framework's bundle: URLs; the framework is resolved per call from the UUID in the URL host. - URLHandlers.m_sm is initialised eagerly, since the constructor that used to set it up is no longer invoked. Adds PlurlURLHandlersTest, which starts a framework and asserts that registration actually happened, that the plurl: protocol resolves, and that shouldHandle does not claim classes outside this framework. On JDK 25 the framework suite is at 121 tests with 9 failures, 8 of which are pre-existing platform specific ones unrelated to this change. The remaining failure is URLHandlersTest.urlHandlersWithClassLoaderIsolation, discussed on the pull request. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Thanks for sorting the headers — I refreshed the vendored sources from upstream, so they include the Apache-2.0 headers from eclipse-osgi-technology/plurl#45 and the fix from eclipse-osgi-technology/plurl#55. Only the package rename is applied. And Both of your review points were real defects
Plurl is now the mechanism, not an addition alongside it
On Linux the framework suite is 121 tests with a single error, which is the question below. Routing a URL re-parsed outside any bundle
URL url = bundle.getEntry("...");
new URL(url.toExternalForm()).openStream(); // called from a plain test classThat second Note I deliberately do not bind the I initially assumed Equinox must solve this and Felix was missing something. Reading String host = url.getHost(); // "<bundleId>.<container.hashCode()>"
bundleID = parseBundleIDFromURLHost(host); // parses ONLY the bundle id
Module module = getModule(bundleID); // -> container.getModule(id), the BOUND containerThe container is encoded in the host by Felix's UUID lookup is a stronger guarantee, which is why there's a test for it here and no equivalent upstream. So the question isn't how Equinox does it, but: Would plurl consider routing a A Without that, the Felix-side options are:
I'd rather not do 1 silently. Happy to raise the API idea as a plurl issue with a concrete proposal if you think it's reasonable. |
I am a bit confused by this because there is a unit test in plurl that mimics a copy scenario and a separate class loader (proxy) scenario. The intent of Plurl is not only can the users have their own copy loaded by a difference class loader, but their own copy could live in a completely different package name. For example. see the test that has a copy of Plurl APIs at In this case it reflects on agreed upon method names from the Plurl APIs. This way, as long as the copies mirror the upstream Plurl APIs then they can intermix in the same JVM. This way Felix and Equinox can both run in the same JVM and still multiplex on the protocols even if they copied the plurl API/impl into their own felix and equinox packages. Can you go into more details on what plurl changes you need to get the |
This commit touches only the vendored plurl sources and is meant to be read as a proposal against the upstream plurl project, discussed on #552. It is separated from the Felix side so it can be reviewed, or lifted upstream, on its own. Problem: a factory can currently only be selected from a class on the call stack. If no class is claimed, "the first factory added is selected". That is arbitrary when several instances of the same framework are registered, because they all share a protocol and are told apart only by the URL. It happens in practice when a URL is re-parsed by a caller that is in no bundle, for example new URL(bundleEntryURL.toExternalForm()).openStream(); where nothing on the call stack identifies a framework. Whichever framework registered first is then asked to resolve another framework's URL. Felix detects that and fails; a factory that trusts the selection would answer with the wrong resource. Changes: - PlurlFactory gains a default shouldHandle(URL) returning false, so existing factories are unaffected. A factory that can recognise its own URLs overrides it. - PlurlImpl.findFactory consults shouldHandle(URL) before walking the call stack, reflectively where the factory was compiled against a different copy of the plurl package, and tolerating factories that predate the method. - PlurlFactoryHolder delegates shouldHandle(URL) to the wrapped factory, as it already does for shouldHandle(Class). Without this the default would answer for every holder and the hook would never reach a factory. - PlurlRootURLStreamHandler only records a handler in urlToHandler once the URL is usable for selection. During parseURL the URL is still being populated and its host is not set yet, so a factory cannot claim it and only the call stack is available. Caching that choice would pin the URL to a factory chosen before the URL was known; not recording it lets the first use of the parsed URL select again. The last point is the subtle one: without it a shouldHandle(URL) hook is never reached for a freshly parsed URL, since parseURL runs first and its result is cached for the life of the URL. Note shouldHandle(URL) overloads shouldHandle(Class), which makes shouldHandle(null) ambiguous for callers. A distinct name would avoid that if this is taken upstream. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Uses the plurl shouldHandle(URL) hook added in the previous commit, which is the Felix half of that proposal. Every Felix framework instance in a JVM uses the same bundle: protocol, so the protocol alone does not identify an owner; the framework UUID in the URL host does. PlurlURLHandlers.shouldHandle(URL) compares that UUID against its own framework, so a bundle: URL is routed to the framework that created it even when nothing on the call stack identifies one. That is what URLHandlersTest.urlHandlersWithClassLoaderIsolation exercises: two copies of the framework in separate class loaders, each with its own framework, and a URL re-parsed from a plain test class. Previously the first registered factory was selected and could not resolve the other framework's UUID, failing with "No framework context found". It now passes. On JDK 25 the framework suite is 121 tests with 8 failures, all of them pre-existing platform specific ones unrelated to this work, and no errors. URLHandlersTest, ResourceLoadingTest and PlurlURLHandlersTest all pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
apache-rat flagged the README as the only unapproved file in the framework, since a Markdown file needs the header as an HTML comment. This is our own file, so the header belongs there rather than in the rat excludes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Refines the previous plurl commit so that nothing changes for a factory that does not implement shouldHandle(URL). Raised because Equinox depends on the behaviour the first version altered. The first version skipped recording a handler in urlToHandler while the URL was still being parsed, so that the first use of the parsed URL selected again. That weakened a guarantee plurl documents and Equinox relies on: BundleResourceHandler keeps mutable per URL state (its bundleEntry field, cleared in parseURL and used as a fast path in openConnection), which assumes the same handler serves a URL from parse through to use. Re-selecting could also route to a different container when the call stack at first use differs from the call stack at parse time. Instead the handler is recorded during parseURL exactly as before, and only a factory that positively claims the URL may correct that record, once, after which it stays pinned as before: - findFactory is restored to its original form, so URL based selection no longer affects the general path. - findClaimedPlurlStreamHandler answers only when a factory claims the URL. There is no call stack inspection and no fallback to the first factory, so it returns null unless an owner positively identifies the URL as its own. - URLToHandler.replace updates the record for that URL. A factory that does not implement shouldHandle(URL) never claims, so the recorded handler is never replaced and behaviour is identical to today. That includes both Equinox factories, which declare only shouldHandle(Class). Verified on JDK 25: URLHandlersTest.urlHandlersWithClassLoaderIsolation still passes and the framework suite is unchanged at 121 tests with no errors. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
You're right, my earlier statement was wrong — To answer your question: protocol-level delegation wouldn't help, because every Felix framework instance uses the same
Three things worth knowing, all found by implementing it:
Separately: when no factory claims a URL, declining may be safer than selecting the first factory added, since framework A can be handed a URL naming framework B. Felix's UUID check makes that a clean failure; Equinox resolves only the bundle id against its bound container, so it would answer with its own resource. Happy to leave the fallback as-is if you'd rather. Glad to raise this as a plurl issue/PR, squashed however you prefer. Ready-to-apply patch against
|
b5bbd43 to
c3094d7
Compare
|
Open an issue against eclipse-osgi-technology/plurl and you can attach your PR there also. That way we can discuss possible API additions there. Thanks for looking at this. |
Thanks! Will do and continue the discussion there. |
|
Because I've already released a version of Equinox with the current Plurl signatures I will need to attempt to make any new implementation with the new APIs still be able to handle when copies of the old APIs are still in use in the same JVM instance. That may become impossible over time, but I want to at least try to provide some compatibility between versions of plurl. Otherwise it will become increasingly difficult to be able to update a core framework implementation which hosts other applications which may embed their own version of a framework implementation (Equinox or Felix). Think of a WAR that uses osgi connect to compose their application components using felix or equinox in their WEB-INF/lib folder. And then that WAR being deployed to a server which is implemented with OSGi, such as glassfish(using felix) or liberty(using equinox). |
Replaces the earlier shouldHandle(URL) proposal, which cannot work: plurl
pins its chosen handler onto the URL in parseURL via reflection into the
private java.net.URL handler field, so once that field is writable (any JVM
run with --add-opens java.base/java.net) plurl steps out of the way and never
regains control at openConnection. The hook would never be reached in exactly
the deployments that matter -- the Felix framework bundle declares
Add-opens: java.base/java.net.
Selection has to be decidable at the moment plurl chooses, which is while the
URL is still being parsed and has no host or path yet. The spec does exist at
that point, so:
default boolean shouldHandle(String protocol, String spec)
consulted in findFactory before the call stack is walked. A protocol may be
shared by several factories that are distinguishable only by the URL (multiple
framework instances, where the owner is an id in the host), and such a URL may
be parsed by a caller that no factory recognises from the call stack, leaving
nothing else to select on.
Defaulting to false leaves existing factories unaffected. The signature uses
only java.lang types, so the reflective path finds it on factories compiled
against a different copy of the plurl package, and a factory that predates the
method is tolerated rather than failing -- both needed while released plurl
signatures stay in use in the same JVM. The lookup is cached, because it sits
on the URL parsing path.
PlurlFactoryHolder delegates the new method the same way it already delegates
shouldHandle(Class); without that, findFactory only ever asks the holders,
which answer with the interface default.
Verified in the plurl repo itself: 51 tests pass, including a new test that
fails on unpatched plurl.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… spec Follows the plurl change from shouldHandle(URL) to shouldHandle(protocol, spec). Every Felix framework in the JVM uses the same bundle: protocol, so the protocol alone does not identify an owner -- the framework UUID in the host does. The URL cannot be asked for its host, because plurl has to pick a factory before the URL is parsed, so the host is read out of the spec. Adds a test for the hook, covering another framework's UUID, a port in the host, a non-bundle protocol and a spec with no host. URLHandlersTest.urlHandlersWithClassLoaderIsolation passes, and the suite is at the pre-existing baseline. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
One correction before I open the plurl issue: the
Selection has to be decidable at the moment plurl chooses, i.e. mid-parse, when the URL has no host yet. The spec does exist then: default boolean shouldHandle(String protocol, String spec) { return false; }consulted in On your compatibility point: the signature uses only
51/51 in the plurl repo, including a new |
Now that plurl installs the JVM factories, URLHandlers is never instantiated: its private constructor was the only caller of the reflective field clearing, and nothing constructs it any more. Everything reachable only from there was dead, including the whole cross-classloader rendezvous it existed to support -- registerFrameworkListsForContextSearch published the framework list to a URLHandlers in another classloader, which plurl now does properly. The class keeps only the framework registry that URLHandlersBundleStreamHandler needs to find the owner of a caller when a handler is not bound to one. Removed: the constructor and its handler pre-loading, the two context-search registration methods, the URLStreamHandlerFactory and ContentHandlerFactory implementations (the class is no longer either), the built-in handler lookup and the handler caches. SecureAction loses swapStaticFieldIfNotClass and flush, which had no callers left. This removes every reflective write to a java.net static field. One sun.misc.Unsafe use remains in SecureAction.getAccessor, which is a different concern: it is the setAccessible strategy for deep reflection, still needed by addURLToURLClassLoader when installing framework extension bundles. No behaviour change intended in what remains: registerFrameworkInstance and unregisterFrameworkInstance still perform exactly one list update and one counter step per call, as the lock dance around the removed registry did. Suite is at the pre-existing baseline. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
SecureAction.getAccessor exists to defeat JPMS encapsulation, not because
setAccessible is unavailable: accessor.bytes is a class declared "package
java.net", injected into java.base through a trusted MethodHandles.Lookup
stolen with sun.misc.Unsafe, so that the setAccessible call comes from inside
that module and the access check passes without the package being open.
It was used unconditionally for java.net and jdk.internal.loader, and primed
eagerly in the static initialiser, so every framework start paid for it. On
JDK 25 that means every start prints
WARNING: A terminally deprecated method in sun.misc.Unsafe has been called
WARNING: sun.misc.Unsafe::staticFieldOffset has been called by
org.apache.felix.framework.util.SecureAction
WARNING: sun.misc.Unsafe::staticFieldOffset will be removed in a future
release
Now plain setAccessible is tried first and Unsafe is the fallback for when the
JVM actually refuses. Where the package is open -- the Add-opens of the
org.apache.felix.main launcher, or any --add-opens on the command line -- the
deprecated method is never called at all. The eager priming is dropped, since
the accessor is built on demand.
Unsafe cannot simply be removed: verified on JDK 25 that without --add-opens,
setAccessible on URLClassLoader.addURL throws InaccessibleObjectException, and
ClassPathExtenderFactory needs that and
ClassLoaders$AppClassLoader.appendToClassPathForInstrumentation to install
framework extension bundles.
Suite is at the pre-existing baseline on JDK 21, and ExtensionManagerTest
still installs extensions on JDK 25 through the fallback.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…nstalled
CI on JDK 25 still attributed two terminal deprecation warnings to Felix, one
from the unit tests and one from the TCK run, i.e. from a real framework start:
WARNING: sun.misc.Unsafe::staticFieldOffset has been called by
org.apache.felix.framework.util.SecureAction
DefaultClassLoaderExtender resolved URLClassLoader.addURL and
ClassLoaders$AppClassLoader.appendToClassPathForInstrumentation in its static
initialiser and made both accessible there. That runs from
ExtensionManager.<clinit>, so every framework instance paid for it whether or
not it ever installs an extension bundle, and on a JVM where those packages
are not open the access is served by defining an accessor inside java.base
with sun.misc.Unsafe.
Looking a method up needs no access -- verified on JDK 25 with Unsafe denied
and no --add-opens, getDeclaredMethod succeeds for both while setAccessible
throws InaccessibleObjectException. So only the lookup stays in the static
initialiser, which is all getExtender needs to report whether it can extend,
and the access is requested in add(File), where the method is invoked.
A framework that installs no extension bundle now never calls setAccessible
on a java.base member, and never touches Unsafe. Verified on JDK 25: no
warning is attributed to Felix by StartStopBundleTest or ServiceObjectsTest,
while ExtensionManagerTest still installs extensions and produces exactly one,
at the point the extension is installed.
Suite is at the pre-existing baseline on JDK 21.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The vendored plurl sources carry "Copyright (c) Contributors to the Eclipse Foundation" under the Apache License 2.0, and were not recorded in NOTICE, LICENSE or DEPENDENCIES. Bundled third-party code has to be attributed; the OSGi Alliance entry already in this NOTICE is the same case. No LICENSE change is needed, since plurl is under the same license as the product. Also drops the PROTOTYPE marker from the PlurlURLHandlers javadoc. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ports The plurl copy that wins the install in a JVM is the one that routes, and it may be older than the copy a factory brought with it -- another framework instance, or an application embedding its own. A factory registering with such a router has no way to know that a capability it depends on will never be consulted, so it registers and is then silently misrouted. That matters here: bundle: URLs can only be attributed to a framework by the UUID they carry, so without shouldHandle(String, String) they go to whichever factory registered first. Adds a plurlCapabilities operation reporting a Set<String>, and Plurl.capabilities() as the convenience form. An implementation predating the operation rejects it with an IOException, so absence of an answer is the answer and is reported as an empty set. Also tolerates a leading slash in the operation path, so the documented "plurl://op/<operation>" form works and is not mistaken for an older plurl. Pushed upstream as part of eclipse-osgi-technology/plurl#63. 54 tests pass there, including one covering this. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
If the plurl that won the install in the JVM does not consult shouldHandle(protocol, spec), a bundle: URL parsed by a caller outside any bundle is handed to whichever factory registered first, which cannot resolve another instance's UUID. Nothing on the factory side can fix that, so it is reported at startup instead of surfacing later as a failed resource lookup. The test pins the capability of the vendored copy, so that re-vendoring an older plurl fails there rather than quietly turning this warning on for everyone. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Added the silent-degradation warning, and the upstream half that makes it possible — plurl#63 now has a second commit. The problem: the plurl copy that wins the install in a JVM is the one that routes, and it may be older than the copy this framework brought. We register successfully and are then silently misrouted, with no way to tell — Upstream now answers a
Also Framework suite at the pre-existing baseline. @tjwatson no action needed on the Felix commits, but the capability commit upstream is take-it-or-leave-it — the selection commit stands on its own without it. |
Warning
Draft / prototype — not for merge. There is an unresolved licensing question below that blocks this as it stands.
Prototype exploring @tjwatson's suggestion in #433 (comment) that Felix and Equinox should share a cooperative mechanism for the
java.net.URLfactory singletons. Branches off, and targets, #433.The problem
URLHandlersclaims the JVM-wideURLStreamHandlerFactoryby reflectively swapping a private static field onjava.net.URL, viaSecureAction.swapStaticFieldIfNotClass. Obtaining aMethodHandles.Lookuptrusted enough to do that is the only remaining reason the framework usessun.misc.Unsafe— thestaticFieldOffset/getObjectsteal ofIMPL_LOOKUP. It also means whichever framework installs itself last wins the singleton, so Felix and Equinox cannot coexist in one JVM without clobbering each other.To be clear about urgency: this is not a JDK 25 blocker. That
Unsafeblock is guarded bycatch (Throwable)and falls back toAccessibleObject.setAccessible, so on JDK 25 it only produces the familiar warning. This is about JDK 26+ and about framework interop.What this does
6581777) intoorg.apache.felix.framework.plurl. The only change is the package rename; every license header and copyright notice is preserved verbatim. This mirrors what Equinox did in Plurl API for managing Java singleton factories in URL eclipse-equinox/equinox#848.PlurlURLHandlers, adaptingURLHandlerstoPlurlStreamHandlerFactory/PlurlContentHandlerFactory. Plurl installs one cooperative router through the supportedURL.setURLStreamHandlerFactoryAPI and routes by asking each registered factoryshouldHandle(Class)— which maps directly onto whatURLHandlers.getFrameworkFromContext()already does by hand, and needs noUnsafe.Verified on JDK 25: the framework compiles and its test results are unchanged.
Every vendored source file declares:
but plurl's own
LICENSEfile and itspom.xmlboth declare Apache-2.0. (ItsNOTICEalso still refers to "slf4j-osgi", which looks like a copy-paste leftover.)This matters because of where it is being copied to. EPL-2.0 is Category B at the ASF and must not be included in an Apache source release. Equinox was free to vendor these files because Eclipse projects are EPL-2.0 natively; Felix is not.
Given the repo's LICENSE and pom say Apache-2.0, my guess is the EPL headers are carried over from the original Equinox code rather than intended. Could you confirm the intended license and, if it is Apache-2.0, correct the source headers upstream? That would unblock the vendoring approach for Felix and anyone else.
If the headers are intentionally EPL-2.0, then Felix cannot vendor the sources and the options become:
cc @stbischof @laeubi @mattrpav
🤖 Generated with Claude Code