Skip to content

Commit

Permalink
Support AsyncFile, Path. Added PathPart and FilePart
Browse files Browse the repository at this point in the history
  • Loading branch information
FroMage committed Apr 20, 2021
1 parent 3b461a2 commit 7b9fc5b
Show file tree
Hide file tree
Showing 27 changed files with 857 additions and 1 deletion.
20 changes: 20 additions & 0 deletions docs/src/main/asciidoc/resteasy-reactive.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,26 @@ response (see <<negotiation>> for more advanced information).
You can return any of the pre-defined types that you can read from the <<resource-types,HTTP response>>,
and any other type will be mapped <<json,from that type to JSON>>.

In addition, the following return types are also supported:

.Table Additional response body parameter type
|===
|Type|Usage

|link:{jdkapi}/java/nio/file/Path.html[`Path`]
|The contents of the file specified by the given path

|`PathPart`
|The partial contents of the file specified by the given path

|`FilePart`
|The partial contents of a file

|link:{vertxapi}io/vertx/core/file/AsyncFile.html[`AsyncFile`]
|Vert.x AsyncFile, which can be in full, or partial

|===

Alternately, you can also return a <<reactive,reactive type>> such as link:{mutinyapi}/io/smallrye/mutiny/Uni.html[`Uni`],
link:{mutinyapi}/io/smallrye/mutiny/Multi.html[`Multi`] or
link:{jdkapi}/java/util/concurrent/CompletionStage.html[`CompletionStage`]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package io.quarkus.resteasy.reactive.common.runtime;

import java.io.IOException;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;

import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyWriter;

import io.vertx.core.file.AsyncFile;

public class VertxAsyncFileMessageBodyWriter implements MessageBodyWriter<AsyncFile> {
public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
// allow for subtypes, such as AsyncFileImpl
return AsyncFile.class.isAssignableFrom(type);
}

public void writeTo(AsyncFile asyncFile, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException {
throw new UnsupportedOperationException("Returning an AsyncFile is not supported with WriterInterceptors");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import io.quarkus.vertx.http.runtime.security.QuarkusHttpUser;
import io.undertow.server.HttpServerExchange;
import io.undertow.server.ResponseCommitListener;
import io.vertx.core.Handler;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.core.net.impl.ConnectionBase;
Expand Down Expand Up @@ -574,4 +575,26 @@ public V setValue(V value) {
return old;
}
}

@Override
public ServerHttpResponse sendFile(String path, long offset, long length) {
context.response().sendFile(path, offset, length);
return this;
}

@Override
public boolean isWriteQueueFull() {
return context.response().writeQueueFull();
}

@Override
public ServerHttpResponse addDrainHandler(Runnable onDrain) {
context.response().drainHandler(new Handler<Void>() {
@Override
public void handle(Void event) {
onDrain.run();
}
});
return this;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@
import io.quarkus.resteasy.reactive.server.runtime.ResteasyReactiveInitialiser;
import io.quarkus.resteasy.reactive.server.runtime.ResteasyReactiveRecorder;
import io.quarkus.resteasy.reactive.server.runtime.ResteasyReactiveRuntimeRecorder;
import io.quarkus.resteasy.reactive.server.runtime.ServerVertxAsyncFileMessageBodyWriter;
import io.quarkus.resteasy.reactive.server.runtime.ServerVertxBufferMessageBodyWriter;
import io.quarkus.resteasy.reactive.server.runtime.exceptionmappers.AuthenticationCompletionExceptionMapper;
import io.quarkus.resteasy.reactive.server.runtime.exceptionmappers.AuthenticationFailedExceptionMapper;
Expand Down Expand Up @@ -127,6 +128,7 @@
import io.quarkus.vertx.http.runtime.VertxHttpRecorder;
import io.vertx.core.Handler;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.file.AsyncFile;
import io.vertx.ext.web.RoutingContext;

public class ResteasyReactiveProcessor {
Expand All @@ -148,6 +150,10 @@ void vertxIntegration(BuildProducer<MessageBodyWriterBuildItem> writerBuildItemB
writerBuildItemBuildProducer.produce(new MessageBodyWriterBuildItem(ServerVertxBufferMessageBodyWriter.class.getName(),
Buffer.class.getName(), Collections.singletonList(MediaType.WILDCARD), RuntimeType.SERVER, true,
Priorities.USER));
writerBuildItemBuildProducer
.produce(new MessageBodyWriterBuildItem(ServerVertxAsyncFileMessageBodyWriter.class.getName(),
AsyncFile.class.getName(), Collections.singletonList(MediaType.WILDCARD), RuntimeType.SERVER, true,
Priorities.USER));
}

@BuildStep
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package io.quarkus.resteasy.reactive.server.test.providers;

import java.io.File;
import java.nio.file.Paths;

import javax.ws.rs.GET;
import javax.ws.rs.Path;

import org.jboss.resteasy.reactive.FilePart;
import org.jboss.resteasy.reactive.PathPart;

import io.smallrye.mutiny.Uni;
import io.vertx.core.file.AsyncFile;
import io.vertx.core.file.OpenOptions;
import io.vertx.ext.web.RoutingContext;

@Path("providers/file")
public class FileResource {

private static final String FILE = "src/test/resources/lorem.txt";

@Path("file")
@GET
public File getFile() {
return new File(FILE);
}

@Path("file-partial")
@GET
public FilePart getFilePart() {
return new FilePart(new File(FILE), 20, 10);
}

@Path("path")
@GET
public java.nio.file.Path getPath() {
return Paths.get(FILE);
}

@Path("path-partial")
@GET
public PathPart getPathPart() {
return new PathPart(Paths.get(FILE), 20, 10);
}

@Path("async-file")
@GET
public Uni<AsyncFile> getAsyncFile(RoutingContext vertxRequest) {
return Uni.createFrom().emitter(emitter -> {
vertxRequest.vertx().fileSystem().open(FILE, new OpenOptions(), result -> {
if (result.succeeded())
emitter.complete(result.result());
else
emitter.fail(result.cause());
});
});
}

@Path("async-file-partial")
@GET
public Uni<AsyncFile> getAsyncFilePartial(RoutingContext vertxRequest) {
return Uni.createFrom().emitter(emitter -> {
vertxRequest.vertx().fileSystem().open(FILE, new OpenOptions(), result -> {
if (result.succeeded()) {
AsyncFile asyncFile = result.result();
asyncFile.setReadPos(20);
asyncFile.setReadLength(10);
emitter.complete(asyncFile);
} else
emitter.fail(result.cause());
});
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package io.quarkus.resteasy.reactive.server.test.providers;

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

import javax.ws.rs.core.HttpHeaders;

import org.hamcrest.Matchers;
import org.jboss.resteasy.reactive.FilePart;
import org.jboss.resteasy.reactive.PathPart;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.test.QuarkusUnitTest;
import io.quarkus.test.common.http.TestHTTPResource;
import io.restassured.RestAssured;

public class FileTestCase {

private final static String LOREM = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut\n"
+
"enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor\n"
+
"in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident,\n"
+
" sunt in culpa qui officia deserunt mollit anim id est laborum.\n" +
"\n" +
"";
private static final String FILE = "src/test/resources/lorem.txt";

@TestHTTPResource
URI uri;

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
.addClasses(FileResource.class, WithWriterInterceptor.class, WriterInterceptor.class));

@Test
public void testFiles() throws Exception {
// adjusting expected file size for Windows, whose git checkout will adjust line separators
String content;
if (System.lineSeparator().length() == 2) {
content = LOREM.replace("\n", System.lineSeparator());
} else {
content = LOREM;
}
String contentLength = String.valueOf(content.length());
RestAssured.get("/providers/file/file")
.then()
.statusCode(200)
.header(HttpHeaders.CONTENT_LENGTH, contentLength)
.body(Matchers.equalTo(content));
RestAssured.get("/providers/file/file-partial")
.then()
.statusCode(200)
.header(HttpHeaders.CONTENT_LENGTH, "10")
.body(Matchers.equalTo(content.substring(20, 30)));
RestAssured.get("/providers/file/path")
.then()
.statusCode(200)
.header(HttpHeaders.CONTENT_LENGTH, contentLength)
.body(Matchers.equalTo(content));
RestAssured.get("/providers/file/path-partial")
.then()
.statusCode(200)
.header(HttpHeaders.CONTENT_LENGTH, "10")
.body(Matchers.equalTo(content.substring(20, 30)));
RestAssured.get("/providers/file/async-file")
.then()
.header(HttpHeaders.CONTENT_LENGTH, Matchers.nullValue())
.statusCode(200)
.body(Matchers.equalTo(content));
RestAssured.get("/providers/file/async-file-partial")
.then()
.statusCode(200)
.header(HttpHeaders.CONTENT_LENGTH, "10")
.body(Matchers.equalTo(LOREM.substring(20, 30)));
}

@Test
public void testChecks() throws IOException {
// creation-time checks
Path path = Paths.get(FILE);
// works
new PathPart(path, 10, 10);
new PathPart(path, 0, Files.size(path));
// fails
try {
new PathPart(path, -1, 10);
Assertions.fail();
} catch (IllegalArgumentException x) {
}
try {
new PathPart(path, 0, -1);
Assertions.fail();
} catch (IllegalArgumentException x) {
}
try {
new PathPart(path, 0, 1000);
Assertions.fail();
} catch (IllegalArgumentException x) {
}
try {
new PathPart(path, 250, 250);
Assertions.fail();
} catch (IllegalArgumentException x) {
}

File file = new File(FILE);
// works
new FilePart(file, 10, 10);
new FilePart(file, 0, file.length());
// fails
try {
new FilePart(file, -1, 10);
Assertions.fail();
} catch (IllegalArgumentException x) {
}
try {
new FilePart(file, 0, -1);
Assertions.fail();
} catch (IllegalArgumentException x) {
}
try {
new FilePart(file, 0, 1000);
Assertions.fail();
} catch (IllegalArgumentException x) {
}
try {
new FilePart(file, 250, 250);
Assertions.fail();
} catch (IllegalArgumentException x) {
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package io.quarkus.resteasy.reactive.server.test.providers;

import javax.ws.rs.GET;
import javax.ws.rs.Path;

import io.vertx.core.file.AsyncFile;
import io.vertx.ext.web.RoutingContext;

@Path("providers/file-invalid")
public class InvalidFileResource {

@WithWriterInterceptor
@Path("async-file-blocking")
@GET
public AsyncFile getAsyncFileBlocking(RoutingContext vertxRequest) {
// we're not calling this anyway
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package io.quarkus.resteasy.reactive.server.test.providers;

import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.test.QuarkusUnitTest;

public class InvalidFileTestCase {

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
.addClasses(InvalidFileResource.class, WithWriterInterceptor.class, WriterInterceptor.class))
.assertException(t -> {
while (t.getCause() != null)
t = t.getCause();
Assertions.assertTrue(
t.getMessage().equals("Endpoints that return an AsyncFile cannot have any WriterInterceptor set"));
});

@Test
public void test() throws Exception {
Assertions.fail("Deployment should have failed");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package io.quarkus.resteasy.reactive.server.test.providers;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import javax.ws.rs.NameBinding;

@NameBinding
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface WithWriterInterceptor {

}
Loading

0 comments on commit 7b9fc5b

Please sign in to comment.