Skip to content

Add shaded artifact (classifier "shaded") with relocated dependencies - #102

Closed
bertysentry wants to merge 2 commits into
mainfrom
feature/shaded-artifact
Closed

Add shaded artifact (classifier "shaded") with relocated dependencies#102
bertysentry wants to merge 2 commits into
mainfrom
feature/shaded-artifact

Conversation

@bertysentry

Copy link
Copy Markdown
Contributor

Summary

Attaches a second artifact — winrm-java-<version>-shaded.jar (classifier shaded) — in which all dependencies are relocated under org.metricshub.winrm.shaded.*, so embedding winrm-java cannot clash with, or leak ServiceLoader/JAXP registrations into, the host application's classpath. The regular thin JAR is unchanged; existing consumers are unaffected.

Why

Investigating a customer-reported WinRM failure in MetricsHub Enterprise (see MetricsHub/metricshub-community#1271) showed how fragile a flat classpath is around this library:

  • winrm-java's own tree pollutes the host: Woodstox registers itself as the JVM-wide default XMLInputFactory for every other component on the classpath.
  • The host can pollute winrm-java: version clashes on CXF/HttpClient/JAXB are one mvn dependency away.

The shaded artifact fixes both directions of classpath interference. Note what it does NOT fix: JAXP factory poisoning through DocumentBuilderFactory.newInstance() (e.g. Oracle xmlparserv2, the root cause in the linked issue) is a JDK-global lookup that no amount of relocation can shield — verified experimentally. That requires the classloader isolation proposed in the linked issue (or pinning the JAXP factories via system properties).

Design notes

  • ServicesResourceTransformer renames and rewrites META-INF/services files to the relocated names (JAXB, SAAJ, JAX-WS, CXF bus factory all resolve within the shaded namespace).
  • META-INF/cxf/bus-extensions.txt lists class names as plain text, which relocation does not rewrite — the jar ships a pre-relocated merged copy of all cxf-* module entries (src/main/shade/bus-extensions.txt); originals are filtered out. (Fun fact: cxf-rt-ws-addr's copy has no trailing newline — naive concatenation silently corrupts the merge.)
  • javax.xml.stream.* service files are dropped — the shaded jar must not claim the JVM-wide StAX default inside host applications; CXF falls back to the JDK StAX implementation.
  • Kept external (documented in the README): org.slf4j:slf4j-api (host controls logging; the embedded, relocated jcl-over-slf4j backs the shaded HttpClient's commons-logging and routes to the host's slf4j) and org.bouncycastle:bcprov-jdk18on (signed JCE provider — repackaging would break it).
  • jakarta.servlet.* service files from jaxws-rt are excluded so the jar cannot register into host servlet containers.

Verification

  • mvn verify: 31/31 tests pass, prettier/checkstyle/pmd clean.
  • Isolated smoke test with only the shaded jar + slf4j + bcprov on the classpath: WinRMService.createInstance() (WSDL parse, JAX-WS catalog, CXF client build) succeeds, and executeWql() against a nonexistent host runs the full stack — JAXB serialization, SAAJ, NTLM encryption interceptors, async HTTP conduit — down to a clean UnknownHostException at the DNS layer. No unrelocated third-party classes remain in the jar.
  • Jar hygiene audited: every META-INF/services entry is either relocated or belongs to winrm-java.

Usage (for MetricsHub extensions)

See the new README section: depend on the shaded classifier with a wildcard exclusion, plus explicit slf4j-api and bcprov-jdk18on.

🤖 Generated with Claude Code

bertysentry and others added 2 commits July 22, 2026 13:20
Attach a second artifact (classifier "shaded") in which every dependency
is relocated under org.metricshub.winrm.shaded.* so that embedding
winrm-java cannot clash with, or leak ServiceLoader/JAXP registrations
into, the host application's classpath:

- All CXF/HttpClient/JAXB/SAAJ/smbj/etc. packages relocated; the
  ServicesResourceTransformer renames and rewrites META-INF/services
  entries to the relocated names.
- CXF discovers its features from META-INF/cxf/bus-extensions.txt whose
  class names are plain text (not rewritten by relocation), so the jar
  ships a pre-relocated merged copy of all cxf-* entries
  (src/main/shade/bus-extensions.txt) and the originals are filtered out.
- javax.xml.stream.* service files are dropped: the shaded jar must not
  claim the JVM-wide default StAX implementation in host applications
  (CXF falls back to the JDK StAX implementation).
- org.slf4j:slf4j-api stays external so the host controls logging;
  jcl-over-slf4j is embedded (relocated) to back the shaded HttpClient's
  commons-logging. org.bouncycastle stays external: it is a signed JCE
  provider and must not be repackaged.

Verified by running WinRMService.createInstance + executeWql with ONLY
the shaded jar, slf4j and bcprov on the classpath: the full stack (WSDL
parse, JAX-WS catalog, JAXB, SAAJ, NTLM encryption interceptors, async
HTTP conduit) works up to the network layer.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Exclude artifacts that are never used at runtime:
- com.sun.xml.ws (Metro/jaxws-rt) and its satellites (gmbal, ha-api,
  management-api, streambuffer, FastInfoset) plus the duplicate
  com.sun.xml.bind JAXB flavor: CXF is the JAX-WS implementation here
  and winrm-java only uses the jakarta.xml.ws API. saaj-impl and its
  actual dependencies (stax-ex, mimepull, angus-activation) are kept.
- jakarta.mail / angus-mail: only used for MTOM/attachments, which
  WinRM never uses.

Woodstox stays in (relocated): CXF requires it for secure StAX parsing
(refuses to start without it unless org.apache.cxf.stax.allowInsecureParser
is set) and instantiates it by class name, so dropping its
javax.xml.stream service files hides it from the host JVM without
breaking CXF.

Verified with the same isolated smoke test: full stack functional down
to the network layer; no unrelocated classes or unshaded service
registrations remain in the jar.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@bertysentry

Copy link
Copy Markdown
Contributor Author

Trimmed the shaded artifact 14.5 MB → 10.6 MB by excluding runtime-dead artifacts: Metro (jaxws-rt — a second, unused JAX-WS runtime; CXF is the implementation) with its satellites (gmbal, ha-api, management-api, streambuffer, FastInfoset, duplicate com.sun.xml.bind JAXB), and jakarta.mail/angus-mail (MTOM/attachments only — unused by WinRM).

Woodstox must stay: CXF refuses to create a secure XMLInputFactory without it (verified — dropping it fails with Cannot create a secure XMLInputFactory). It remains relocated and unregistered, so it stays invisible to host applications.

Deliberately NOT using minimizeJar: CXF loads most of its classes reflectively (bus-extensions.txt, service files), so static-reachability minimization would strip live code; protecting it with include filters would cover most of the jar anyway. What remains (CXF ~3.3 MB, Woodstox 1.8 MB, HttpClient stack 1.6 MB, JAXB 1.1 MB, smbj, SAAJ) is all genuinely exercised.

Same verification as before: isolated smoke test passes (full stack to the network layer), 31/31 tests, jar hygiene clean.

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