You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@juneau.apache.org by ja...@apache.org on 2018/09/30 20:45:18 UTC
[juneau] branch master updated: Support for end-to-end REST Java
interfaces.
This is an automated email from the ASF dual-hosted git repository.
jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git
The following commit(s) were added to refs/heads/master by this push:
new 8abb14f Support for end-to-end REST Java interfaces.
8abb14f is described below
commit 8abb14f95f55f1b66dae5b3093d297ae5af9c5b4
Author: JamesBognar <ja...@apache.org>
AuthorDate: Sun Sep 30 16:44:58 2018 -0400
Support for end-to-end REST Java interfaces.
---
.../java/org/apache/juneau/internal/IOUtils.java | 317 ++++++++++------
.../main/java/org/apache/juneau/utils/IOPipe.java | 69 +++-
.../org/apache/juneau/rest/client/RestCall.java | 17 +
.../org/apache/juneau/rest/client/RestClient.java | 2 +-
.../rest/client/remote/RemoteMethodReturn.java | 7 +-
.../rest/client/remote/EndToEndInterfaceTest.java | 405 +++++++++++++++++++++
.../java/org/apache/juneau/rest/RestContext.java | 6 +-
.../org/apache/juneau/rest/RestJavaMethod.java | 2 +-
.../java/org/apache/juneau/rest/RestRequest.java | 52 ++-
.../apache/juneau/rest/helper/ReaderResource.java | 211 +++++++++--
.../juneau/rest/helper/ReaderResourceBuilder.java | 127 -------
.../apache/juneau/rest/helper/StreamResource.java | 208 +++++++++--
.../juneau/rest/helper/StreamResourceBuilder.java | 115 ------
.../org/apache/juneau/rest/response/Found.java | 12 +
.../juneau/rest/response/MovedPermanently.java | 12 +
.../juneau/rest/response/PermanentRedirect.java | 12 +
.../org/apache/juneau/rest/response/SeeOther.java | 12 +
.../juneau/rest/response/TemporaryRedirect.java | 12 +
.../rest/annotation/AnnotationInheritanceTest.java | 70 +++-
19 files changed, 1189 insertions(+), 479 deletions(-)
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/IOUtils.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/IOUtils.java
index efea505..074baa2 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/IOUtils.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/IOUtils.java
@@ -41,21 +41,6 @@ public final class IOUtils {
}
/**
- * Reads the contents of a file into a string.
- *
- * @param in The file to read using default character encoding.
- * @return The contents of the reader as a string, or <jk>null</jk> if file does not exist.
- * @throws IOException If a problem occurred trying to read from the reader.
- */
- public static String read(File in) throws IOException {
- if (in == null || ! in.exists())
- return null;
- try (Reader r = FileReaderBuilder.create(in).build()) {
- return read(r, 0, 1024);
- }
- }
-
- /**
* Reads the specified object to a <code>String</code>.
*
* <p>
@@ -68,53 +53,54 @@ public final class IOUtils {
* <li><code><jk>byte</jk>[]</code>
* </ul>
*
- * @param o The object to read.
+ * @param in The object to read.
* @return The object serialized to a string, or <jk>null</jk> if it wasn't a supported type.
* @throws IOException
*/
- public static String read(Object o) throws IOException {
- if (o instanceof CharSequence)
- return o.toString();
- if (o instanceof File)
- return read((File)o);
- if (o instanceof Reader)
- return read((Reader)o);
- if (o instanceof InputStream)
- return read((InputStream)o);
- if (o instanceof byte[])
- return read(new ByteArrayInputStream((byte[])o));
- return null;
+ public static String read(Object in) throws IOException {
+ if (in == null)
+ return null;
+ if (in instanceof CharSequence)
+ return in.toString();
+ if (in instanceof File)
+ return read((File)in);
+ if (in instanceof Reader)
+ return read((Reader)in);
+ if (in instanceof InputStream)
+ return read((InputStream)in);
+ if (in instanceof byte[])
+ return read(new ByteArrayInputStream((byte[])in));
+ throw new IOException("Cannot convert object of type '"+in.getClass().getName()+"' to a String.");
}
/**
- * Writes the contents of the specified <code>Reader</code> to the specified file.
+ * Same as {@link #read(Object)} but appends all the input into a single String.
*
- * @param out The file to write the output to.
- * @param in The reader to pipe from.
- * @return The number of characters written to the file.
+ * @param in The objects to read.
+ * @return The objects serialized to a string, never <jk>null</jk>.
* @throws IOException
*/
- public static int write(File out, Reader in) throws IOException {
- assertFieldNotNull(out, "out");
- assertFieldNotNull(in, "in");
- try (Writer w = FileWriterBuilder.create(out).build()) {
- return IOPipe.create(in, w).run();
- }
+ public static String read(Object...in) throws IOException {
+ if (in.length == 1)
+ return read(in[0]);
+ StringWriter sw = new StringWriter();
+ for (Object o : in)
+ sw.write(emptyIfNull(read(o)));
+ return sw.toString();
}
/**
- * Writes the contents of the specified <code>InputStream</code> to the specified file.
+ * Reads the contents of a file into a string.
*
- * @param out The file to write the output to.
- * @param in The input stream to pipe from.
- * @return The number of characters written to the file.
- * @throws IOException
+ * @param in The file to read using default character encoding.
+ * @return The contents of the reader as a string, or <jk>null</jk> if file does not exist.
+ * @throws IOException If a problem occurred trying to read from the reader.
*/
- public static int write(File out, InputStream in) throws IOException {
- assertFieldNotNull(out, "out");
- assertFieldNotNull(in, "in");
- try (OutputStream os = new FileOutputStream(out)) {
- return IOPipe.create(in, os).run();
+ public static String read(File in) throws IOException {
+ if (in == null || ! in.exists())
+ return null;
+ try (Reader r = FileReaderBuilder.create(in).build()) {
+ return read(r, 0, 1024);
}
}
@@ -157,48 +143,6 @@ public final class IOUtils {
}
/**
- * Read the specified input stream into a byte array and closes the stream.
- *
- * @param in The input stream.
- * @param bufferSize The expected size of the buffer.
- * @return The contents of the stream as a byte array.
- * @throws IOException Thrown by underlying stream.
- */
- public static byte[] readBytes(InputStream in, int bufferSize) throws IOException {
- if (in == null)
- return null;
- ByteArrayOutputStream buff = new ByteArrayOutputStream(bufferSize);
- int nRead;
- byte[] b = new byte[Math.min(bufferSize, 8192)];
-
- try {
- while ((nRead = in.read(b, 0, b.length)) != -1)
- buff.write(b, 0, nRead);
- buff.flush();
-
- return buff.toByteArray();
- } finally {
- in.close();
- }
- }
-
- /**
- * Reads a raw stream of bytes from the specified file.
- *
- * @param f The file to read.
- * @return A byte array containing the contents of the file.
- * @throws IOException
- */
- public static byte[] readBytes(File f) throws IOException {
- if (f == null || ! (f.exists() && f.canRead()))
- return null;
-
- try (FileInputStream fis = new FileInputStream(f)) {
- return readBytes(fis, (int)f.length());
- }
- }
-
- /**
* Reads the specified input into a {@link String} until the end of the input is reached.
*
* <p>
@@ -217,6 +161,8 @@ public final class IOUtils {
public static String read(Reader in, int length, int bufferSize) throws IOException {
if (in == null)
return null;
+ if (bufferSize == 0)
+ bufferSize = 1024;
length = (length <= 0 ? bufferSize : length);
StringBuilder sb = new StringBuilder(length); // Assume they're ASCII characters.
try {
@@ -231,55 +177,194 @@ public final class IOUtils {
}
/**
- * Pipes the contents of the specified reader into the writer.
+ * Read the specified object into a byte array.
*
- * <p>
- * The reader is closed, the writer is not.
+ * @param in
+ * The object to read into a byte array.
+ * <br>Can be any of the following types:
+ * <ul>
+ * <li><code><jk>byte</jk>[]</code>
+ * <li>{@link InputStream}
+ * <li>{@link Reader}
+ * <li>{@link CharSequence}
+ * <li>{@link File}
+ * </ul>
+ * @param buffSize
+ * The buffer size to use.
+ * @return The contents of the stream as a byte array.
+ * @throws IOException Thrown by underlying stream or if object is not a supported type.
+ */
+ public static byte[] readBytes(Object in, int buffSize) throws IOException {
+ if (in == null)
+ return new byte[0];
+ if (in instanceof byte[])
+ return (byte[])in;
+ if (in instanceof CharSequence)
+ return in.toString().getBytes(UTF8);
+ if (in instanceof InputStream)
+ return readBytes((InputStream)in, buffSize);
+ if (in instanceof Reader)
+ return read((Reader)in, 0, buffSize).getBytes(UTF8);
+ if (in instanceof File)
+ return readBytes((File)in, buffSize);
+ throw new IOException("Cannot convert object of type '"+in.getClass().getName()+"' to a byte array.");
+ }
+
+ /**
+ * Read the specified input stream into a byte array.
*
* @param in
- * The reader to pipe from.
- * @param out
- * The writer to pipe to.
+ * The stream to read into a byte array.
+ * @return The contents of the stream as a byte array.
+ * @throws IOException Thrown by underlying stream.
+ */
+ public static byte[] readBytes(InputStream in) throws IOException {
+ return readBytes(in, 1024);
+ }
+
+ /**
+ * Read the specified input stream into a byte array.
+ *
+ * @param in
+ * The stream to read into a byte array.
+ * @param buffSize
+ * The buffer size to use.
+ * @return The contents of the stream as a byte array.
+ * @throws IOException Thrown by underlying stream.
+ */
+ public static byte[] readBytes(InputStream in, int buffSize) throws IOException {
+ if (buffSize == 0)
+ buffSize = 1024;
+ try (final ByteArrayOutputStream buff = new ByteArrayOutputStream(buffSize)) {
+ int nRead;
+ byte[] b = new byte[buffSize];
+ while ((nRead = in.read(b, 0, b.length)) != -1)
+ buff.write(b, 0, nRead);
+ buff.flush();
+ return buff.toByteArray();
+ }
+ }
+
+ /**
+ * Read the specified file into a byte array.
+ *
+ * @param in
+ * The file to read into a byte array.
+ * @return The contents of the file as a byte array.
+ * @throws IOException Thrown by underlying stream.
+ */
+ public static byte[] readBytes(File in) throws IOException {
+ return readBytes(in, 1024);
+ }
+
+ /**
+ * Read the specified file into a byte array.
+ *
+ * @param in
+ * The file to read into a byte array.
+ * @param buffSize
+ * The buffer size to use.
+ * @return The contents of the file as a byte array.
+ * @throws IOException Thrown by underlying stream.
+ */
+ public static byte[] readBytes(File in, int buffSize) throws IOException {
+ if (buffSize == 0)
+ buffSize = 1024;
+ if (! (in.exists() && in.canRead()))
+ return new byte[0];
+ buffSize = Math.min((int)in.length(), buffSize);
+ try (FileInputStream fis = new FileInputStream(in)) {
+ return readBytes(fis, buffSize);
+ }
+ }
+
+ /**
+ * Shortcut for calling <code>readBytes(in, 1024);</code>
+ *
+ * @param in
+ * The object to read into a byte array.
+ * <br>Can be any of the following types:
+ * <ul>
+ * <li><code><jk>byte</jk>[]</code>
+ * <li>{@link InputStream}
+ * <li>{@link Reader}
+ * <li>{@link CharSequence}
+ * <li>{@link File}
+ * </ul>
+ * @return The contents of the stream as a byte array.
+ * @throws IOException Thrown by underlying stream or if object is not a supported type.
+ */
+ public static byte[] readBytes(Object in) throws IOException {
+ return readBytes(in, 1024);
+ }
+
+ /**
+ * Same as {@link #readBytes(Object)} but appends all the input into a single byte array.
+ *
+ * @param in The objects to read.
+ * @return The objects serialized to a byte array, never <jk>null</jk>.
+ * @throws IOException
+ */
+ public static byte[] readBytes(Object...in) throws IOException {
+ if (in.length == 1)
+ return readBytes(in[0]);
+ try (final ByteArrayOutputStream buff = new ByteArrayOutputStream(1024)) {
+ for (Object o : in) {
+ byte[] bo = readBytes(o);
+ if (bo != null)
+ buff.write(bo);
+ }
+ buff.flush();
+ return buff.toByteArray();
+ }
+ }
+
+ /**
+ * Writes the contents of the specified <code>Reader</code> to the specified file.
+ *
+ * @param out The file to write the output to.
+ * @param in The reader to pipe from.
+ * @return The number of characters written to the file.
* @throws IOException
*/
- public static void pipe(Reader in, Writer out) throws IOException {
+ public static int write(File out, Reader in) throws IOException {
assertFieldNotNull(out, "out");
assertFieldNotNull(in, "in");
- IOPipe.create(in, out).run();
+ try (Writer w = FileWriterBuilder.create(out).build()) {
+ return IOPipe.create(in, w).run();
+ }
}
/**
- * Pipes the contents of the specified object into the writer.
- *
- * <p>
- * The reader is closed, the writer is not.
+ * Writes the contents of the specified <code>InputStream</code> to the specified file.
*
- * @param in
- * The input to pipe from.
- * Can be any of the types defined by {@link #toReader(Object)}.
- * @param out
- * The writer to pipe to.
+ * @param out The file to write the output to.
+ * @param in The input stream to pipe from.
+ * @return The number of characters written to the file.
* @throws IOException
*/
- public static void pipe(Object in, Writer out) throws IOException {
- pipe(toReader(in), out);
+ public static int write(File out, InputStream in) throws IOException {
+ assertFieldNotNull(out, "out");
+ assertFieldNotNull(in, "in");
+ try (OutputStream os = new FileOutputStream(out)) {
+ return IOPipe.create(in, os).run();
+ }
}
/**
- * Pipes the contents of the specified streams.
+ * Pipes the contents of the specified object into the writer.
*
* <p>
- * The input stream is closed, the output stream is not.
+ * The reader is closed, the writer is not.
*
* @param in
- * The reader to pipe from.
+ * The input to pipe from.
+ * Can be any of the types defined by {@link #toReader(Object)}.
* @param out
* The writer to pipe to.
* @throws IOException
*/
- public static void pipe(InputStream in, OutputStream out) throws IOException {
- assertFieldNotNull(out, "out");
- assertFieldNotNull(in, "in");
+ public static void pipe(Object in, Writer out) throws IOException {
IOPipe.create(in, out).run();
}
@@ -297,7 +382,7 @@ public final class IOUtils {
* @throws IOException
*/
public static void pipe(Object in, OutputStream out) throws IOException {
- pipe(toInputStream(in), out);
+ IOPipe.create(in, out).run();
}
/**
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/IOPipe.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/IOPipe.java
index 8990c4e..8bd7428 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/IOPipe.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/IOPipe.java
@@ -47,12 +47,10 @@ public class IOPipe {
assertFieldNotNull(input, "input");
assertFieldNotNull(output, "output");
- if (input instanceof CharSequence)
- this.input = new StringReader(input.toString());
- else if (input instanceof InputStream || input instanceof Reader)
+ if (input instanceof InputStream || input instanceof Reader || input instanceof File || input instanceof byte[] || input instanceof CharSequence || input == null)
this.input = input;
else
- illegalArg("Invalid input class type. Must be one of the following: InputStream, Reader, CharSequence");
+ illegalArg("Invalid input class type. Must be one of the following: InputStream, Reader, CharSequence, byte[], File");
if (output instanceof OutputStream || output instanceof Writer)
this.output = output;
@@ -165,22 +163,41 @@ public class IOPipe {
int c = 0;
try {
- if (input instanceof InputStream && output instanceof OutputStream && lineProcessor == null) {
- InputStream in = (InputStream)input;
+ if (input == null)
+ return 0;
+
+ if ((input instanceof InputStream || input instanceof byte[]) && output instanceof OutputStream && lineProcessor == null) {
OutputStream out = (OutputStream)output;
- byte[] b = new byte[buffSize];
- int i;
- while ((i = in.read(b)) > 0) {
- c += i;
- out.write(b, 0, i);
+ if (input instanceof InputStream) {
+ InputStream in = (InputStream)input;
+ byte[] b = new byte[buffSize];
+ int i;
+ while ((i = in.read(b)) > 0) {
+ c += i;
+ out.write(b, 0, i);
+ }
+ } else {
+ byte[] b = (byte[])input;
+ out.write(b);
+ c = b.length;
}
out.flush();
} else {
- Reader in = (input instanceof Reader ? (Reader)input : new InputStreamReader((InputStream)input, UTF8));
+ @SuppressWarnings("resource")
Writer out = (output instanceof Writer ? (Writer)output : new OutputStreamWriter((OutputStream)output, UTF8));
- output = out;
- input = in;
+ closeIn |= input instanceof File;
if (byLines || lineProcessor != null) {
+ Reader in = null;
+ if (input instanceof Reader)
+ in = (Reader)input;
+ else if (input instanceof InputStream)
+ in = new InputStreamReader((InputStream)input, UTF8);
+ else if (input instanceof File)
+ in = new FileReader((File)input);
+ else if (input instanceof byte[])
+ in = new StringReader(new String((byte[])input, "UTF8"));
+ else if (input instanceof CharSequence)
+ in = new StringReader(input.toString());
try (Scanner s = new Scanner(in)) {
while (s.hasNextLine()) {
String l = s.nextLine();
@@ -195,11 +212,25 @@ public class IOPipe {
}
}
} else {
- int i;
- char[] b = new char[buffSize];
- while ((i = in.read(b)) > 0) {
- c += i;
- out.write(b, 0, i);
+ if (input instanceof InputStream)
+ input = new InputStreamReader((InputStream)input, UTF8);
+ else if (input instanceof File)
+ input = new FileReader((File)input);
+ else if (input instanceof byte[])
+ input = new String((byte[])input, UTF8);
+
+ if (input instanceof Reader) {
+ Reader in = (Reader)input;
+ int i;
+ char[] b = new char[buffSize];
+ while ((i = in.read(b)) > 0) {
+ c += i;
+ out.write(b, 0, i);
+ }
+ } else {
+ String s = input.toString();
+ out.write(s);
+ c = s.length();
}
}
out.flush();
diff --git a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestCall.java b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestCall.java
index d1c53da..ceff58b 100644
--- a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestCall.java
+++ b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestCall.java
@@ -16,6 +16,7 @@ import static org.apache.juneau.internal.ClassUtils.*;
import static org.apache.juneau.internal.IOUtils.*;
import static org.apache.juneau.internal.StringUtils.*;
import static org.apache.juneau.httppart.HttpPartType.*;
+import static org.apache.http.HttpStatus.*;
import java.io.*;
import java.lang.reflect.*;
@@ -2243,6 +2244,7 @@ public final class RestCall extends BeanSession implements Closeable {
connect();
Header h = response.getFirstHeader("Content-Type");
+ int sc = response.getStatusLine().getStatusCode();
String ct = firstNonEmpty(h == null ? null : h.getValue(), "text/plain");
MediaType mt = MediaType.forString(ct);
@@ -2254,6 +2256,21 @@ public final class RestCall extends BeanSession implements Closeable {
if (parser != null) {
try (Closeable in = parser.isReaderParser() ? getReader() : getInputStream()) {
+
+ // HttpClient automatically ignores the content body for certain HTTP status codes.
+ // So instantiate the object anyway if it has a no-arg constructor.
+ // This allows a remote resource method to return a NoContent object for example.
+ if (in == null && (sc < SC_OK || sc == SC_NO_CONTENT || sc == SC_NOT_MODIFIED || sc == SC_RESET_CONTENT)) {
+ Constructor<T> c = ClassUtils.findNoArgConstructor(type.getInnerClass(), Visibility.PUBLIC);
+ if (c != null) {
+ try {
+ return c.newInstance();
+ } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
+ throw new ParseException(e);
+ }
+ }
+ }
+
ParserSessionArgs pArgs = new ParserSessionArgs(this.getProperties(), null, response.getLocale(), null, mt, responseBodySchema, false, null);
return parser.createSession(pArgs).parse(in, type);
}
diff --git a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
index d0817cc..4e2b0f0 100644
--- a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
+++ b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
@@ -1121,7 +1121,7 @@ public class RestClient extends BeanContext implements Closeable {
} else if (rmr.getReturnValue() == RemoteReturn.BEAN) {
return rc.getResponse(rmr.getResponseBeanMeta());
} else {
- Object v = rc.getResponseBody(method.getGenericReturnType());
+ Object v = rc.getResponseBody(rmr.getReturnType());
if (v == null && method.getReturnType().isPrimitive())
v = ClassUtils.getPrimitiveDefault(method.getReturnType());
return v;
diff --git a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/remote/RemoteMethodReturn.java b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/remote/RemoteMethodReturn.java
index 03c2148..eb985a9 100644
--- a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/remote/RemoteMethodReturn.java
+++ b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/remote/RemoteMethodReturn.java
@@ -36,14 +36,15 @@ public final class RemoteMethodReturn {
RemoteMethodReturn(Method m) {
RemoteMethod rm = m.getAnnotation(RemoteMethod.class);
- RemoteReturn rv = m.getReturnType() == void.class ? RemoteReturn.NONE : rm == null ? RemoteReturn.BODY : rm.returns();
- this.returnType = m.getGenericReturnType();
- if (hasAnnotation(Response.class, m)) {
+ Class<?> rt = m.getReturnType();
+ RemoteReturn rv = rt == void.class ? RemoteReturn.NONE : rm == null ? RemoteReturn.BODY : rm.returns();
+ if (hasAnnotation(Response.class, rt) && rt.isInterface()) {
this.meta = ResponseBeanMeta.create(m, PropertyStore.DEFAULT);
rv = RemoteReturn.BEAN;
} else {
this.meta = null;
}
+ this.returnType = m.getGenericReturnType();
this.returnValue = rv;
}
diff --git a/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/remote/EndToEndInterfaceTest.java b/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/remote/EndToEndInterfaceTest.java
new file mode 100644
index 0000000..d31efcd
--- /dev/null
+++ b/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/remote/EndToEndInterfaceTest.java
@@ -0,0 +1,405 @@
+// ***************************************************************************************************************************
+// * 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. *
+// ***************************************************************************************************************************
+package org.apache.juneau.rest.client.remote;
+
+import static org.apache.juneau.http.HttpMethodName.*;
+import static org.junit.Assert.*;
+
+import org.apache.juneau.http.annotation.*;
+import org.apache.juneau.json.*;
+import org.apache.juneau.rest.annotation.*;
+import org.apache.juneau.rest.client.*;
+import org.apache.juneau.rest.mock.*;
+import org.apache.juneau.rest.response.*;
+import org.junit.*;
+import org.junit.runners.*;
+
+/**
+ * Tests inheritance of annotations from interfaces.
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@SuppressWarnings("javadoc")
+public class EndToEndInterfaceTest {
+
+ //=================================================================================================================
+ // Simple tests, split annotations.
+ //=================================================================================================================
+
+ @RemoteResource
+ public static interface IA {
+
+ @RemoteMethod(method="PUT", path="/a01")
+ public String a01(@Body String b);
+
+ @RemoteMethod(method="GET", path="/a02")
+ public String a02(@Query("foo") String b);
+
+ @RemoteMethod(method="GET", path="/a03")
+ public String a03(@Header("foo") String b);
+ }
+
+ @RestResource(serializers=SimpleJsonSerializer.class, parsers=JsonParser.class, defaultAccept="text/json")
+ public static class A implements IA {
+
+ @Override
+ @RestMethod(name=PUT, path="/a01")
+ public String a01(String b) {
+ return b;
+ }
+
+ @Override
+ @RestMethod(name=GET, path="/a02")
+ public String a02(String b) {
+ return b;
+ }
+
+ @Override
+ @RestMethod(name=GET, path="/a03")
+ public String a03(String b) {
+ return b;
+ }
+ }
+
+ private static MockRest a = MockRest.create(A.class);
+ private static IA ia = RestClient.create().json().mockHttpConnection(a).build().getRemoteResource(IA.class);
+
+ @Test
+ public void a01_splitAnnotations_Body() throws Exception {
+ assertEquals("foo", ia.a01("foo"));
+ }
+ @Test
+ public void a02_splitAnnotations_Query() throws Exception {
+ assertEquals("foo", ia.a02("foo"));
+ }
+ @Test
+ public void a03_splitAnnotations_Header() throws Exception {
+ assertEquals("foo", ia.a03("foo"));
+ }
+
+ //=================================================================================================================
+ // Simple tests, combined annotations.
+ //=================================================================================================================
+
+ @RemoteResource
+ @RestResource(serializers=SimpleJsonSerializer.class, parsers=JsonParser.class, defaultAccept="text/json")
+ public static interface IB {
+
+ @RemoteMethod(method="PUT", path="/a01")
+ @RestMethod(name=PUT, path="/a01")
+ public String b01(@Body String b);
+
+ @RemoteMethod(method="GET", path="/a02")
+ @RestMethod(name=GET, path="/a02")
+ public String b02(@Query("foo") String b);
+
+ @RemoteMethod(method="GET", path="/a03")
+ @RestMethod(name=GET, path="/a03")
+ public String b03(@Header("foo") String b);
+ }
+
+ public static class B implements IB {
+
+ @Override
+ public String b01(String b) {
+ return b;
+ }
+
+ @Override
+ public String b02(String b) {
+ return b;
+ }
+
+ @Override
+ public String b03(String b) {
+ return b;
+ }
+ }
+
+ private static MockRest b = MockRest.create(B.class);
+ private static IB ib = RestClient.create().json().mockHttpConnection(b).build().getRemoteResource(IB.class);
+
+ @Test
+ public void b01_combinedAnnotations_Body() throws Exception {
+ assertEquals("foo", ib.b01("foo"));
+ }
+ @Test
+ public void b02_combinedAnnotations_Query() throws Exception {
+ assertEquals("foo", ib.b02("foo"));
+ }
+ @Test
+ public void b03_combinedAnnotations_Header() throws Exception {
+ assertEquals("foo", ib.b03("foo"));
+ }
+
+ //=================================================================================================================
+ // Standard responses
+ //=================================================================================================================
+
+ @RemoteResource
+ @RestResource(serializers=SimpleJsonSerializer.class, parsers=JsonParser.class, defaultAccept="text/json")
+ public static interface IC {
+
+ @RemoteMethod
+ @RestMethod
+ public Ok ok();
+
+ @RemoteMethod
+ @RestMethod
+ public Accepted accepted();
+
+ @RemoteMethod
+ @RestMethod
+ public AlreadyReported alreadyReported();
+
+ @RemoteMethod
+ @RestMethod
+ public Continue _continue();
+
+ @RemoteMethod
+ @RestMethod
+ public Created created();
+
+ @RemoteMethod
+ @RestMethod
+ public EarlyHints earlyHints();
+
+ @RemoteMethod
+ @RestMethod
+ public Found found();
+
+ @RemoteMethod
+ @RestMethod
+ public IMUsed iMUsed();
+
+ @RemoteMethod
+ @RestMethod
+ public MovedPermanently movedPermanently();
+
+ @RemoteMethod
+ @RestMethod
+ public MultipleChoices multipleChoices();
+
+ @RemoteMethod
+ @RestMethod
+ public MultiStatus multiStatus();
+
+ @RemoteMethod
+ @RestMethod
+ public NoContent noContent();
+
+ @RemoteMethod
+ @RestMethod
+ public NonAuthoritiveInformation nonAuthoritiveInformation();
+
+ @RemoteMethod
+ @RestMethod
+ public NotModified notModified();
+
+ @RemoteMethod
+ @RestMethod
+ public PartialContent partialContent();
+
+ @RemoteMethod
+ @RestMethod
+ public PermanentRedirect permanentRedirect();
+
+ @RemoteMethod
+ @RestMethod
+ public Processing processing();
+
+ @RemoteMethod
+ @RestMethod
+ public ResetContent resetContent();
+
+ @RemoteMethod
+ @RestMethod
+ public SeeOther seeOther();
+
+ @RemoteMethod
+ @RestMethod
+ public SwitchingProtocols switchingProtocols();
+
+ @RemoteMethod
+ @RestMethod
+ public TemporaryRedirect temporaryRedirect();
+
+ @RemoteMethod
+ @RestMethod
+ public UseProxy useProxy();
+ }
+
+ public static class C implements IC {
+
+ @Override public Ok ok() { return Ok.OK; }
+ @Override public Accepted accepted() { return Accepted.INSTANCE; }
+ @Override public AlreadyReported alreadyReported() { return AlreadyReported.INSTANCE; }
+ @Override public Continue _continue() { return Continue.INSTANCE; }
+ @Override public Created created() { return Created.INSTANCE; }
+ @Override public EarlyHints earlyHints() { return EarlyHints.INSTANCE; }
+ @Override public Found found() { return Found.INSTANCE; }
+ @Override public IMUsed iMUsed() { return IMUsed.INSTANCE; }
+ @Override public MovedPermanently movedPermanently() { return MovedPermanently.INSTANCE; }
+ @Override public MultipleChoices multipleChoices() { return MultipleChoices.INSTANCE; }
+ @Override public MultiStatus multiStatus() { return MultiStatus.INSTANCE; }
+ @Override public NoContent noContent() { return NoContent.INSTANCE; }
+ @Override public NonAuthoritiveInformation nonAuthoritiveInformation() { return NonAuthoritiveInformation.INSTANCE; }
+ @Override public NotModified notModified() { return NotModified.INSTANCE; }
+ @Override public PartialContent partialContent() { return PartialContent.INSTANCE; }
+ @Override public PermanentRedirect permanentRedirect() { return PermanentRedirect.INSTANCE; }
+ @Override public Processing processing() { return Processing.INSTANCE; }
+ @Override public ResetContent resetContent() { return ResetContent.INSTANCE; }
+ @Override public SeeOther seeOther() { return SeeOther.INSTANCE; }
+ @Override public SwitchingProtocols switchingProtocols() { return SwitchingProtocols.INSTANCE; }
+ @Override public TemporaryRedirect temporaryRedirect() { return TemporaryRedirect.INSTANCE; }
+ @Override public UseProxy useProxy() { return UseProxy.INSTANCE; }
+ }
+
+ private static IC ic = RestClient.create().json().disableRedirectHandling().mockHttpConnection(MockRest.create(C.class)).build().getRemoteResource(IC.class);
+
+ @Test
+ public void c01_standardResponses_Ok() throws Exception {
+ assertEquals("OK", ic.ok().toString());
+ }
+ @Test
+ public void c02_standardResponses_Accepted() throws Exception {
+ assertEquals("Accepted", ic.accepted().toString());
+ }
+ @Test
+ public void c03_standardResponses_AlreadyReported() throws Exception {
+ assertEquals("Already Reported", ic.alreadyReported().toString());
+ }
+ @Test
+ public void c04_standardResponses_Continue() throws Exception {
+ // HttpClient goes into loop if status code is less than 200.
+ //assertEquals("Continue", ic._continue().toString());
+ }
+ @Test
+ public void c05_standardResponses_Created() throws Exception {
+ assertEquals("Created", ic.created().toString());
+ }
+ @Test
+ public void c06_standardResponses_EarlyHints() throws Exception {
+ // HttpClient goes into loop if status code is less than 200.
+ //assertEquals("Early Hints", ic.earlyHints().toString());
+ }
+ @Test
+ public void c07_standardResponses_Found() throws Exception {
+ assertEquals("Found", ic.found().toString());
+ }
+ @Test
+ public void c08_standardResponses_IMUsed() throws Exception {
+ assertEquals("IM Used", ic.iMUsed().toString());
+ }
+ @Test
+ public void c09_standardResponses_MovedPermanently() throws Exception {
+ assertEquals("Moved Permanently", ic.movedPermanently().toString());
+ }
+ @Test
+ public void c10_standardResponses_MultipleChoices() throws Exception {
+ assertEquals("Multiple Choices", ic.multipleChoices().toString());
+ }
+ @Test
+ public void c11_standardResponses_MultiStatus() throws Exception {
+ assertEquals("Multi-Status", ic.multiStatus().toString());
+ }
+ @Test
+ public void c12_standardResponses_NoContent() throws Exception {
+ assertEquals("No Content", ic.noContent().toString());
+ }
+ @Test
+ public void c13_standardResponses_NonAuthoritiveInformation() throws Exception {
+ assertEquals("Non-Authoritative Information", ic.nonAuthoritiveInformation().toString());
+ }
+ @Test
+ public void c14_standardResponses_NotModified() throws Exception {
+ assertEquals("Not Modified", ic.notModified().toString());
+ }
+ @Test
+ public void c15_standardResponses_PartialContent() throws Exception {
+ assertEquals("Partial Content", ic.partialContent().toString());
+ }
+ @Test
+ public void c16_standardResponses_PermanentRedirect() throws Exception {
+ assertEquals("Permanent Redirect", ic.permanentRedirect().toString());
+ }
+ @Test
+ public void c17_standardResponses_Processing() throws Exception {
+ // HttpClient goes into loop if status code is less than 200.
+ //assertEquals("Processing", ic.processing().toString());
+ }
+ @Test
+ public void c18_standardResponses_ResetContent() throws Exception {
+ assertEquals("Reset Content", ic.resetContent().toString());
+ }
+ @Test
+ public void c19_standardResponses_SeeOther() throws Exception {
+ assertEquals("See Other", ic.seeOther().toString());
+ }
+ @Test
+ public void c20_standardResponses_SwitchingProtocols() throws Exception {
+ // HttpClient goes into loop if status code is less than 200.
+ //assertEquals("Switching Protocols", ic.switchingProtocols().toString());
+ }
+ @Test
+ public void c21_standardResponses_TemporaryRedirect() throws Exception {
+ assertEquals("Temporary Redirect", ic.temporaryRedirect().toString());
+ }
+ @Test
+ public void c22_standardResponses_UseProxy() throws Exception {
+ assertEquals("Use Proxy", ic.useProxy().toString());
+ }
+
+ //=================================================================================================================
+ // Helper responses
+ //=================================================================================================================
+
+// @RemoteResource
+// @RestResource(serializers=SimpleJsonSerializer.class, parsers=JsonParser.class, defaultAccept="text/json")
+// public static interface ID {
+//
+// @RemoteMethod
+// @RestMethod
+// public BeanDescription beanDescription();
+//
+// @RemoteMethod
+// @RestMethod
+// public ChildResourceDescriptions beanDescription();
+
+ // BeanDescription.java
+// ChildResourceDescriptions.java
+// ResourceDescription.java
+// ResourceDescriptions.java
+// SeeOtherRoot.java
+ // ReaderResource
+ // StreamResource
+// }
+//
+// public static class D implements ID {
+//
+// }
+//
+// private static ID id = RestClient.create().json().disableRedirectHandling().mockHttpConnection(MockRest.create(D.class)).build().getRemoteResource(ID.class);
+//
+// @Test
+// public void d01_helperResponses_Ok() throws Exception {
+// assertEquals("OK", ic.ok().toString());
+// }
+
+
+ //-----------------------------------------------------------------------------------------------------------------
+ // TODO
+ //-----------------------------------------------------------------------------------------------------------------
+ // Object return type.
+ // Thrown objects.
+
+}
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
index a482968..90eca8d 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
@@ -518,7 +518,7 @@ public final class RestContext extends BeanContext {
* <ul>
* <li class='jm'>{@link RestRequest#getClasspathReaderResource(String) getClasspathReaderResource(String)}
* <li class='jm'>{@link RestRequest#getClasspathReaderResource(String,boolean) getClasspathReaderResource(String,boolean)}
- * <li class='jm'>{@link RestRequest#getClasspathReaderResource(String,boolean,MediaType) getClasspathReaderResource(String,boolean,MediaType)}
+ * <li class='jm'>{@link RestRequest#getClasspathReaderResource(String,boolean,MediaType,boolean) getClasspathReaderResource(String,boolean,MediaType,boolean)}
* </ul>
* </ul>
*
@@ -1470,7 +1470,7 @@ public final class RestContext extends BeanContext {
* Used for specifying the content type on file resources retrieved through the following methods:
* <ul>
* <li class='jm'>{@link RestContext#resolveStaticFile(String) RestContext.resolveStaticFile(String)}
- * <li class='jm'>{@link RestRequest#getClasspathReaderResource(String,boolean,MediaType)}
+ * <li class='jm'>{@link RestRequest#getClasspathReaderResource(String,boolean,MediaType,boolean)}
* <li class='jm'>{@link RestRequest#getClasspathReaderResource(String,boolean)}
* <li class='jm'>{@link RestRequest#getClasspathReaderResource(String)}
* </ul>
@@ -3573,7 +3573,7 @@ public final class RestContext extends BeanContext {
String name = (i == -1 ? p2 : p2.substring(i+1));
String mediaType = mimetypesFileTypeMap.getContentType(name);
Map<String,Object> responseHeaders = sfm.responseHeaders != null ? sfm.responseHeaders : staticFileResponseHeaders;
- sr = new StreamResource(MediaType.forString(mediaType), responseHeaders, is);
+ sr = new StreamResource(MediaType.forString(mediaType), responseHeaders, true, is);
break;
}
}
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestJavaMethod.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestJavaMethod.java
index c496a84..e156c6b 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestJavaMethod.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestJavaMethod.java
@@ -141,7 +141,7 @@ public class RestJavaMethod implements Comparable<RestJavaMethod> {
try {
- RestMethod m = method.getAnnotation(RestMethod.class);
+ RestMethod m = getAnnotation(RestMethod.class, method);
if (m == null)
throw new RestServletException("@RestMethod annotation not found on method ''{0}''", sig);
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
index da5b25a..5713fe8 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
@@ -1338,21 +1338,24 @@ public final class RestRequest extends HttpServletRequestWrapper {
* resolved by the variable resolver returned by {@link #getVarResolverSession()}.
* <br>See {@link RestContext#getVarResolver()} for the list of supported variables.
* @param mediaType The value to set as the <js>"Content-Type"</js> header for this object.
+ * @param cached If <jk>true</jk>, the resource will be read into a byte array for fast serialization.
* @return A new reader resource, or <jk>null</jk> if resource could not be found.
* @throws IOException
*/
- public ReaderResource getClasspathReaderResource(String name, boolean resolveVars, MediaType mediaType) throws IOException {
+ public ReaderResource getClasspathReaderResource(String name, boolean resolveVars, MediaType mediaType, boolean cached) throws IOException {
String s = context.getClasspathResourceAsString(name, getLocale());
if (s == null)
return null;
- ReaderResourceBuilder b = new ReaderResourceBuilder().mediaType(mediaType).contents(s);
+ ReaderResource.Builder b = ReaderResource.create().mediaType(mediaType).contents(s);
if (resolveVars)
b.varResolver(getVarResolverSession());
+ if (cached)
+ b.cached();
return b.build();
}
/**
- * Same as {@link #getClasspathReaderResource(String, boolean, MediaType)} except uses the resource mime-type map
+ * Same as {@link #getClasspathReaderResource(String, boolean, MediaType, boolean)} except uses the resource mime-type map
* constructed using {@link RestContextBuilder#mimeTypes(String...)} to determine the media type.
*
* @param name The name of the resource (i.e. the value normally passed to {@link Class#getResourceAsStream(String)}.
@@ -1364,7 +1367,7 @@ public final class RestRequest extends HttpServletRequestWrapper {
* @throws IOException
*/
public ReaderResource getClasspathReaderResource(String name, boolean resolveVars) throws IOException {
- return getClasspathReaderResource(name, resolveVars, MediaType.forString(context.getMediaTypeForName(name)));
+ return getClasspathReaderResource(name, resolveVars, MediaType.forString(context.getMediaTypeForName(name)), false);
}
/**
@@ -1375,7 +1378,46 @@ public final class RestRequest extends HttpServletRequestWrapper {
* @throws IOException
*/
public ReaderResource getClasspathReaderResource(String name) throws IOException {
- return getClasspathReaderResource(name, false, MediaType.forString(context.getMediaTypeForName(name)));
+ return getClasspathReaderResource(name, false, MediaType.forString(context.getMediaTypeForName(name)), false);
+ }
+
+ /**
+ * Returns an instance of a {@link StreamResource} that represents the contents of a resource binary file from the
+ * classpath.
+ *
+ * <h5 class='section'>See Also:</h5>
+ * <ul>
+ * <li class='jf'>{@link org.apache.juneau.rest.RestContext#REST_classpathResourceFinder}
+ * <li class='jm'>{@link org.apache.juneau.rest.RestRequest#getClasspathStreamResource(String)}
+ * </ul>
+ *
+ * @param name The name of the resource (i.e. the value normally passed to {@link Class#getResourceAsStream(String)}.
+ * @param mediaType The value to set as the <js>"Content-Type"</js> header for this object.
+ * @param cached If <jk>true</jk>, the resource will be read into a byte array for fast serialization.
+ * @return A new stream resource, or <jk>null</jk> if resource could not be found.
+ * @throws IOException
+ */
+ @SuppressWarnings("resource")
+ public StreamResource getClasspathStreamResource(String name, MediaType mediaType, boolean cached) throws IOException {
+ InputStream is = context.getClasspathResource(name, getLocale());
+ if (is == null)
+ return null;
+ StreamResource.Builder b = StreamResource.create().mediaType(mediaType).contents(is);
+ if (cached)
+ b.cached();
+ return b.build();
+ }
+
+ /**
+ * Same as {@link #getClasspathStreamResource(String, MediaType, boolean)} except uses the resource mime-type map
+ * constructed using {@link RestContextBuilder#mimeTypes(String...)} to determine the media type.
+ *
+ * @param name The name of the resource (i.e. the value normally passed to {@link Class#getResourceAsStream(String)}.
+ * @return A new stream resource, or <jk>null</jk> if resource could not be found.
+ * @throws IOException
+ */
+ public StreamResource getClasspathStreamResource(String name) throws IOException {
+ return getClasspathStreamResource(name, MediaType.forString(context.getMediaTypeForName(name)), false);
}
/**
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/helper/ReaderResource.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/helper/ReaderResource.java
index 07c7d2a..a157950 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/helper/ReaderResource.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/helper/ReaderResource.java
@@ -35,7 +35,7 @@ import org.apache.juneau.svl.*;
* <br>The contents of the request passed into the constructor are immediately converted to read-only strings.
*
* <p>
- * Instances of this class can be built using {@link ReaderResourceBuilder}.
+ * Instances of this class can be built using {@link Builder}.
*
* <h5 class='section'>See Also:</h5>
* <ul>
@@ -46,17 +46,12 @@ import org.apache.juneau.svl.*;
public class ReaderResource implements Writable {
private final MediaType mediaType;
- private final String[] contents;
+ private final Object[] contents;
private final VarResolverSession varSession;
private final Map<String,Object> headers;
- /**
- * Creates a new instance of a {@link ReaderResourceBuilder}
- *
- * @return A new instance of a {@link ReaderResourceBuilder}
- */
- public static ReaderResourceBuilder create() {
- return new ReaderResourceBuilder();
+ ReaderResource(Builder b) throws IOException {
+ this(b.mediaType, b.headers, b.varResolver, b.cached, b.contents.toArray());
}
/**
@@ -65,6 +60,9 @@ public class ReaderResource implements Writable {
* @param mediaType The resource media type.
* @param headers The HTTP response headers for this streamed resource.
* @param varSession Optional variable resolver for resolving variables in the string.
+ * @param cached
+ * Identifies if this resource is cached in memory.
+ * <br>If <jk>true</jk>, the contents will be loaded into a String for fast retrieval.
* @param contents
* The resource contents.
* <br>If multiple contents are specified, the results will be concatenated.
@@ -77,30 +75,153 @@ public class ReaderResource implements Writable {
* </ul>
* @throws IOException
*/
- public ReaderResource(MediaType mediaType, Map<String,Object> headers, VarResolverSession varSession, Object...contents) throws IOException {
+ public ReaderResource(MediaType mediaType, Map<String,Object> headers, VarResolverSession varSession, boolean cached, Object...contents) throws IOException {
this.mediaType = mediaType;
this.varSession = varSession;
-
this.headers = immutableMap(headers);
+ this.contents = cached ? new Object[]{read(contents)} : contents;
+ }
- this.contents = new String[contents.length];
- for (int i = 0; i < contents.length; i++) {
- Object c = contents[i];
- if (c == null)
- this.contents[i] = "";
- else if (c instanceof InputStream)
- this.contents[i] = read((InputStream)c);
- else if (c instanceof File)
- this.contents[i] = read((File)c);
- else if (c instanceof Reader)
- this.contents[i] = read((Reader)c);
- else if (c instanceof CharSequence)
- this.contents[i] = ((CharSequence)c).toString();
- else
- throw new IOException("Invalid class type passed to ReaderResource: " + c.getClass().getName());
+ //-----------------------------------------------------------------------------------------------------------------
+ // Builder
+ //-----------------------------------------------------------------------------------------------------------------
+
+ /**
+ * Creates a new instance of a {@link Builder} for this class.
+ *
+ * @return A new instance of a {@link Builder}.
+ */
+ public static Builder create() {
+ return new Builder();
+ }
+
+ /**
+ * Builder class for constructing {@link ReaderResource} objects.
+ *
+ * <h5 class='section'>See Also:</h5>
+ * <ul>
+ * <li class='link'>{@doc juneau-rest-server.RestMethod.ReaderResource}
+ * </ul>
+ */
+ public static class Builder {
+
+ ArrayList<Object> contents = new ArrayList<>();
+ MediaType mediaType;
+ VarResolverSession varResolver;
+ Map<String,Object> headers = new LinkedHashMap<>();
+ boolean cached;
+
+ /**
+ * Specifies the resource media type string.
+ *
+ * @param mediaType The resource media type string.
+ * @return This object (for method chaining).
+ */
+ public Builder mediaType(String mediaType) {
+ this.mediaType = MediaType.forString(mediaType);
+ return this;
+ }
+
+ /**
+ * Specifies the resource media type string.
+ *
+ * @param mediaType The resource media type string.
+ * @return This object (for method chaining).
+ */
+ public Builder mediaType(MediaType mediaType) {
+ this.mediaType = mediaType;
+ return this;
+ }
+
+ /**
+ * Specifies the contents for this resource.
+ *
+ * <p>
+ * This method can be called multiple times to add more content.
+ *
+ * @param contents
+ * The resource contents.
+ * <br>If multiple contents are specified, the results will be concatenated.
+ * <br>Contents can be any of the following:
+ * <ul>
+ * <li><code>InputStream</code>
+ * <li><code>Reader</code> - Converted to UTF-8 bytes.
+ * <li><code>File</code>
+ * <li><code>CharSequence</code> - Converted to UTF-8 bytes.
+ * </ul>
+ * @return This object (for method chaining).
+ */
+ public Builder contents(Object...contents) {
+ this.contents.addAll(Arrays.asList(contents));
+ return this;
+ }
+
+ /**
+ * Specifies an HTTP response header value.
+ *
+ * @param name The HTTP header name.
+ * @param value
+ * The HTTP header value.
+ * <br>Will be converted to a <code>String</code> using {@link Object#toString()}.
+ * @return This object (for method chaining).
+ */
+ public Builder header(String name, Object value) {
+ this.headers.put(name, value);
+ return this;
+ }
+
+ /**
+ * Specifies HTTP response header values.
+ *
+ * @param headers
+ * The HTTP headers.
+ * <br>Values will be converted to <code>Strings</code> using {@link Object#toString()}.
+ * @return This object (for method chaining).
+ */
+ public Builder headers(Map<String,Object> headers) {
+ this.headers.putAll(headers);
+ return this;
+ }
+
+ /**
+ * Specifies the variable resolver to use for this resource.
+ *
+ * @param varResolver The variable resolver.
+ * @return This object (for method chaining).
+ */
+ public Builder varResolver(VarResolverSession varResolver) {
+ this.varResolver = varResolver;
+ return this;
+ }
+
+ /**
+ * Specifies that this resource is intended to be cached.
+ *
+ * <p>
+ * This will trigger the contents to be loaded into a String for fast serializing.
+ *
+ * @return This object (for method chaining).
+ */
+ public Builder cached() {
+ this.cached = true;
+ return this;
+ }
+
+ /**
+ * Create a new {@link ReaderResource} using values in this builder.
+ *
+ * @return A new immutable {@link ReaderResource} object.
+ * @throws IOException
+ */
+ public ReaderResource build() throws IOException {
+ return new ReaderResource(this);
}
}
+ //-----------------------------------------------------------------------------------------------------------------
+ // Properties
+ //-----------------------------------------------------------------------------------------------------------------
+
/**
* Get the HTTP response headers.
*
@@ -117,11 +238,13 @@ public class ReaderResource implements Writable {
@ResponseBody
@Override /* Writeable */
public Writer writeTo(Writer w) throws IOException {
- for (String s : contents) {
- if (varSession != null)
- varSession.resolveTo(s, w);
- else
- w.write(s);
+ for (Object o : contents) {
+ if (o != null) {
+ if (varSession == null)
+ pipe(o, w);
+ else
+ varSession.resolveTo(read(o), w);
+ }
}
return w;
}
@@ -134,15 +257,13 @@ public class ReaderResource implements Writable {
@Override /* Object */
public String toString() {
- if (contents.length == 1 && varSession == null)
- return contents[0];
- StringWriter sw = new StringWriter();
- for (String s : contents) {
- if (varSession != null)
- return varSession.resolve(s);
- sw.write(s);
+ try {
+ if (contents.length == 1 && varSession == null)
+ return read(contents[0]);
+ return writeTo(new StringWriter()).toString();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
}
- return sw.toString();
}
/**
@@ -162,4 +283,16 @@ public class ReaderResource implements Writable {
s = s.replaceAll("(?s)\\/\\*(.*?)\\*\\/\\s*", "");
return s;
}
+
+ /**
+ * Returns the contents of this resource.
+ *
+ * @return The contents of this resource.
+ */
+ public Reader getContents() {
+ if (contents.length == 1 && varSession == null && contents[0] instanceof Reader) {
+ return (Reader)contents[0];
+ }
+ return new StringReader(toString());
+ }
}
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/helper/ReaderResourceBuilder.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/helper/ReaderResourceBuilder.java
deleted file mode 100644
index 3004a6d..0000000
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/helper/ReaderResourceBuilder.java
+++ /dev/null
@@ -1,127 +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. *
-// ***************************************************************************************************************************
-package org.apache.juneau.rest.helper;
-
-import java.io.*;
-import java.util.*;
-
-import org.apache.juneau.http.*;
-import org.apache.juneau.svl.*;
-
-/**
- * Builder class for constructing {@link ReaderResource} objects.
- *
- * <h5 class='section'>See Also:</h5>
- * <ul>
- * <li class='link'>{@doc juneau-rest-server.RestMethod.ReaderResource}
- * </ul>
- */
-public final class ReaderResourceBuilder {
- ArrayList<Object> contents = new ArrayList<>();
- MediaType mediaType;
- VarResolverSession varResolver;
- Map<String,Object> headers = new LinkedHashMap<>();
-
- /**
- * Specifies the resource media type string.
- *
- * @param mediaType The resource media type string.
- * @return This object (for method chaining).
- */
- public ReaderResourceBuilder mediaType(String mediaType) {
- this.mediaType = MediaType.forString(mediaType);
- return this;
- }
-
- /**
- * Specifies the resource media type string.
- *
- * @param mediaType The resource media type string.
- * @return This object (for method chaining).
- */
- public ReaderResourceBuilder mediaType(MediaType mediaType) {
- this.mediaType = mediaType;
- return this;
- }
-
- /**
- * Specifies the contents for this resource.
- *
- * <p>
- * This method can be called multiple times to add more content.
- *
- * @param contents
- * The resource contents.
- * <br>If multiple contents are specified, the results will be concatenated.
- * <br>Contents can be any of the following:
- * <ul>
- * <li><code>InputStream</code>
- * <li><code>Reader</code> - Converted to UTF-8 bytes.
- * <li><code>File</code>
- * <li><code>CharSequence</code> - Converted to UTF-8 bytes.
- * </ul>
- * @return This object (for method chaining).
- */
- public ReaderResourceBuilder contents(Object...contents) {
- this.contents.addAll(Arrays.asList(contents));
- return this;
- }
-
- /**
- * Specifies an HTTP response header value.
- *
- * @param name The HTTP header name.
- * @param value
- * The HTTP header value.
- * <br>Will be converted to a <code>String</code> using {@link Object#toString()}.
- * @return This object (for method chaining).
- */
- public ReaderResourceBuilder header(String name, Object value) {
- this.headers.put(name, value);
- return this;
- }
-
- /**
- * Specifies HTTP response header values.
- *
- * @param headers
- * The HTTP headers.
- * <br>Values will be converted to <code>Strings</code> using {@link Object#toString()}.
- * @return This object (for method chaining).
- */
- public ReaderResourceBuilder headers(Map<String,Object> headers) {
- this.headers.putAll(headers);
- return this;
- }
-
- /**
- * Specifies the variable resolver to use for this resource.
- *
- * @param varResolver The variable resolver.
- * @return This object (for method chaining).
- */
- public ReaderResourceBuilder varResolver(VarResolverSession varResolver) {
- this.varResolver = varResolver;
- return this;
- }
-
- /**
- * Create a new {@link ReaderResource} using values in this builder.
- *
- * @return A new immutable {@link ReaderResource} object.
- * @throws IOException
- */
- public ReaderResource build() throws IOException {
- return new ReaderResource(mediaType, headers, varResolver, contents.toArray());
- }
-}
\ No newline at end of file
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/helper/StreamResource.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/helper/StreamResource.java
index 8dc6097..feecd8e 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/helper/StreamResource.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/helper/StreamResource.java
@@ -16,6 +16,7 @@ import static org.apache.juneau.internal.CollectionUtils.*;
import static org.apache.juneau.internal.IOUtils.*;
import java.io.*;
+import java.nio.*;
import java.util.*;
import org.apache.juneau.*;
@@ -33,7 +34,7 @@ import org.apache.juneau.http.annotation.*;
* <br>The contents of the request passed into the constructor are immediately converted to read-only byte arrays.
*
* <p>
- * Instances of this class can be built using {@link StreamResourceBuilder}.
+ * Instances of this class can be built using {@link Builder}.
*
* <h5 class='section'>See Also:</h5>
* <ul>
@@ -44,16 +45,11 @@ import org.apache.juneau.http.annotation.*;
public class StreamResource implements Streamable {
private final MediaType mediaType;
- private final byte[][] contents;
+ private final Object[] contents;
private final Map<String,Object> headers;
- /**
- * Creates a new instance of a {@link StreamResourceBuilder}
- *
- * @return A new instance of a {@link StreamResourceBuilder}
- */
- public static StreamResourceBuilder create() {
- return new StreamResourceBuilder();
+ StreamResource(Builder b) throws IOException {
+ this(b.mediaType, b.headers, b.cached, b.contents.toArray());
}
/**
@@ -61,6 +57,9 @@ public class StreamResource implements Streamable {
*
* @param mediaType The resource media type.
* @param headers The HTTP response headers for this streamed resource.
+ * @param cached
+ * Identifies if this stream resource is cached in memory.
+ * <br>If <jk>true</jk>, the contents will be loaded into a byte array for fast retrieval.
* @param contents
* The resource contents.
* <br>If multiple contents are specified, the results will be concatenated.
@@ -74,28 +73,133 @@ public class StreamResource implements Streamable {
* </ul>
* @throws IOException
*/
- public StreamResource(MediaType mediaType, Map<String,Object> headers, Object...contents) throws IOException {
+ public StreamResource(MediaType mediaType, Map<String,Object> headers, boolean cached, Object...contents) throws IOException {
this.mediaType = mediaType;
-
this.headers = immutableMap(headers);
+ this.contents = cached ? new Object[]{readBytes(contents)} : contents;
+ }
- this.contents = new byte[contents.length][];
- for (int i = 0; i < contents.length; i++) {
- Object c = contents[i];
- if (c == null)
- this.contents[i] = new byte[0];
- else if (c instanceof byte[])
- this.contents[i] = (byte[])c;
- else if (c instanceof InputStream)
- this.contents[i] = readBytes((InputStream)c, 1024);
- else if (c instanceof File)
- this.contents[i] = readBytes((File)c);
- else if (c instanceof Reader)
- this.contents[i] = read((Reader)c).getBytes(UTF8);
- else if (c instanceof CharSequence)
- this.contents[i] = ((CharSequence)c).toString().getBytes(UTF8);
- else
- throw new IOException("Invalid class type passed to StreamResource: " + c.getClass().getName());
+ //-----------------------------------------------------------------------------------------------------------------
+ // Builder
+ //-----------------------------------------------------------------------------------------------------------------
+
+ /**
+ * Creates a new instance of a {@link Builder} for this class.
+ *
+ * @return A new instance of a {@link Builder}.
+ */
+ public static Builder create() {
+ return new Builder();
+ }
+
+ /**
+ * Builder class for constructing {@link StreamResource} objects.
+ *
+ * <h5 class='section'>See Also:</h5>
+ * <ul>
+ * <li class='link'>{@doc juneau-rest-server.RestMethod.StreamResource}
+ * </ul>
+ */
+ public static class Builder {
+ ArrayList<Object> contents = new ArrayList<>();
+ MediaType mediaType;
+ Map<String,Object> headers = new LinkedHashMap<>();
+ boolean cached;
+
+ /**
+ * Specifies the resource media type string.
+ *
+ * @param mediaType The resource media type string.
+ * @return This object (for method chaining).
+ */
+ public Builder mediaType(String mediaType) {
+ this.mediaType = MediaType.forString(mediaType);
+ return this;
+ }
+
+ /**
+ * Specifies the resource media type string.
+ *
+ * @param mediaType The resource media type string.
+ * @return This object (for method chaining).
+ */
+ public Builder mediaType(MediaType mediaType) {
+ this.mediaType = mediaType;
+ return this;
+ }
+
+ /**
+ * Specifies the contents for this resource.
+ *
+ * <p>
+ * This method can be called multiple times to add more content.
+ *
+ * @param contents
+ * The resource contents.
+ * <br>If multiple contents are specified, the results will be concatenated.
+ * <br>Contents can be any of the following:
+ * <ul>
+ * <li><code><jk>byte</jk>[]</code>
+ * <li><code>InputStream</code>
+ * <li><code>Reader</code> - Converted to UTF-8 bytes.
+ * <li><code>File</code>
+ * <li><code>CharSequence</code> - Converted to UTF-8 bytes.
+ * </ul>
+ * @return This object (for method chaining).
+ */
+ public Builder contents(Object...contents) {
+ this.contents.addAll(Arrays.asList(contents));
+ return this;
+ }
+
+ /**
+ * Specifies an HTTP response header value.
+ *
+ * @param name The HTTP header name.
+ * @param value
+ * The HTTP header value.
+ * <br>Will be converted to a <code>String</code> using {@link Object#toString()}.
+ * @return This object (for method chaining).
+ */
+ public Builder header(String name, Object value) {
+ this.headers.put(name, value);
+ return this;
+ }
+
+ /**
+ * Specifies HTTP response header values.
+ *
+ * @param headers
+ * The HTTP headers.
+ * <br>Values will be converted to <code>Strings</code> using {@link Object#toString()}.
+ * @return This object (for method chaining).
+ */
+ public Builder headers(Map<String,Object> headers) {
+ this.headers.putAll(headers);
+ return this;
+ }
+
+ /**
+ * Specifies that this resource is intended to be cached.
+ *
+ * <p>
+ * This will trigger the contents to be loaded into a byte array for fast serializing.
+ *
+ * @return This object (for method chaining).
+ */
+ public Builder cached() {
+ this.cached = true;
+ return this;
+ }
+
+ /**
+ * Create a new {@link StreamResource} using values in this builder.
+ *
+ * @return A new immutable {@link StreamResource} object.
+ * @throws IOException
+ */
+ public StreamResource build() throws IOException {
+ return new StreamResource(this);
}
}
@@ -115,8 +219,8 @@ public class StreamResource implements Streamable {
@ResponseBody
@Override /* Streamable */
public void streamTo(OutputStream os) throws IOException {
- for (byte[] b : contents)
- os.write(b);
+ for (Object c : contents)
+ pipe(c, os);
os.flush();
}
@@ -125,4 +229,48 @@ public class StreamResource implements Streamable {
public MediaType getMediaType() {
return mediaType;
}
+
+ /**
+ * Returns the contents of this stream resource.
+ *
+ * @return The contents of this stream resource.
+ * @throws IOException
+ */
+ public InputStream getContents() throws IOException {
+ if (contents.length == 1) {
+ Object c = contents[0];
+ if (c != null) {
+ if (c instanceof byte[])
+ return new ByteArrayInputStream((byte[])c);
+ else if (c instanceof InputStream)
+ return (InputStream)c;
+ else if (c instanceof File)
+ return new FileInputStream((File)c);
+ else if (c instanceof CharSequence)
+ return new ByteArrayInputStream((((CharSequence)c).toString().getBytes(UTF8)));
+ }
+ }
+ byte[][] bc = new byte[contents.length][];
+ int c = 0;
+ for (int i = 0; i < contents.length; i++) {
+ Object o = contents[i];
+ if (o == null)
+ bc[i] = new byte[0];
+ else if (o instanceof byte[])
+ bc[i] = (byte[])o;
+ else if (o instanceof InputStream)
+ bc[i] = readBytes((InputStream)o, 1024);
+ else if (o instanceof Reader)
+ bc[i] = read((Reader)o).getBytes(UTF8);
+ else if (o instanceof File)
+ bc[i] = readBytes((File)o);
+ else if (o instanceof CharSequence)
+ bc[i] = ((CharSequence)o).toString().getBytes(UTF8);
+ c += bc[i].length;
+ }
+ ByteBuffer bb = ByteBuffer.allocate(c);
+ for (byte[] b : bc)
+ bb.put(b);
+ return new ByteArrayInputStream(bb.array());
+ }
}
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/helper/StreamResourceBuilder.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/helper/StreamResourceBuilder.java
deleted file mode 100644
index b9fdcc2..0000000
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/helper/StreamResourceBuilder.java
+++ /dev/null
@@ -1,115 +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. *
-// ***************************************************************************************************************************
-package org.apache.juneau.rest.helper;
-
-import java.io.*;
-import java.util.*;
-
-import org.apache.juneau.http.*;
-
-/**
- * Builder class for constructing {@link StreamResource} objects.
- *
- * <h5 class='section'>See Also:</h5>
- * <ul>
- * <li class='link'>{@doc juneau-rest-server.RestMethod.StreamResource}
- * </ul>
- */
-public final class StreamResourceBuilder {
- ArrayList<Object> contents = new ArrayList<>();
- MediaType mediaType;
- Map<String,Object> headers = new LinkedHashMap<>();
-
- /**
- * Specifies the resource media type string.
- *
- * @param mediaType The resource media type string.
- * @return This object (for method chaining).
- */
- public StreamResourceBuilder mediaType(String mediaType) {
- this.mediaType = MediaType.forString(mediaType);
- return this;
- }
-
- /**
- * Specifies the resource media type string.
- *
- * @param mediaType The resource media type string.
- * @return This object (for method chaining).
- */
- public StreamResourceBuilder mediaType(MediaType mediaType) {
- this.mediaType = mediaType;
- return this;
- }
-
- /**
- * Specifies the contents for this resource.
- *
- * <p>
- * This method can be called multiple times to add more content.
- *
- * @param contents
- * The resource contents.
- * <br>If multiple contents are specified, the results will be concatenated.
- * <br>Contents can be any of the following:
- * <ul>
- * <li><code><jk>byte</jk>[]</code>
- * <li><code>InputStream</code>
- * <li><code>Reader</code> - Converted to UTF-8 bytes.
- * <li><code>File</code>
- * <li><code>CharSequence</code> - Converted to UTF-8 bytes.
- * </ul>
- * @return This object (for method chaining).
- */
- public StreamResourceBuilder contents(Object...contents) {
- this.contents.addAll(Arrays.asList(contents));
- return this;
- }
-
- /**
- * Specifies an HTTP response header value.
- *
- * @param name The HTTP header name.
- * @param value
- * The HTTP header value.
- * <br>Will be converted to a <code>String</code> using {@link Object#toString()}.
- * @return This object (for method chaining).
- */
- public StreamResourceBuilder header(String name, Object value) {
- this.headers.put(name, value);
- return this;
- }
-
- /**
- * Specifies HTTP response header values.
- *
- * @param headers
- * The HTTP headers.
- * <br>Values will be converted to <code>Strings</code> using {@link Object#toString()}.
- * @return This object (for method chaining).
- */
- public StreamResourceBuilder headers(Map<String,Object> headers) {
- this.headers.putAll(headers);
- return this;
- }
-
- /**
- * Create a new {@link StreamResource} using values in this builder.
- *
- * @return A new immutable {@link StreamResource} object.
- * @throws IOException
- */
- public StreamResource build() throws IOException {
- return new StreamResource(mediaType, headers, contents.toArray());
- }
-}
\ No newline at end of file
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/response/Found.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/response/Found.java
index 7631ea0..d2ce146 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/response/Found.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/response/Found.java
@@ -52,6 +52,18 @@ public class Found extends HttpResponse {
}
/**
+ * Constructor with no redirect.
+ * <p>
+ * Used for end-to-end interfaces.
+ *
+ * @param message Message to send as the response.
+ */
+ public Found(String message) {
+ super(message);
+ this.location = null;
+ }
+
+ /**
* Constructor using custom message.
* @param message Message to send as the response.
* @param location <code>Location</code> header value.
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/response/MovedPermanently.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/response/MovedPermanently.java
index c14889a..968e00a 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/response/MovedPermanently.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/response/MovedPermanently.java
@@ -48,6 +48,18 @@ public class MovedPermanently extends HttpResponse {
}
/**
+ * Constructor with no redirect.
+ * <p>
+ * Used for end-to-end interfaces.
+ *
+ * @param message Message to send as the response.
+ */
+ public MovedPermanently(String message) {
+ super(message);
+ this.location = null;
+ }
+
+ /**
* Constructor using custom message.
* @param message Message to send as the response.
* @param location <code>Location</code> header value.
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/response/PermanentRedirect.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/response/PermanentRedirect.java
index 5eec0a3..cfc5ff4 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/response/PermanentRedirect.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/response/PermanentRedirect.java
@@ -49,6 +49,18 @@ public class PermanentRedirect extends HttpResponse {
}
/**
+ * Constructor with no redirect.
+ * <p>
+ * Used for end-to-end interfaces.
+ *
+ * @param message Message to send as the response.
+ */
+ public PermanentRedirect(String message) {
+ super(message);
+ this.location = null;
+ }
+
+ /**
* Constructor using custom message.
* @param message Message to send as the response.
* @param location <code>Location</code> header value.
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/response/SeeOther.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/response/SeeOther.java
index f01ab5c..7cdc508 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/response/SeeOther.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/response/SeeOther.java
@@ -51,6 +51,18 @@ public class SeeOther extends HttpResponse {
}
/**
+ * Constructor with no redirect.
+ * <p>
+ * Used for end-to-end interfaces.
+ *
+ * @param message Message to send as the response.
+ */
+ public SeeOther(String message) {
+ super(message);
+ this.location = null;
+ }
+
+ /**
* Constructor using custom message.
*
* @param message Message to send as the response.
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/response/TemporaryRedirect.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/response/TemporaryRedirect.java
index 0ef69e7..221bf77 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/response/TemporaryRedirect.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/response/TemporaryRedirect.java
@@ -50,6 +50,18 @@ public class TemporaryRedirect extends HttpResponse {
}
/**
+ * Constructor with no redirect.
+ * <p>
+ * Used for end-to-end interfaces.
+ *
+ * @param message Message to send as the response.
+ */
+ public TemporaryRedirect(String message) {
+ super(message);
+ this.location = null;
+ }
+
+ /**
* Constructor using custom message.
* @param message Message to send as the response.
* @param location <code>Location</code> header value.
diff --git a/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/annotation/AnnotationInheritanceTest.java b/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/annotation/AnnotationInheritanceTest.java
index 3f53930..706f8ef 100644
--- a/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/annotation/AnnotationInheritanceTest.java
+++ b/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/annotation/AnnotationInheritanceTest.java
@@ -12,6 +12,11 @@
// ***************************************************************************************************************************
package org.apache.juneau.rest.annotation;
+import static org.apache.juneau.http.HttpMethodName.*;
+
+import org.apache.juneau.http.annotation.*;
+import org.apache.juneau.json.*;
+import org.apache.juneau.rest.mock.*;
import org.junit.*;
import org.junit.runners.*;
@@ -19,30 +24,55 @@ import org.junit.runners.*;
* Tests inheritance of annotations from interfaces.
*/
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@SuppressWarnings("javadoc")
public class AnnotationInheritanceTest {
//=================================================================================================================
// @Body on parameter
//=================================================================================================================
-// @RestResource(serializers=SimpleJsonSerializer.class, parsers=JsonParser.class, defaultAccept="text/json")
-// public static interface IA {
-// @RestMethod(name=PUT, path="/String")
-// public String a01(@Body String b);
-// }
-//
-// public static class A implements IA {
-//
-// @Override
-// public String a01(String b) {
-// return b;
-// }
-// }
-//
-// private static MockRest a = MockRest.create(A.class);
-//
-// @Test
-// public void a01a_onParameter_String() throws Exception {
-// // a.put("/String", "'foo'").json().execute().assertBody("'foo'");
-// }
+ @RestResource(serializers=SimpleJsonSerializer.class, parsers=JsonParser.class, defaultAccept="text/json")
+ public static interface IA {
+ @RestMethod(name=PUT, path="/a01")
+ public String a01(@Body String b);
+
+ @RestMethod(name=GET, path="/a02")
+ public String a02(@Query("foo") String b);
+
+ @RestMethod(name=GET, path="/a03")
+ public String a03(@Header("foo") String b);
+ }
+
+ public static class A implements IA {
+
+ @Override
+ public String a01(String b) {
+ return b;
+ }
+
+ @Override
+ public String a02(String b) {
+ return b;
+ }
+
+ @Override
+ public String a03(String b) {
+ return b;
+ }
+ }
+
+ private static MockRest a = MockRest.create(A.class);
+
+ @Test
+ public void a01_inherited_Body() throws Exception {
+ a.put("/a01", "'foo'").json().execute().assertBody("'foo'");
+ }
+ @Test
+ public void a02_inherited_Query() throws Exception {
+ a.get("/a02").query("foo", "bar").json().execute().assertBody("'bar'");
+ }
+ @Test
+ public void a03_inherited_Header() throws Exception {
+ a.get("/a03").header("foo", "bar").json().execute().assertBody("'bar'");
+ }
}