Skip to content

Commit 26d51f6

Browse files
arturobernalgok2c
authored andcommitted
Use reflection for Commons Compress codecs to avoid hard dep. (#737)
Code optimization and javadoc update
1 parent 5f3d006 commit 26d51f6

File tree

10 files changed

+270
-227
lines changed

10 files changed

+270
-227
lines changed

httpclient5/pom.xml

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,6 @@
7777
<artifactId>log4j-core</artifactId>
7878
<scope>test</scope>
7979
</dependency>
80-
<dependency>
81-
<groupId>org.brotli</groupId>
82-
<artifactId>dec</artifactId>
83-
<optional>true</optional>
84-
</dependency>
8580
<dependency>
8681
<groupId>com.kohlschutter.junixsocket</groupId>
8782
<artifactId>junixsocket-core</artifactId>
@@ -121,6 +116,7 @@
121116
<dependency>
122117
<groupId>com.aayushatharva.brotli4j</groupId>
123118
<artifactId>brotli4j</artifactId>
119+
<optional>true</optional>
124120
</dependency>
125121
</dependencies>
126122

httpclient5/src/main/java/org/apache/hc/client5/http/entity/BrotliInputStreamFactory.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,10 @@
2929
import java.io.IOException;
3030
import java.io.InputStream;
3131

32+
import com.aayushatharva.brotli4j.decoder.BrotliInputStream;
33+
3234
import org.apache.hc.core5.annotation.Contract;
3335
import org.apache.hc.core5.annotation.ThreadingBehavior;
34-
import org.brotli.dec.BrotliInputStream;
3536

3637
/**
3738
* {@link InputStreamFactory} for handling Brotli Content Coded responses.

httpclient5/src/test/java/org/apache/hc/client5/http/entity/TestBrotli.java renamed to httpclient5/src/main/java/org/apache/hc/client5/http/entity/compress/BrotliCodecFactory.java

Lines changed: 28 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -25,33 +25,40 @@
2525
*
2626
*/
2727

28-
package org.apache.hc.client5.http.entity;
28+
package org.apache.hc.client5.http.entity.compress;
2929

30-
import org.apache.hc.client5.http.entity.compress.ContentCodecRegistry;
31-
import org.apache.hc.client5.http.entity.compress.ContentCoding;
32-
import org.apache.hc.core5.http.HttpEntity;
33-
import org.apache.hc.core5.http.io.entity.ByteArrayEntity;
34-
import org.apache.hc.core5.http.io.entity.EntityUtils;
35-
import org.junit.jupiter.api.Assertions;
36-
import org.junit.jupiter.api.Test;
30+
import java.io.BufferedOutputStream;
31+
import java.io.InputStream;
32+
import java.io.OutputStream;
3733

38-
class TestBrotli {
34+
import com.aayushatharva.brotli4j.decoder.BrotliInputStream;
3935

40-
/**
41-
* Brotli decompression test implemented by request with specified response encoding br
42-
*
43-
* @throws Exception
44-
*/
45-
@Test
46-
void testDecompressionWithBrotli() throws Exception {
36+
import org.apache.hc.core5.annotation.Contract;
37+
import org.apache.hc.core5.annotation.Internal;
38+
import org.apache.hc.core5.annotation.ThreadingBehavior;
39+
import org.apache.hc.core5.io.IOFunction;
4740

48-
final byte[] bytes = new byte[] {33, 44, 0, 4, 116, 101, 115, 116, 32, 98, 114, 111, 116, 108, 105, 10, 3};
41+
/**
42+
* Brotli Codecs factory.
43+
*
44+
* @since 5.6
45+
*/
46+
@Internal
47+
@Contract(threading = ThreadingBehavior.STATELESS)
48+
final class BrotliCodecFactory {
4949

50-
final HttpEntity entity = ContentCodecRegistry.unwrap(
51-
ContentCoding.BROTLI,
52-
new ByteArrayEntity(bytes, null));
50+
/**
51+
* Creates a lazy decoder that instantiates the Commons Compress stream.
52+
*/
53+
static IOFunction<InputStream, InputStream> decoder() {
54+
return BrotliInputStream::new;
55+
}
5356

54-
Assertions.assertEquals("test brotli\n", EntityUtils.toString(entity));
57+
/**
58+
* Creates a lazy encoder that instantiates the Commons Compress stream.
59+
*/
60+
static IOFunction<OutputStream, OutputStream> encoder() {
61+
return BufferedOutputStream::new;
5562
}
5663

5764
}
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
/*
2+
* ====================================================================
3+
* Licensed to the Apache Software Foundation (ASF) under one
4+
* or more contributor license agreements. See the NOTICE file
5+
* distributed with this work for additional information
6+
* regarding copyright ownership. The ASF licenses this file
7+
* to you under the Apache License, Version 2.0 (the
8+
* "License"); you may not use this file except in compliance
9+
* with the License. You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing,
14+
* software distributed under the License is distributed on an
15+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16+
* KIND, either express or implied. See the License for the
17+
* specific language governing permissions and limitations
18+
* under the License.
19+
* ====================================================================
20+
*
21+
* This software consists of voluntary contributions made by many
22+
* individuals on behalf of the Apache Software Foundation. For more
23+
* information on the Apache Software Foundation, please see
24+
* <http://www.apache.org/>.
25+
*
26+
*/
27+
28+
package org.apache.hc.client5.http.entity.compress;
29+
30+
import java.io.IOException;
31+
import java.io.InputStream;
32+
import java.io.OutputStream;
33+
import java.util.Locale;
34+
import org.apache.commons.compress.compressors.CompressorException;
35+
import org.apache.commons.compress.compressors.CompressorStreamFactory;
36+
import org.apache.hc.core5.annotation.Contract;
37+
import org.apache.hc.core5.annotation.Internal;
38+
import org.apache.hc.core5.annotation.ThreadingBehavior;
39+
import org.apache.hc.core5.io.IOFunction;
40+
41+
/**
42+
* Codecs factory for Apache Commons Compress.
43+
* <p>
44+
* Use {@link #runtimeAvailable(ContentCoding)} to probe whether a given coding
45+
* can be provided by the current classpath configuration. Callers can then
46+
* register codecs conditionally without hard dependencies.
47+
* </p>
48+
*
49+
* @since 5.6
50+
*/
51+
@Internal
52+
@Contract(threading = ThreadingBehavior.STATELESS)
53+
final class CommonsCompressCodecFactory {
54+
55+
private static final String FACTORY_CLASS =
56+
"org.apache.commons.compress.compressors.CompressorStreamFactory";
57+
58+
// CC stream classes
59+
private static final String CC_BROTLI = "org.apache.commons.compress.compressors.brotli.BrotliCompressorInputStream";
60+
private static final String CC_ZSTD = "org.apache.commons.compress.compressors.zstandard.ZstdCompressorInputStream";
61+
private static final String CC_XZ = "org.apache.commons.compress.compressors.xz.XZCompressorInputStream";
62+
private static final String CC_LZMA = "org.apache.commons.compress.compressors.lzma.LZMACompressorInputStream";
63+
private static final String CC_LZ4_F = "org.apache.commons.compress.compressors.lz4.FramedLZ4CompressorInputStream";
64+
private static final String CC_LZ4_B = "org.apache.commons.compress.compressors.lz4.BlockLZ4CompressorInputStream";
65+
private static final String CC_BZIP2 = "org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream";
66+
private static final String CC_PACK200 = "org.apache.commons.compress.compressors.pack200.Pack200CompressorInputStream";
67+
private static final String CC_DEFLATE64 = "org.apache.commons.compress.compressors.deflate64.Deflate64CompressorInputStream";
68+
69+
// Helper libs
70+
private static final String H_BROTLI = "org.brotli.dec.BrotliInputStream";
71+
private static final String H_ZSTD = "com.github.luben.zstd.ZstdInputStream";
72+
private static final String H_XZ = "org.tukaani.xz.XZInputStream";
73+
74+
private CommonsCompressCodecFactory() {
75+
}
76+
77+
private static boolean isPresent(final String className) {
78+
try {
79+
Class.forName(className, false, CommonsCompressCodecFactory.class.getClassLoader());
80+
return true;
81+
} catch (final ClassNotFoundException | LinkageError ex) {
82+
return false;
83+
}
84+
}
85+
86+
/**
87+
* Creates a lazy decoder that instantiates the Commons Compress stream.
88+
*/
89+
static IOFunction<InputStream, InputStream> decoder(final String token) {
90+
final String enc = token.toLowerCase(Locale.ROOT);
91+
final CompressorStreamFactory factory = new CompressorStreamFactory();
92+
return in -> {
93+
try {
94+
return factory.createCompressorInputStream(enc, in);
95+
} catch (final CompressorException | LinkageError ex) {
96+
throw new IOException("Unable to decode Content-Encoding '" + enc + '\'', ex);
97+
}
98+
};
99+
}
100+
101+
/**
102+
* Creates a lazy encoder that instantiates the Commons Compress stream.
103+
*/
104+
static IOFunction<OutputStream, OutputStream> encoder(final String token) {
105+
final String enc = token.toLowerCase(Locale.ROOT);
106+
final CompressorStreamFactory factory = new CompressorStreamFactory();
107+
return in -> {
108+
try {
109+
return factory.createCompressorOutputStream(enc, in);
110+
} catch (final CompressorException | LinkageError ex) {
111+
throw new IOException("Unable to decode Content-Encoding '" + enc + '\'', ex);
112+
}
113+
};
114+
}
115+
116+
/**
117+
* Best-effort availability probe for optional Commons-Compress codecs.
118+
* <p>
119+
* Returns {@code true} only if the CC factory and the codec-specific
120+
* implementation (and helper classes if required) are present on the
121+
* classpath. Built-in gzip/deflate are handled elsewhere and are not
122+
* probed here.
123+
* </p>
124+
*/
125+
static boolean runtimeAvailable(final ContentCoding coding) {
126+
if (coding == null) {
127+
return false;
128+
}
129+
if (!isPresent(FACTORY_CLASS)) {
130+
return false;
131+
}
132+
switch (coding) {
133+
case BROTLI:
134+
return isPresent(CC_BROTLI) && isPresent(H_BROTLI);
135+
case ZSTD:
136+
return isPresent(CC_ZSTD) && isPresent(H_ZSTD);
137+
case XZ:
138+
return isPresent(CC_XZ) && isPresent(H_XZ);
139+
case LZMA:
140+
return isPresent(CC_LZMA) && isPresent(H_XZ);
141+
case LZ4_FRAMED:
142+
return isPresent(CC_LZ4_F);
143+
case LZ4_BLOCK:
144+
return isPresent(CC_LZ4_B);
145+
case BZIP2:
146+
return isPresent(CC_BZIP2);
147+
case PACK200:
148+
return isPresent(CC_PACK200) || isPresent("java.util.jar.Pack200");
149+
case DEFLATE64:
150+
return isPresent(CC_DEFLATE64);
151+
default:
152+
return false;
153+
}
154+
}
155+
}

httpclient5/src/main/java/org/apache/hc/client5/http/entity/compress/CommonsCompressDecoderFactory.java

Lines changed: 0 additions & 111 deletions
This file was deleted.

0 commit comments

Comments
 (0)