Skip to content

Commit b7bfff3

Browse files
ellisjoeiamdanfox
authored andcommitted
[improvement] FeatureFlag to make Response the return type for binary Jersey Responses (#37)
<!-- PR title should start with '[fix]', '[improvement]' or '[break]' if this PR would cause a patch, minor or major SemVer bump. Omit the prefix if this PR doesn't warrant a standalone release. --> ## Before this PR Currently binary response types are rendered as `StreamingOutput`. There's a bug in Jersey which causes a failure part way through streaming back that response to appear as a successful response to the client: eclipse-ee4j/jersey#3850 ## After this PR By rendering `Response` as the return type we are still able to use `StreamingOutput` as the body of the response, but we are also able to include the `Content-Length` header when we know the size of the response. Including the content length allows clients to determine there was an error, even when the stream appeared to have been closed successfully.
1 parent cb12eb5 commit b7bfff3

8 files changed

Lines changed: 84 additions & 7 deletions

File tree

conjure-java-core/src/main/java/com/palantir/conjure/java/FeatureFlags.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616

1717
package com.palantir.conjure.java;
1818

19+
import com.palantir.conjure.java.services.JerseyServiceGenerator;
1920
import com.palantir.conjure.java.services.Retrofit2ServiceGenerator;
21+
import javax.ws.rs.core.Response;
2022

2123
public enum FeatureFlags {
2224
/**
@@ -25,4 +27,9 @@ public enum FeatureFlags {
2527
*/
2628
RetrofitCompletableFutures,
2729

30+
/**
31+
* Instructs the {@link JerseyServiceGenerator} to generate binary response endpoints with the {@link Response}
32+
* type.
33+
*/
34+
JerseyBinaryAsResponse,
2835
}

conjure-java-core/src/main/java/com/palantir/conjure/java/services/JerseyServiceGenerator.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@
1919
import static com.google.common.base.Preconditions.checkArgument;
2020

2121
import com.google.common.collect.ImmutableList;
22+
import com.google.common.collect.ImmutableSet;
2223
import com.google.common.collect.Lists;
2324
import com.palantir.conjure.java.ConjureAnnotations;
25+
import com.palantir.conjure.java.FeatureFlags;
2426
import com.palantir.conjure.java.types.JerseyMethodTypeClassNameVisitor;
2527
import com.palantir.conjure.java.types.JerseyReturnTypeClassNameVisitor;
2628
import com.palantir.conjure.java.types.TypeMapper;
@@ -65,13 +67,21 @@
6567
public final class JerseyServiceGenerator implements ServiceGenerator {
6668

6769

68-
public JerseyServiceGenerator() {}
70+
private final Set<FeatureFlags> experimentalFeatures;
71+
72+
public JerseyServiceGenerator() {
73+
this(ImmutableSet.of());
74+
}
75+
76+
public JerseyServiceGenerator(Set<FeatureFlags> experimentalFeatures) {
77+
this.experimentalFeatures = ImmutableSet.copyOf(experimentalFeatures);
78+
}
6979

7080
@Override
7181
public Set<JavaFile> generate(ConjureDefinition conjureDefinition) {
7282
TypeMapper returnTypeMapper = new TypeMapper(
7383
conjureDefinition.getTypes(),
74-
types -> new JerseyReturnTypeClassNameVisitor(types));
84+
types -> new JerseyReturnTypeClassNameVisitor(types, experimentalFeatures));
7585
TypeMapper methodTypeMapper = new TypeMapper(
7686
conjureDefinition.getTypes(), JerseyMethodTypeClassNameVisitor::new);
7787
return conjureDefinition.getServices().stream()

conjure-java-core/src/main/java/com/palantir/conjure/java/types/JerseyReturnTypeClassNameVisitor.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.palantir.conjure.java.types;
1818

19+
import com.palantir.conjure.java.FeatureFlags;
1920
import com.palantir.conjure.spec.ExternalReference;
2021
import com.palantir.conjure.spec.ListType;
2122
import com.palantir.conjure.spec.MapType;
@@ -26,13 +27,18 @@
2627
import com.squareup.javapoet.ClassName;
2728
import com.squareup.javapoet.TypeName;
2829
import java.util.List;
30+
import java.util.Set;
31+
import javax.ws.rs.core.Response;
32+
import javax.ws.rs.core.StreamingOutput;
2933

3034
public final class JerseyReturnTypeClassNameVisitor implements ClassNameVisitor {
3135

3236
private final DefaultClassNameVisitor delegate;
37+
private final boolean binaryAsResponse;
3338

34-
public JerseyReturnTypeClassNameVisitor(List<TypeDefinition> types) {
39+
public JerseyReturnTypeClassNameVisitor(List<TypeDefinition> types, Set<FeatureFlags> featureFlags) {
3540
this.delegate = new DefaultClassNameVisitor(types);
41+
this.binaryAsResponse = featureFlags.contains(FeatureFlags.JerseyBinaryAsResponse);
3642
}
3743

3844
@Override
@@ -53,7 +59,11 @@ public TypeName visitOptional(OptionalType type) {
5359
@Override
5460
public TypeName visitPrimitive(PrimitiveType type) {
5561
if (type.get() == PrimitiveType.Value.BINARY) {
56-
return ClassName.get(javax.ws.rs.core.StreamingOutput.class);
62+
if (binaryAsResponse) {
63+
return ClassName.get(Response.class);
64+
} else {
65+
return ClassName.get(StreamingOutput.class);
66+
}
5767
}
5868
return delegate.visitPrimitive(type);
5969
}

conjure-java-core/src/test/java/com/palantir/conjure/java/JerseyServiceGeneratorTests.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import static org.assertj.core.api.Assertions.assertThat;
2020

2121
import com.google.common.collect.ImmutableList;
22+
import com.google.common.collect.ImmutableSet;
2223
import com.palantir.conjure.defs.Conjure;
2324
import com.palantir.conjure.java.services.JerseyServiceGenerator;
2425
import com.palantir.conjure.spec.ConjureDefinition;
@@ -87,6 +88,27 @@ public void testBinaryReturnInputStream() throws IOException {
8788
}
8889
}
8990

91+
@Test
92+
public void testBinaryReturnResponse() throws IOException {
93+
ConjureDefinition def = Conjure.parse(
94+
ImmutableList.of(new File("src/test/resources/example-binary.yml")));
95+
List<Path> files = new JerseyServiceGenerator(ImmutableSet.of(FeatureFlags.JerseyBinaryAsResponse))
96+
.emit(def, folder.getRoot());
97+
98+
for (Path file : files) {
99+
if (Boolean.valueOf(System.getProperty("recreate", "false"))) {
100+
Path output = Paths.get(
101+
"src/test/resources/test/api/" + file.getFileName() + ".jersey.binary_as_response");
102+
Files.delete(output);
103+
Files.copy(file, output);
104+
}
105+
106+
assertThat(TestUtils.readFromFile(file)).isEqualTo(
107+
TestUtils.readFromFile(Paths.get(
108+
"src/test/resources/test/api/" + file.getFileName() + ".jersey.binary_as_response")));
109+
}
110+
}
111+
90112
private void testServiceGeneration(String conjureFile) throws IOException {
91113
ConjureDefinition def = Conjure.parse(
92114
ImmutableList.of(new File("src/test/resources/" + conjureFile + ".yml")));
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package test.api;
2+
3+
import javax.annotation.Generated;
4+
import javax.ws.rs.Consumes;
5+
import javax.ws.rs.GET;
6+
import javax.ws.rs.Path;
7+
import javax.ws.rs.Produces;
8+
import javax.ws.rs.core.MediaType;
9+
import javax.ws.rs.core.Response;
10+
11+
@Consumes(MediaType.APPLICATION_JSON)
12+
@Produces(MediaType.APPLICATION_JSON)
13+
@Path("/")
14+
@Generated("com.palantir.conjure.java.services.JerseyServiceGenerator")
15+
public interface TestService {
16+
@GET
17+
@Produces(MediaType.APPLICATION_OCTET_STREAM)
18+
Response getBinary();
19+
}

conjure-java/src/main/java/com/palantir/conjure/java/cli/CliConfiguration.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ public abstract class CliConfiguration {
3030
public static final String JERSEY_OPTION = "jersey";
3131
public static final String RETROFIT_OPTION = "retrofit";
3232
public static final String RETROFIT_COMPLETABLE_FUTURES = "retrofitCompletableFutures";
33+
public static final String JERSEY_BINARY_AS_RESPONSE = "jerseyBinaryAsResponse";
3334

3435
abstract File target();
3536

@@ -91,6 +92,9 @@ static CliConfiguration of(String target, String outputDirectory, Option[] optio
9192
case RETROFIT_COMPLETABLE_FUTURES:
9293
flagsBuilder.add(FeatureFlags.RetrofitCompletableFutures);
9394
break;
95+
case JERSEY_BINARY_AS_RESPONSE:
96+
flagsBuilder.add(FeatureFlags.JerseyBinaryAsResponse);
97+
break;
9498
default:
9599
break;
96100
}

conjure-java/src/main/java/com/palantir/conjure/java/cli/ConjureJavaCli.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ static CliConfiguration parseCliConfiguration(String[] args) {
5757
"Generate retrofit interfaces for streaming/async clients"));
5858
options.addOption(new Option(CliConfiguration.RETROFIT_COMPLETABLE_FUTURES,
5959
"Generate retrofit services which return Java8 CompletableFuture instead of OkHttp Call"));
60+
options.addOption(new Option(CliConfiguration.JERSEY_BINARY_AS_RESPONSE,
61+
"Generate jersey interfaces which return Response instead of StreamingOutput"));
6062

6163
try {
6264
CommandLine cmd = parser.parse(options, args, false);
@@ -75,7 +77,7 @@ static void generate(CliConfiguration config) {
7577
try {
7678
ConjureDefinition conjureDefinition = OBJECT_MAPPER.readValue(config.target(), ConjureDefinition.class);
7779
TypeGenerator typeGenerator = new ObjectGenerator();
78-
ServiceGenerator jerseyGenerator = new JerseyServiceGenerator();
80+
ServiceGenerator jerseyGenerator = new JerseyServiceGenerator(config.featureFlags());
7981
ServiceGenerator retrofitGenerator = new Retrofit2ServiceGenerator(config.featureFlags());
8082

8183
if (config.generateObjects()) {

conjure-java/src/test/java/com/palantir/conjure/java/cli/ConjureJavaCliTest.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,16 @@ public void parseFeatureFlags() {
6464
targetFile.getAbsolutePath(),
6565
folder.getRoot().getAbsolutePath(),
6666
"--objects",
67-
"--retrofitCompletableFutures"
67+
"--retrofitCompletableFutures",
68+
"--jerseyBinaryAsResponse"
6869
};
6970
CliConfiguration expectedConfiguration = CliConfiguration.builder()
7071
.target(targetFile)
7172
.outputDirectory(folder.getRoot())
7273
.generateObjects(true)
73-
.featureFlags(ImmutableSet.of(FeatureFlags.RetrofitCompletableFutures))
74+
.featureFlags(ImmutableSet.of(
75+
FeatureFlags.RetrofitCompletableFutures,
76+
FeatureFlags.JerseyBinaryAsResponse))
7477
.build();
7578
assertThat(ConjureJavaCli.parseCliConfiguration(args)).isEqualTo(expectedConfiguration);
7679
}

0 commit comments

Comments
 (0)