From 8e05f72c42f1c6b03ea65402bf08f0dee2280efc Mon Sep 17 00:00:00 2001 From: Arturo Bernal Date: Sun, 12 Oct 2025 10:59:35 +0200 Subject: [PATCH] Use reflection for Commons Compress codecs to avoid hard dep. Prevent EIIE when commons-compress is absent; wrap failures as IOException. Fix runtimeAvailable() to probe CC classes; decouple from helper JARs. --- httpclient5/pom.xml | 6 +- .../http/entity/BrotliInputStreamFactory.java | 3 +- .../compress/CommonsCompressCodecFactory.java | 177 ++++++++++++++++++ .../CommonsCompressDecoderFactory.java | 111 ----------- .../compress/CommonsCompressSupport.java | 16 +- ...singEntity.java => CompressingEntity.java} | 59 +++--- .../entity/compress/ContentCodecRegistry.java | 94 ++++++---- pom.xml | 5 - 8 files changed, 284 insertions(+), 187 deletions(-) create mode 100644 httpclient5/src/main/java/org/apache/hc/client5/http/entity/compress/CommonsCompressCodecFactory.java delete mode 100644 httpclient5/src/main/java/org/apache/hc/client5/http/entity/compress/CommonsCompressDecoderFactory.java rename httpclient5/src/main/java/org/apache/hc/client5/http/entity/compress/{CommonsCompressingEntity.java => CompressingEntity.java} (56%) diff --git a/httpclient5/pom.xml b/httpclient5/pom.xml index 4478777448..4003210b9d 100644 --- a/httpclient5/pom.xml +++ b/httpclient5/pom.xml @@ -77,11 +77,6 @@ log4j-core test - - org.brotli - dec - true - com.kohlschutter.junixsocket junixsocket-core @@ -121,6 +116,7 @@ com.aayushatharva.brotli4j brotli4j + true diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/entity/BrotliInputStreamFactory.java b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/BrotliInputStreamFactory.java index 369f914fc5..2b06d30d90 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/entity/BrotliInputStreamFactory.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/BrotliInputStreamFactory.java @@ -29,9 +29,10 @@ import java.io.IOException; import java.io.InputStream; +import com.aayushatharva.brotli4j.decoder.BrotliInputStream; + import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; -import org.brotli.dec.BrotliInputStream; /** * {@link InputStreamFactory} for handling Brotli Content Coded responses. diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/entity/compress/CommonsCompressCodecFactory.java b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/compress/CommonsCompressCodecFactory.java new file mode 100644 index 0000000000..6d47f0a2f6 --- /dev/null +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/compress/CommonsCompressCodecFactory.java @@ -0,0 +1,177 @@ +/* + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.hc.client5.http.entity.compress; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.Method; +import java.util.Locale; + +import org.apache.hc.core5.annotation.Contract; +import org.apache.hc.core5.annotation.Internal; +import org.apache.hc.core5.annotation.ThreadingBehavior; +import org.apache.hc.core5.io.IOFunction; + +/** + * Reflection-based codecs factory for Apache Commons Compress. + *

+ * This class never links Commons Compress at compile time. At runtime, it + * reflectively locates {@code CompressorStreamFactory} and creates encoder / + * decoder streams for IANA tokens (e.g. {@code "zstd"}, {@code "xz"}) only + * when the relevant classes (and any helper JARs) are present. + *

+ *

+ * Use {@link #runtimeAvailable(ContentCoding)} to probe whether a given coding + * can be provided by the current classpath configuration. Callers can then + * register codecs conditionally without hard dependencies. + *

+ * + * @since 5.6 + */ +@Internal +@Contract(threading = ThreadingBehavior.STATELESS) +final class CommonsCompressCodecFactory { + + private static final String FACTORY_CLASS = + "org.apache.commons.compress.compressors.CompressorStreamFactory"; + + // CC stream classes + private static final String CC_BROTLI = "org.apache.commons.compress.compressors.brotli.BrotliCompressorInputStream"; + private static final String CC_ZSTD = "org.apache.commons.compress.compressors.zstandard.ZstdCompressorInputStream"; + private static final String CC_XZ = "org.apache.commons.compress.compressors.xz.XZCompressorInputStream"; + private static final String CC_LZMA = "org.apache.commons.compress.compressors.lzma.LZMACompressorInputStream"; + private static final String CC_LZ4_F = "org.apache.commons.compress.compressors.lz4.FramedLZ4CompressorInputStream"; + private static final String CC_LZ4_B = "org.apache.commons.compress.compressors.lz4.BlockLZ4CompressorInputStream"; + private static final String CC_BZIP2 = "org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream"; + private static final String CC_PACK200 = "org.apache.commons.compress.compressors.pack200.Pack200CompressorInputStream"; + private static final String CC_DEFLATE64 = "org.apache.commons.compress.compressors.deflate64.Deflate64CompressorInputStream"; + + // Helper libs + private static final String H_BROTLI = "org.brotli.dec.BrotliInputStream"; + private static final String H_ZSTD = "com.github.luben.zstd.ZstdInputStream"; + private static final String H_XZ = "org.tukaani.xz.XZInputStream"; + + private CommonsCompressCodecFactory() { + } + + private static boolean isPresent(final String className) { + try { + Class.forName(className, false, CommonsCompressCodecFactory.class.getClassLoader()); + return true; + } catch (final ClassNotFoundException | LinkageError ex) { + return false; + } + } + + /** + * Creates a lazy decoder that instantiates the Commons Compress stream + * reflectively on first use. Throws {@link IOException} if Commons Compress + * is not available or the codec cannot be created. + */ + static IOFunction decoder(final String token) { + final String enc = token.toLowerCase(Locale.ROOT); + return in -> { + try { + final ClassLoader cl = CommonsCompressCodecFactory.class.getClassLoader(); + final Class factoryCls = Class.forName(FACTORY_CLASS, false, cl); + final Object factory = factoryCls.getConstructor().newInstance(); + final Method m = factoryCls.getMethod("createCompressorInputStream", String.class, InputStream.class); + final Object stream = m.invoke(factory, enc, in); + return (InputStream) stream; + } catch (final ClassNotFoundException e) { + throw new IOException("Apache Commons Compress is not on the classpath", e); + } catch (final ReflectiveOperationException | IllegalArgumentException | LinkageError e) { + throw new IOException("Unable to decode Content-Encoding '" + enc + '\'', e); + } + }; + } + + /** + * Creates a lazy encoder that instantiates the Commons Compress stream + * reflectively on first use. Throws {@link IOException} if Commons Compress + * is not available or the codec cannot be created. + */ + static IOFunction encoder(final String token) { + final String enc = token.toLowerCase(Locale.ROOT); + return out -> { + try { + final ClassLoader cl = CommonsCompressCodecFactory.class.getClassLoader(); + final Class factoryCls = Class.forName(FACTORY_CLASS, false, cl); + final Object factory = factoryCls.getConstructor().newInstance(); + final Method m = factoryCls.getMethod("createCompressorOutputStream", String.class, OutputStream.class); + final Object cos = m.invoke(factory, enc, out); + return (OutputStream) cos; + } catch (final ClassNotFoundException e) { + throw new IOException("Apache Commons Compress is not on the classpath", e); + } catch (final ReflectiveOperationException | IllegalArgumentException | LinkageError e) { + throw new IOException("Unable to encode using Content-Encoding '" + enc + '\'', e); + } + }; + } + + /** + * Best-effort availability probe for optional Commons-Compress codecs. + *

+ * Returns {@code true} only if the CC factory and the codec-specific + * implementation (and helper classes if required) are present on the + * classpath. Built-in gzip/deflate are handled elsewhere and are not + * probed here. + *

+ */ + static boolean runtimeAvailable(final ContentCoding coding) { + if (coding == null) { + return false; + } + if (!isPresent(FACTORY_CLASS)) { + return false; + } + switch (coding) { + case BROTLI: + return isPresent(CC_BROTLI) && isPresent(H_BROTLI); + case ZSTD: + return isPresent(CC_ZSTD) && isPresent(H_ZSTD); + case XZ: + return isPresent(CC_XZ) && isPresent(H_XZ); + case LZMA: + return isPresent(CC_LZMA) && isPresent(H_XZ); + case LZ4_FRAMED: + return isPresent(CC_LZ4_F); + case LZ4_BLOCK: + return isPresent(CC_LZ4_B); + case BZIP2: + return isPresent(CC_BZIP2); + case PACK200: + return isPresent(CC_PACK200) || isPresent("java.util.jar.Pack200"); + case DEFLATE64: + return isPresent(CC_DEFLATE64); + default: + return false; + } + } +} diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/entity/compress/CommonsCompressDecoderFactory.java b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/compress/CommonsCompressDecoderFactory.java deleted file mode 100644 index 9bc7d77708..0000000000 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/entity/compress/CommonsCompressDecoderFactory.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * ==================================================================== - * 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. - * ==================================================================== - * - * This software consists of voluntary contributions made by many - * individuals on behalf of the Apache Software Foundation. For more - * information on the Apache Software Foundation, please see - * . - * - */ - -package org.apache.hc.client5.http.entity.compress; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Collections; -import java.util.EnumMap; -import java.util.Locale; -import java.util.Map; - -import org.apache.commons.compress.compressors.CompressorException; -import org.apache.commons.compress.compressors.CompressorStreamFactory; -import org.apache.hc.core5.annotation.Contract; -import org.apache.hc.core5.annotation.Internal; -import org.apache.hc.core5.annotation.ThreadingBehavior; -import org.apache.hc.core5.io.IOFunction; - -/** - * A factory for creating InputStream instances, utilizing Apache Commons Compress. - * This class is compiled with Commons Compress as an optional dependency, loading - * only when the library is present at runtime, avoiding mandatory inclusion in - * downstream builds. - *

- *

- * Some encodings require native helper JARs; runtime availability is checked - * using a lightweight Class.forName probe to register codecs only when helpers - * are present. - * - * @since 5.6 - */ -@Internal -@Contract(threading = ThreadingBehavior.STATELESS) -final class CommonsCompressDecoderFactory { - - /** - * Map of codings that need extra JARs → the fully‐qualified class we test for - */ - private static final Map REQUIRED_CLASS_NAME; - - static { - final Map m = new EnumMap<>(ContentCoding.class); - m.put(ContentCoding.BROTLI, "org.brotli.dec.BrotliInputStream"); - m.put(ContentCoding.ZSTD, "com.github.luben.zstd.ZstdInputStream"); - m.put(ContentCoding.XZ, "org.tukaani.xz.XZInputStream"); - m.put(ContentCoding.LZMA, "org.tukaani.xz.XZInputStream"); - REQUIRED_CLASS_NAME = Collections.unmodifiableMap(m); - } - - /** - * @return lazy decoder for the given IANA token (lower-case). - */ - static IOFunction decoder(final String token) { - final String enc = token.toLowerCase(Locale.ROOT); - final CompressorStreamFactory factory = new CompressorStreamFactory(); - return in -> { - try { - return factory.createCompressorInputStream(enc, in); - } catch (final CompressorException | LinkageError ex) { - throw new IOException("Unable to decode Content-Encoding '" + enc + '\'', ex); - } - }; - } - - /** - * Tests that required helper classes are present for a coding token. - */ - static boolean runtimeAvailable(final String token) { - final ContentCoding coding = ContentCoding.fromToken(token); - if (coding == null) { - return true; - } - final String helper = REQUIRED_CLASS_NAME.get(coding); - if (helper == null) { - // no extra JAR needed - return true; - } - try { - Class.forName(helper, false, - CommonsCompressDecoderFactory.class.getClassLoader()); - return true; - } catch (final ClassNotFoundException | LinkageError ex) { - return false; - } - } -} \ No newline at end of file diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/entity/compress/CommonsCompressSupport.java b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/compress/CommonsCompressSupport.java index 986f5c5683..2053bd59d4 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/entity/compress/CommonsCompressSupport.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/compress/CommonsCompressSupport.java @@ -33,9 +33,12 @@ import org.apache.hc.core5.annotation.ThreadingBehavior; /** - * Utility that answers the question “Is Apache Commons Compress - * on the class-path and in a usable state?” Both the encoder and - * decoder registries rely on this information. + * Lightweight guard that checks whether the Commons Compress factory + * class is loadable with the current class loader. + *

+ * Used by the codec registry to decide if reflective wiring of optional + * codecs should even be attempted. + *

* * @since 5.6 */ @@ -46,8 +49,11 @@ final class CommonsCompressSupport { private static final String CCSF = "org.apache.commons.compress.compressors.CompressorStreamFactory"; - /** Non-instantiable. */ - private CommonsCompressSupport() { } + /** + * Non-instantiable. + */ + private CommonsCompressSupport() { + } /** * Returns {@code true} if the core Commons Compress class can be loaded diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/entity/compress/CommonsCompressingEntity.java b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/compress/CompressingEntity.java similarity index 56% rename from httpclient5/src/main/java/org/apache/hc/client5/http/entity/compress/CommonsCompressingEntity.java rename to httpclient5/src/main/java/org/apache/hc/client5/http/entity/compress/CompressingEntity.java index 7580b1a34b..0b4ce098bd 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/entity/compress/CommonsCompressingEntity.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/compress/CompressingEntity.java @@ -30,36 +30,39 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.util.Locale; -import org.apache.commons.compress.compressors.CompressorException; -import org.apache.commons.compress.compressors.CompressorStreamFactory; -import org.apache.hc.core5.annotation.Contract; -import org.apache.hc.core5.annotation.Internal; -import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.io.entity.HttpEntityWrapper; +import org.apache.hc.core5.io.IOFunction; import org.apache.hc.core5.util.Args; + /** - * Compresses the wrapped entity on-the-fly using Apache Commons Compress. - * - *

The codec is chosen by its IANA token (for example {@code "br"} or - * {@code "zstd"}). The helper JAR must be present at run-time; otherwise - * {@link #writeTo(OutputStream)} will throw {@link IOException}.

+ * Streaming wrapper that compresses the enclosed {@link HttpEntity} on write. + *

+ * The actual compressor is supplied as an {@link IOFunction}<OutputStream,OutputStream> + * and is resolved by the caller (for example via reflective factories). This keeps + * compression back-ends fully optional and avoids hard classpath dependencies. + *

+ *

+ * The entity reports the configured {@code Content-Encoding} token and streams + * the content; length is unknown ({@code -1}), and the entity is chunked. + *

* * @since 5.6 */ -@Internal -@Contract(threading = ThreadingBehavior.STATELESS) -public final class CommonsCompressingEntity extends HttpEntityWrapper { +public final class CompressingEntity extends HttpEntityWrapper { - private final String coding; // lower-case - private final CompressorStreamFactory factory = new CompressorStreamFactory(); + private final IOFunction encoder; + private final String coding; // lower-case token for header reporting - CommonsCompressingEntity(final HttpEntity src, final String coding) { + public CompressingEntity( + final HttpEntity src, + final String coding, + final IOFunction encoder) { super(src); - this.coding = coding.toLowerCase(Locale.ROOT); + this.encoder = Args.notNull(encoder, "Stream encoder"); + this.coding = Args.notNull(coding, "Content coding").toLowerCase(java.util.Locale.ROOT); } @Override @@ -69,8 +72,8 @@ public String getContentEncoding() { @Override public long getContentLength() { - return -1; - } // streaming + return -1; // streaming + } @Override public boolean isChunked() { @@ -78,17 +81,23 @@ public boolean isChunked() { } @Override - public InputStream getContent() { // Pull-mode is not supported + public InputStream getContent() { throw new UnsupportedOperationException("Compressed entity is write-only"); } @Override public void writeTo(final OutputStream out) throws IOException { Args.notNull(out, "Output stream"); - try (OutputStream cos = factory.createCompressorOutputStream(coding, out)) { - super.writeTo(cos); - } catch (final CompressorException | LinkageError ex) { - throw new IOException("Unable to compress using coding '" + coding + '\'', ex); + final OutputStream wrapped = encoder.apply(out); + try { + super.writeTo(wrapped); + } finally { + // Close the wrapped stream to flush trailers/footers if any. + try { + wrapped.close(); + } catch (final IOException ignore) { + // best effort + } } } } \ No newline at end of file diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/entity/compress/ContentCodecRegistry.java b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/compress/ContentCodecRegistry.java index 3d7ad9eaaa..76bceecbf5 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/entity/compress/ContentCodecRegistry.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/compress/ContentCodecRegistry.java @@ -27,6 +27,9 @@ package org.apache.hc.client5.http.entity.compress; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Constructor; import java.util.Arrays; import java.util.Collections; import java.util.EnumMap; @@ -34,20 +37,30 @@ import java.util.function.UnaryOperator; import java.util.zip.GZIPInputStream; +import org.apache.hc.client5.http.entity.DeflateCompressingEntity; import org.apache.hc.client5.http.entity.DeflateInputStream; +import org.apache.hc.client5.http.entity.GzipCompressingEntity; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.HttpEntity; -import org.brotli.dec.BrotliInputStream; +import org.apache.hc.core5.io.IOFunction; /** - * Run-time catalogue of built-in and Commons-Compress - * {@linkplain java.util.function.UnaryOperator encoders} / {@linkplain java.util.function.UnaryOperator decoders}. - * - *

Entries are wired once at class-load time and published through an - * unmodifiable map, so lookups are lock-free and thread-safe.

+ * Registry of encode/decode transformations for HTTP content codings. + *

+ * Entries are wired once at class-load time and exposed via an unmodifiable map. + * Built-in gzip/deflate are always available. Additional codecs are discovered + * reflectively: + *

+ *
    + *
  • Commons Compress codecs, when the library and (if required) its helper JARs + * are present.
  • + *
  • Decode-only Brotli via brotli4j, when present on the classpath. This does + * not affect the advertised {@code Accept-Encoding} unless an encoder is also + * registered.
  • + *
* * @since 5.6 */ @@ -61,16 +74,10 @@ private static Map build() { final Map m = new EnumMap<>(ContentCoding.class); m.put(ContentCoding.GZIP, - new Codec( - // encoder - org.apache.hc.client5.http.entity.GzipCompressingEntity::new, - ent -> new DecompressingEntity(ent, GZIPInputStream::new))); - m.put(ContentCoding.DEFLATE, - new Codec( - org.apache.hc.client5.http.entity.DeflateCompressingEntity::new, - ent -> new DecompressingEntity(ent, DeflateInputStream::new))); - - /* 2. Commons-Compress extras ---------------------------------- */ + new Codec(GzipCompressingEntity::new, ent -> new DecompressingEntity(ent, GZIPInputStream::new))); + m.put(ContentCoding.DEFLATE, new Codec(DeflateCompressingEntity::new, ent -> new DecompressingEntity(ent, DeflateInputStream::new))); + + // 2) Commons-Compress (optional) — reflectively wired if (CommonsCompressSupport.isPresent()) { for (final ContentCoding c : Arrays.asList( ContentCoding.BROTLI, @@ -83,21 +90,21 @@ private static Map build() { ContentCoding.PACK200, ContentCoding.DEFLATE64)) { - if (CommonsCompressDecoderFactory.runtimeAvailable(c.token())) { + if (CommonsCompressCodecFactory.runtimeAvailable(c)) { m.put(c, new Codec( - e -> new CommonsCompressingEntity(e, c.token()), + e -> new CompressingEntity(e, c.token(), + CommonsCompressCodecFactory.encoder(c.token())), ent -> new DecompressingEntity(ent, - CommonsCompressDecoderFactory.decoder(c.token())))); + CommonsCompressCodecFactory.decoder(c.token())))); } } - } - /* 3. Native Brotli fallback (decode-only) ---------------------- */ - if (!m.containsKey(ContentCoding.BROTLI) - && CommonsCompressDecoderFactory.runtimeAvailable(ContentCoding.BROTLI.token())) { - m.put(ContentCoding.BROTLI, - Codec.decodeOnly(ent -> - new DecompressingEntity(ent, BrotliInputStream::new))); + } + // 3) Native Brotli fallback (decode-only), no compile-time dep + if (isPresent("com.aayushatharva.brotli4j.decoder.BrotliInputStream") + && isPresent("com.aayushatharva.brotli4j.Brotli4jLoader")) { + m.put(ContentCoding.BROTLI, Codec.decodeOnly(ent -> + new DecompressingEntity(ent, brotli4jDecoder()))); } return Collections.unmodifiableMap(m); @@ -113,20 +120,11 @@ public static HttpEntity unwrap(final ContentCoding c, final HttpEntity src) { return k != null && k.decoder != null ? k.decoder.apply(src) : null; } - private ContentCodecRegistry() { - } - - /** - * Returns the {@link java.util.function.UnaryOperator}<HttpEntity> for the given coding, or {@code null}. - */ public static UnaryOperator decoder(final ContentCoding coding) { final Codec c = REGISTRY.get(coding); return c != null ? c.decoder : null; } - /** - * Returns the {@link java.util.function.UnaryOperator}<HttpEntity> for the given coding, or {@code null}. - */ public static UnaryOperator encoder(final ContentCoding coding) { final Codec c = REGISTRY.get(coding); return c != null ? c.encoder : null; @@ -150,4 +148,30 @@ static Codec decodeOnly(final UnaryOperator d) { } } + private static boolean isPresent(final String className) { + try { + Class.forName(className, false, ContentCodecRegistry.class.getClassLoader()); + return true; + } catch (final ClassNotFoundException | LinkageError ex) { + return false; + } + } + + private static IOFunction brotli4jDecoder() { + return in -> { + try { + final ClassLoader cl = ContentCodecRegistry.class.getClassLoader(); + final Class loader = Class.forName("com.aayushatharva.brotli4j.Brotli4jLoader", false, cl); + loader.getMethod("ensureAvailability").invoke(null); + final Class cls = Class.forName("com.aayushatharva.brotli4j.decoder.BrotliInputStream", false, cl); + final Constructor ctor = cls.getConstructor(InputStream.class); + return (InputStream) ctor.newInstance(in); + } catch (final ReflectiveOperationException | LinkageError e) { + throw new IOException("Unable to decode brotli (brotli4j)", e); + } + }; + } + + private ContentCodecRegistry() { + } } \ No newline at end of file diff --git a/pom.xml b/pom.xml index 15f0627fe8..4e050498f0 100644 --- a/pom.xml +++ b/pom.xml @@ -145,11 +145,6 @@ log4j-core ${log4j.version}
- - org.brotli - dec - ${brotli.version} - org.conscrypt conscrypt-openjdk-uber