Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support AsyncFile, Path. Added PathPart and FilePart #16653

Merged
merged 1 commit into from
May 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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, Matchers.nullValue())
.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