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 a447cb4 Tests a447cb4 is described below commit a447cb4a8d62b8b7a23b19714887b2f4adc36e2d Author: JamesBognar <jamesbog...@apache.org> AuthorDate: Sat May 12 12:08:45 2018 -0400 Tests --- .../org/apache/juneau/testutils/TestUtils.java | 6 + .../src/main/java/org/apache/juneau/ClassMeta.java | 74 +++ juneau-doc/src/main/javadoc/overview.html | 176 +++++- .../rest/test/DefaultContentTypesResource.java | 140 ----- .../apache/juneau/rest/test/ParamsResource.java | 2 +- .../java/org/apache/juneau/rest/test/Root.java | 1 - .../juneau/rest/test/DefaultContentTypesTest.java | 427 ------------- .../org/apache/juneau/rest/test/ParamsTest.java | 2 +- .../org/apache/juneau/rest/test/_TestSuite.java | 1 - .../apache/juneau/rest/BasicRestCallHandler.java | 3 + .../java/org/apache/juneau/rest/RequestBody.java | 29 +- .../java/org/apache/juneau/rest/RestContext.java | 12 +- .../org/apache/juneau/rest/RestParamDefaults.java | 9 - .../org/apache/juneau/rest/annotation/Body.java | 77 ++- .../juneau/rest/mock/MockServletRequest.java | 78 ++- .../juneau/rest/mock/MockServletResponse.java | 44 ++ .../juneau/rest/annotation/BodyAnnotationTest.java | 680 +++++++++++++++++++++ .../apache/juneau/rest/annotation/BodyTest.java | 533 ---------------- .../rest/annotation/DefaultContentTypesTest.java | 384 ++++++++++++ .../juneau/rest/annotation/StatusCodesTest.java | 48 ++ 20 files changed, 1565 insertions(+), 1161 deletions(-) diff --git a/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/testutils/TestUtils.java b/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/testutils/TestUtils.java index cb662e7..abdfced 100644 --- a/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/testutils/TestUtils.java +++ b/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/testutils/TestUtils.java @@ -474,6 +474,12 @@ public class TestUtils { if (! isEquals(expected, actual)) throw new ComparisonFailure(format(msg, args), toString(expected), toString(actual)); } + + public static final void assertContains(Object value, String...substrings) { + for (String substring : substrings) + if (! contains(toString(value), substring)) + throw new ComparisonFailure("Text did not contain expected substring.", toString(substring), toString(value)); + } /** * Creates a ClassMeta for the given types. diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ClassMeta.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ClassMeta.java index a3e6ddc..1913e26 100644 --- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ClassMeta.java +++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ClassMeta.java @@ -119,6 +119,9 @@ public final class ClassMeta<T> implements Type { private final BeanRegistry beanRegistry; // The bean registry of this class meta (if it has one). private final ClassMeta<?>[] args; // Arg types if this is an array of args. private final Object example; // Example object. + private final Transform<Reader,T> readerTransform; + private final Transform<InputStream,T> inputStreamTransform; + private final Transform<String,T> stringTransform; private ReadWriteLock lock = new ReentrantReadWriteLock(false); private Lock rLock = lock.readLock(), wLock = lock.writeLock(); @@ -198,6 +201,9 @@ public final class ClassMeta<T> implements Type { this.exampleField = builder.exampleField; this.example = builder.example; this.args = null; + this.readerTransform = builder.readerTransform; + this.inputStreamTransform = builder.inputStreamTransform; + this.stringTransform = builder.stringTransform; } finally { wLock.unlock(); } @@ -260,6 +266,9 @@ public final class ClassMeta<T> implements Type { this.exampleField = mainType.exampleField; this.example = mainType.example; this.args = null; + this.readerTransform = mainType.readerTransform; + this.inputStreamTransform = mainType.inputStreamTransform; + this.stringTransform = mainType.stringTransform; } /** @@ -309,6 +318,9 @@ public final class ClassMeta<T> implements Type { this.exampleMethod = null; this.exampleField = null; this.example = null; + this.readerTransform = null; + this.inputStreamTransform = null; + this.stringTransform = null; } @SuppressWarnings({"unchecked","rawtypes","hiding"}) @@ -361,6 +373,9 @@ public final class ClassMeta<T> implements Type { Method exampleMethod; Field exampleField; Object example; + Transform<Reader,T> readerTransform; + Transform<InputStream,T> inputStreamTransform; + Transform<String,T> stringTransform; ClassMetaBuilder(Class<T> innerClass, BeanContext beanContext, Class<? extends T> implClass, BeanFilter beanFilter, PojoSwap<T,?>[] pojoSwaps, PojoSwap<?,?>[] childPojoSwaps, Object example) { this.innerClass = innerClass; @@ -451,6 +466,7 @@ public final class ClassMeta<T> implements Type { } } } + // TODO - should use transforms for above code. // Special cases try { @@ -748,6 +764,10 @@ public final class ClassMeta<T> implements Type { } this.example = example; + + this.readerTransform = TransformCache.get(Reader.class, c); + this.inputStreamTransform = TransformCache.get(InputStream.class, c); + this.stringTransform = TransformCache.get(String.class, c); } private BeanFilter findBeanFilter() { @@ -2026,4 +2046,58 @@ public final class ClassMeta<T> implements Type { public int hashCode() { return super.hashCode(); } + + /** + * Returns <jk>true</jk> if this class has a transform associated with it that allows it to be created from a Reader. + * + * @return <jk>true</jk> if this class has a transform associated with it that allows it to be created from a Reader. + */ + public boolean hasReaderTransform() { + return readerTransform != null; + } + + /** + * Returns the transform for this class for creating instances from a Reader. + * + * @return The transform, or <jk>null</jk> if no such transform exists. + */ + public Transform<Reader,T> getReaderTransform() { + return readerTransform; + } + + /** + * Returns <jk>true</jk> if this class has a transform associated with it that allows it to be created from an InputStream. + * + * @return <jk>true</jk> if this class has a transform associated with it that allows it to be created from an InputStream. + */ + public boolean hasInputStreamTransform() { + return inputStreamTransform != null; + } + + /** + * Returns the transform for this class for creating instances from an InputStream. + * + * @return The transform, or <jk>null</jk> if no such transform exists. + */ + public Transform<InputStream,T> getInputStreamTransform() { + return inputStreamTransform; + } + + /** + * Returns <jk>true</jk> if this class has a transform associated with it that allows it to be created from a String. + * + * @return <jk>true</jk> if this class has a transform associated with it that allows it to be created from a String. + */ + public boolean hasStringTransform() { + return stringTransform != null; + } + + /** + * Returns the transform for this class for creating instances from a String. + * + * @return The transform, or <jk>null</jk> if no such transform exists. + */ + public Transform<String,T> getStringTransform() { + return stringTransform; + } } diff --git a/juneau-doc/src/main/javadoc/overview.html b/juneau-doc/src/main/javadoc/overview.html index 4d88d55..29d01ca 100644 --- a/juneau-doc/src/main/javadoc/overview.html +++ b/juneau-doc/src/main/javadoc/overview.html @@ -12279,40 +12279,172 @@ <h3 class='topic' onclick='toggle(this)'>7.7 - @Body</h3> <div class='topic'> <p> - The {@link org.apache.juneau.rest.annotation.Body @Body} annotation provides easy access to the HTTP body content as any <a class='doclink' href='#juneau-marshall.PojoCategories'>parsable</a> POJO type - <br>In the example below, we're POSTing beans. + The {@link org.apache.juneau.rest.annotation.Body @Body} annotation is used to identify POJOs to be used as the body of an HTTP request. </p> + <h5 class='figure'>Example:</h5> <p class='bcode w800'> - <jc>// Example POST of a bean</jc> - <ja>@RestMethod</ja>(name=<jsf>POST</jsf>, path=<js>"/"</js>) - <jk>public void</jk> doPost(<ja>@Body</ja> Person person) <jk>throws</jk> Exception { - <jc>// Do something with person.</jc> + <ja>@RestMethod</ja>(name=<jsf>POST</jsf>) + <jk>public void</jk> addPerson(<ja>@Body</ja> Person person) { + ... } </p> <p> - The HTTP body of a request can be retrieved as a parsed POJO using either the - {@link org.apache.juneau.rest.RestRequest#getBody()} method, or a parameter annotated with - {@link org.apache.juneau.rest.annotation.Body @Body}. + This is functionally equivalent to the following code... </p> <p class='bcode w800'> - <jc>// Equivalent method 1</jc> - <ja>@RestMethod</ja>(name=<jsf>POST</jsf>, path=<js>"/example1"</js>) - <jk>public void</jk> doPost1(<ja>@Body</ja> Person p) { - <jc>// Do something with p.</jc> + <ja>@RestMethod</ja>(name=<jsf>POST</jsf>) + <jk>public void</jk> addPerson(RestRequest req) { + Person person = req.getBody().asType(Person.<jk>class</jk>); + ... } - - <jc>// Equivalent method 2</jc> - <ja>@RestMethod</ja>(name=<jsf>POST</jsf>, path=<js>"/example2"</js>) - <jk>public void</jk> doPost2(RestRequest req) { - Person p = req.getBody).asType(Person.<jk>class</jk>); - <jc>// Do something with p.</jc> + </p> + <p> + This annotation can be applied at the following locations: + </p> + <ul class='spaced-list'> + <li> + Parameters on a {@link RestMethod @RestMethod}. + <li> + POJO classes. + </ul> + + <p> + Any of the following types can be used for the parameter or POJO class: + </p> + <ol class='spaced-list'> + <li> + {@link Reader} + <br><ja>@Body</ja> annotation is optional. + <br><code>Content-Type</code> is ignored. + <li> + {@link InputStream} + <br><ja>@Body</ja> annotation is optional. + <br><code>Content-Type</code> is ignored. + <li> + Any <a class='doclink' href='../../../../../overview-summary.html#juneau-marshall.PojoCategories'>parsable</a> POJO type. + <br><code>Content-Type</code> is required to identify correct parser. + <li> + Objects convertible from {@link Reader} by having one of the following non-deprecated methods: + <ul> + <li><code><jk>public</jk> T(Reader in) {...}</code> + <li><code><jk>public static</jk> T <jsm>create</jsm>(Reader in) {...}</code> + <li><code><jk>public static</jk> T <jsm>fromReader</jsm>(Reader in) {...}</code> + </ul> + <br><code>Content-Type</code> must not be present or match an existing parser so that it's not parsed as a POJO. + <li> + Objects convertible from {@link InputStream} by having one of the following non-deprecated methods: + <ul> + <li><code><jk>public</jk> T(InputStream in) {...}</code> + <li><code><jk>public static</jk> T <jsm>create</jsm>(InputStream in) {...}</code> + <li><code><jk>public static</jk> T <jsm>fromInputStream</jsm>(InputStream in) {...}</code> + </ul> + <br><code>Content-Type</code> must not be present or match an existing parser so that it's not parsed as a POJO. + <li> + Objects convertible from {@link String} (including <code>String</code> itself) by having one of the following non-deprecated methods: + <ul> + <li><code><jk>public</jk> T(String in) {...}</code> (e.g. {@link Integer}, {@link Boolean}) + <li><code><jk>public static</jk> T <jsm>create</jsm>(String in) {...}</code> + <li><code><jk>public static</jk> T <jsm>fromString</jsm>(String in) {...}</code> + <li><code><jk>public static</jk> T <jsm>fromValue</jsm>(String in) {...}</code> + <li><code><jk>public static</jk> T <jsm>valueOf</jsm>(String in) {...}</code> (e.g. enums) + <li><code><jk>public static</jk> T <jsm>parse</jsm>(String in) {...}</code> (e.g. {@link Level}) + <li><code><jk>public static</jk> T <jsm>parseString</jsm>(String in) {...}</code> + <li><code><jk>public static</jk> T <jsm>forName</jsm>(String in) {...}</code> (e.g. {@link Class}, {@link Charset}) + <li><code><jk>public static</jk> T <jsm>forString</jsm>(String in) {...}</code> + </ul> + <br><code>Content-Type</code> must not be present or match an existing parser so that it's not parsed as a POJO. + </ol> + <p> + Special note should be taken on using {@link String} as the body object: + </p> + <p class='bcode w800'> + <ja>@RestMethod</ja>(name=<jsf>POST</jsf>) + <jk>public void</jk> add(<ja>@Body</ja> String body) { + ... } </p> <p> - The Juneau framework will automatically determine the appropriate <l>Parser</l> to use based on the - <l>Content-Type</l> HTTP header. - <br>So the body content could be JSON or XML or any other supported parsing types. + In the case above, when the <code>Content-Type</code> header is present and matches a parser, the body will be parsed into a <code>String</code> using the matched parser + using rule #3 above. + <br>For example, passing in a body of <js>"'foo'"</js> with the header <code>Content-Type: application/json</code> will result in the body being populated with + <js>"foo"</js>. + </p> + <p> + When the <code>Content-Type</code> header is not present or match a parser, the body is pass as-is using rule #6 above. + <br>For example, passing in a body of <js>"'foo'"</js> with no <code>Content-Type</code> header will result in the body being populated with + <js>"'foo'"</js>. </p> + <p> + The <ja>@Body</ja> annotation can also be used on POJO classes used as parameters on a REST method. + <br>This can be useful if you're reusing the same POJOs often in your REST interface and don't want to redefine your Swagger on each method. + </p> + + <h5 class='figure'>Examples:</h5> + <p class='bcode w800'> + <ja>@Body</ja>(description=<js>"POJO convertible from String"</js>)</ja> + <jk>public static class</jk> A { + <jk>private</jk> String <jf>val</jf>; + <jk>public</jk> A(String val) { + <jk>this</jk>.<jf>val</jf> = val; + } + <jk>public</jk> String toString() { + <jk>return</jk> <jf>val</jf>; + } + } + + <ja>@RestMethod</ja>(name=<jsf>PUT</jsf>, path=<js>"/testPojoFromString"</js>) + <jk>public</jk> A testPojoFromString(A a) { + <jk>return</jk> a; + } + </p> + <p class='bcode w800'> + <ja>@Body</ja>(description=<js>"Bean POJO"</js>)</ja> + <jk>public static class</jk> B { + <jk>public</jk> String <jf>f1</jf>; + } + + <ja>@RestMethod</ja>(name=<jsf>PUT</jsf>, path=<js>"/testBean"</js>) + <jk>public</jk> B testBean(B b) { + <jk>return</jk> b; + } + </p> + <p class='bcode w800'> + <ja>@Body</ja>(description=<js>"Bean list"</js>)</ja> + <jk>public static class</jk> C extends LinkedList<B> {} + + <ja>@RestMethod</ja>(name=<jsf>PUT</jsf>, path=<js>"/testBeanList"</js>) + <jk>public</jk> C testString(C c) { + <jk>return</jk> c; + } + </p> + <p class='bcode w800'> + <ja>@Body</ja>(description=<js>"POJO convertible from input stream"</js>)</ja> + <jk>public static class</jk> D { + <jk>private</jk> String <jf>val</jf>; + <jk>public</jk> D(InputStream is) { + <jk>this</jk>.<jf>val</jf> = IOUtils.<jsm>read</jsm>(is); + } + <jk>public</jk> String toString() { + <jk>return</jk> <jf>val</jf>; + } + } + + <ja>@RestMethod</ja>(name=<jsf>PUT</jsf>, path=<js>"/testPojoFromInputStream"</js>) + <jk>public</jk> D testPojoFromInputStream(D d) { + <jk>return</jk> d; + } + </p> + + <h5 class='section'>Notes:</h5> + <ul class='spaced-list'> + <li> + Annotation parameter values will be aggregated when used on POJO parent and child classes. + <br>Values on child classes override values on parent classes. + <li> + Annotation parameter values will be aggregated when used on both POJOs and REST methods. + <br>Values on methods override values on POJO classes. + </ul> + <h5 class='section'>See Also:</h5> <ul> <li class='ja'>{@link org.apache.juneau.rest.annotation.Body} diff --git a/juneau-microservice/juneau-microservice-test/src/main/java/org/apache/juneau/rest/test/DefaultContentTypesResource.java b/juneau-microservice/juneau-microservice-test/src/main/java/org/apache/juneau/rest/test/DefaultContentTypesResource.java deleted file mode 100644 index c15a0d7..0000000 --- a/juneau-microservice/juneau-microservice-test/src/main/java/org/apache/juneau/rest/test/DefaultContentTypesResource.java +++ /dev/null @@ -1,140 +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.test; - -import static org.apache.juneau.http.HttpMethodName.*; - -import org.apache.juneau.*; -import org.apache.juneau.parser.*; -import org.apache.juneau.rest.*; -import org.apache.juneau.rest.annotation.*; -import org.apache.juneau.serializer.*; - -/** - * JUnit automated testcase resource. - */ -@RestResource( - path="/testDefaultContentTypes", - defaultRequestHeaders={" Accept : text/s2 "," Content-Type : text/p2 "}, - parsers={DefaultContentTypesResource.P1.class,DefaultContentTypesResource.P2.class}, serializers={DefaultContentTypesResource.S1.class,DefaultContentTypesResource.S2.class} -) -public class DefaultContentTypesResource extends RestServlet { - private static final long serialVersionUID = 1L; - - public static class P1 extends DummyParser { public P1(PropertyStore ps) {super(ps, "p1", "text/p1");}} - - public static class P2 extends DummyParser { public P2(PropertyStore ps) {super(ps, "p2", "text/p2");}} - - public static class P3 extends DummyParser { public P3(PropertyStore ps) {super(ps, "p3", "text/p3");}} - - public static class S1 extends DummySerializer { public S1(PropertyStore ps) {super(ps, "s1", "text/s1");}} - - public static class S2 extends DummySerializer { public S2(PropertyStore ps) {super(ps, "s2", "text/s2");}} - - public static class S3 extends DummySerializer { public S3(PropertyStore ps) {super(ps, "s3", "text/s3");}} - - /** - * Test that default Accept and Content-Type headers on servlet annotation are picked up. - */ - @RestMethod(name=PUT, path="/testDefaultHeadersOnServletAnnotation") - public String testDefaultHeadersOnServletAnnotation(@Body String in) { - return in; - } - - //==================================================================================================== - // Test that default Accept and Content-Type headers on servlet annotation are picked up - // when @RestMethod.parsers/serializers annotations are used. - //==================================================================================================== - @RestMethod(name=PUT, path="/testRestMethodParsersSerializers", parsers=P3.class, serializers=S3.class) - public String testRestMethodParsersSerializers(@Body String in) { - return in; - } - - //==================================================================================================== - // Test that default Accept and Content-Type headers on servlet annotation are picked up - // when @RestMethod.addParsers/addSerializers annotations are used. - //==================================================================================================== - @RestMethod(name=PUT, path="/testRestMethodAddParsersSerializers", parsers=P3.class, serializers=S3.class, inherit="SERIALIZERS,PARSERS") - public String testRestMethodAddParsersSerializers(@Body String in) { - return in; - } - - //==================================================================================================== - // Various Accept incantations. - //==================================================================================================== - @RestMethod(name=PUT, path="/testAccept") - public String testAccept(@Body String in) { - return in; - } - - //==================================================================================================== - // Test that default Accept and Content-Type headers on method annotation are picked up - // when @RestMethod.parsers/serializers annotations are used. - //==================================================================================================== - @RestMethod(name=PUT, path="/testRestMethodParserSerializerAnnotations", defaultRequestHeaders={"Accept: text/s3","Content-Type: text/p3"}, parsers=P3.class, serializers=S3.class) - public String testRestMethodParserSerializerAnnotations(@Body String in) { - return in; - } - - //==================================================================================================== - // Test that default Accept and Content-Type headers on method annotation are picked up - // when @RestMethod.addParsers/addSerializers annotations are used. - //==================================================================================================== - @RestMethod(name=PUT, path="/testRestMethodAddParsersSerializersAnnotations", defaultRequestHeaders={"Accept: text/s3","Content-Type: text/p3"}, parsers=P3.class, serializers=S3.class, inherit="SERIALIZERS,PARSERS") - public String testRestMethodAddParsersSerializersAnnotations(@Body String in) { - return in; - } - - public static class DummyParser extends ReaderParser { - - private String name; - - private DummyParser(PropertyStore ps, String name, String...consumes) { - super(ps, consumes); - this.name = name; - } - - @Override /* Parser */ - public ReaderParserSession createSession(ParserSessionArgs args) { - return new ReaderParserSession(args) { - - @Override /* ParserSession */ - @SuppressWarnings("unchecked") - protected <T> T doParse(ParserPipe pipe, ClassMeta<T> type) throws Exception { - return (T)name; - } - }; - } - } - - public static class DummySerializer extends WriterSerializer { - - private String name; - - private DummySerializer(PropertyStore ps, String name, String produces) { - super(ps, produces, null); - this.name = name; - } - - @Override /* Serializer */ - public WriterSerializerSession createSession(SerializerSessionArgs args) { - return new WriterSerializerSession(args) { - - @Override /* SerializerSession */ - protected void doSerialize(SerializerPipe out, Object o) throws Exception { - out.getWriter().write(name + "/" + o); - } - }; - } - } -} diff --git a/juneau-microservice/juneau-microservice-test/src/main/java/org/apache/juneau/rest/test/ParamsResource.java b/juneau-microservice/juneau-microservice-test/src/main/java/org/apache/juneau/rest/test/ParamsResource.java index 69fe0e0..e15ca28 100644 --- a/juneau-microservice/juneau-microservice-test/src/main/java/org/apache/juneau/rest/test/ParamsResource.java +++ b/juneau-microservice/juneau-microservice-test/src/main/java/org/apache/juneau/rest/test/ParamsResource.java @@ -401,7 +401,7 @@ public class ParamsResource extends BasicRestServlet { return t != null; } - @RestMethod(name=GET, path="/otherObjects/Parser") + @RestMethod(name=GET, path="/otherObjects/Parser",parsers={JsonParser.class}) public String testOtherParser(Parser t) { return t.getClass().getName(); } diff --git a/juneau-microservice/juneau-microservice-test/src/main/java/org/apache/juneau/rest/test/Root.java b/juneau-microservice/juneau-microservice-test/src/main/java/org/apache/juneau/rest/test/Root.java index 3b4973e..119996e 100644 --- a/juneau-microservice/juneau-microservice-test/src/main/java/org/apache/juneau/rest/test/Root.java +++ b/juneau-microservice/juneau-microservice-test/src/main/java/org/apache/juneau/rest/test/Root.java @@ -32,7 +32,6 @@ import org.apache.juneau.rest.helper.*; ClientFuturesResource.class, ClientVersionResource.class, ConfigResource.class, - DefaultContentTypesResource.class, ErrorConditionsResource.class, TransformsResource.class, FormDataResource.class, diff --git a/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/DefaultContentTypesTest.java b/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/DefaultContentTypesTest.java deleted file mode 100644 index 9ce9e19..0000000 --- a/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/DefaultContentTypesTest.java +++ /dev/null @@ -1,427 +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.test; - -import static javax.servlet.http.HttpServletResponse.*; -import static org.apache.juneau.microservice.testutils.TestUtils.*; -import static org.junit.Assert.*; - -import org.apache.juneau.rest.client.*; -import org.junit.*; - - -public class DefaultContentTypesTest extends RestTestcase { - - private static String URL = "/testDefaultContentTypes"; - private static boolean debug = false; - - //==================================================================================================== - // Test that default Accept and Content-Type headers on servlet annotation are picked up. - //==================================================================================================== - @Test - public void testDefaultHeadersOnServletAnnotation() throws Exception { - RestClient client = TestMicroservice.DEFAULT_CLIENT; - String r; - - String url = URL + "/testDefaultHeadersOnServletAnnotation"; - - r = client.doPut(url, "").accept("").contentType("").getResponseAsString(); - assertEquals("s2/p2", r); - - r = client.doPut(url, "").accept("text/s1").contentType("").getResponseAsString(); - assertEquals("s1/p2", r); - - r = client.doPut(url, "").accept("").contentType("text/p1").getResponseAsString(); - assertEquals("s2/p1", r); - - r = client.doPut(url, "").accept("text/s1").contentType("text/p1").getResponseAsString(); - assertEquals("s1/p1", r); - - r = client.doPut(url, "").accept("text/s2").contentType("").getResponseAsString(); - assertEquals("s2/p2", r); - - r = client.doPut(url, "").accept("").contentType("text/p2").getResponseAsString(); - assertEquals("s2/p2", r); - - r = client.doPut(url, "").accept("text/s2").contentType("text/p2").getResponseAsString(); - assertEquals("s2/p2", r); - - try { - r = client.doPut(url+"?noTrace=true", "").accept("text/s3").contentType("").getResponseAsString(); - fail("Exception expected"); - } catch (RestCallException e) { - checkErrorResponse(debug, e, SC_NOT_ACCEPTABLE, - "Unsupported media-type in request header 'Accept': 'text/s3'", - "Supported media-types: ['text/s1','text/s2']" - ); - } - - try { - r = client.doPut(url+"?noTrace=true", "").accept("").contentType("text/p3").getResponseAsString(); - fail("Exception expected"); - } catch (RestCallException e) { - checkErrorResponse(debug, e, SC_UNSUPPORTED_MEDIA_TYPE, - "Unsupported media-type in request header 'Content-Type': 'text/p3'", - "Supported media-types: ['text/p1','text/p2']" - ); - } - - try { - r = client.doPut(url+"?noTrace=true", "").accept("text/s3").contentType("text/p3").getResponseAsString(); - fail("Exception expected"); - } catch (RestCallException e) { - checkErrorResponse(debug, e, SC_UNSUPPORTED_MEDIA_TYPE, - "Unsupported media-type in request header 'Content-Type': 'text/p3'", - "Supported media-types: ['text/p1','text/p2']" - ); - } - } - - //==================================================================================================== - // Test that default Accept and Content-Type headers on servlet annotation are picked up - // when @RestMethod.parsers/serializers annotations are used. - //==================================================================================================== - @Test - public void testRestMethodParsersSerializers() throws Exception { - RestClient client = TestMicroservice.DEFAULT_CLIENT; - String r; - - String url = URL + "/testRestMethodParsersSerializers"; - - try { - r = client.doPut(url+"?noTrace=true", "").accept("").contentType("").getResponseAsString(); - fail("Exception expected"); - } catch (RestCallException e) { - checkErrorResponse(debug, e, SC_UNSUPPORTED_MEDIA_TYPE, - "Unsupported media-type in request header 'Content-Type': 'text/p2'", - "Supported media-types: ['text/p3']" - ); - } - - try { - r = client.doPut(url+"?noTrace=true", "").accept("text/s1").contentType("").getResponseAsString(); - fail("Exception expected"); - } catch (RestCallException e) { - checkErrorResponse(debug, e, SC_UNSUPPORTED_MEDIA_TYPE, - "Unsupported media-type in request header 'Content-Type': 'text/p2'", - "Supported media-types: ['text/p3']" - ); - } - - try { - r = client.doPut(url+"?noTrace=true", "").accept("").contentType("text/p1").getResponseAsString(); - fail("Exception expected"); - } catch (RestCallException e) { - checkErrorResponse(debug, e, SC_UNSUPPORTED_MEDIA_TYPE, - "Unsupported media-type in request header 'Content-Type': 'text/p1'", - "Supported media-types: ['text/p3']" - ); - } - - try { - r = client.doPut(url+"?noTrace=true", "").accept("text/s1").contentType("text/p1").getResponseAsString(); - fail("Exception expected"); - } catch (RestCallException e) { - checkErrorResponse(debug, e, SC_UNSUPPORTED_MEDIA_TYPE, - "Unsupported media-type in request header 'Content-Type': 'text/p1'", - "Supported media-types: ['text/p3']" - ); - } - - try { - r = client.doPut(url+"?noTrace=true", "").accept("text/s2").contentType("").getResponseAsString(); - fail("Exception expected"); - } catch (RestCallException e) { - checkErrorResponse(debug, e, SC_UNSUPPORTED_MEDIA_TYPE, - "Unsupported media-type in request header 'Content-Type': 'text/p2'", - "Supported media-types: ['text/p3']" - ); - } - - try { - r = client.doPut(url+"?noTrace=true", "").accept("").contentType("text/p2").getResponseAsString(); - fail("Exception expected"); - } catch (RestCallException e) { - checkErrorResponse(debug, e, SC_UNSUPPORTED_MEDIA_TYPE, - "Unsupported media-type in request header 'Content-Type': 'text/p2'", - "Supported media-types: ['text/p3']" - ); - } - - try { - r = client.doPut(url+"?noTrace=true", "").accept("text/s2").contentType("text/p2").getResponseAsString(); - fail("Exception expected"); - } catch (RestCallException e) { - checkErrorResponse(debug, e, SC_UNSUPPORTED_MEDIA_TYPE, - "Unsupported media-type in request header 'Content-Type': 'text/p2'", - "Supported media-types: ['text/p3']" - ); - } - - try { - r = client.doPut(url+"?noTrace=true", "").accept("text/s3").contentType("").getResponseAsString(); - fail("Exception expected"); - } catch (RestCallException e) { - checkErrorResponse(debug, e, SC_UNSUPPORTED_MEDIA_TYPE, - "Unsupported media-type in request header 'Content-Type': 'text/p2'", - "Supported media-types: ['text/p3']" - ); - } - - try { - r = client.doPut(url+"?noTrace=true", "").accept("").contentType("text/p3").getResponseAsString(); - fail("Exception expected"); - } catch (RestCallException e) { - checkErrorResponse(debug, e, SC_NOT_ACCEPTABLE, - "Unsupported media-type in request header 'Accept': 'text/s2'", - "Supported media-types: ['text/s3']" - ); - } - - r = client.doPut(url, "").accept("text/s3").contentType("text/p3").getResponseAsString(); - assertEquals("s3/p3", r); - } - - //==================================================================================================== - // Test that default Accept and Content-Type headers on servlet annotation are picked up - // when @RestMethod.addParsers/addSerializers annotations are used. - //==================================================================================================== - @Test - public void testRestMethodAddParsersSerializers() throws Exception { - RestClient client = TestMicroservice.DEFAULT_CLIENT; - String r; - - String url = URL + "/testRestMethodAddParsersSerializers"; - - r = client.doPut(url, "").accept("").contentType("").getResponseAsString(); - assertEquals("s2/p2", r); - - r = client.doPut(url, "").accept("text/s1").contentType("").getResponseAsString(); - assertEquals("s1/p2", r); - - r = client.doPut(url, "").accept("").contentType("text/p1").getResponseAsString(); - assertEquals("s2/p1", r); - - r = client.doPut(url, "").accept("text/s1").contentType("text/p1").getResponseAsString(); - assertEquals("s1/p1", r); - - r = client.doPut(url, "").accept("text/s2").contentType("").getResponseAsString(); - assertEquals("s2/p2", r); - - r = client.doPut(url, "").accept("").contentType("text/p2").getResponseAsString(); - assertEquals("s2/p2", r); - - r = client.doPut(url, "").accept("text/s2").contentType("text/p2").getResponseAsString(); - assertEquals("s2/p2", r); - - r = client.doPut(url, "").accept("text/s3").contentType("").getResponseAsString(); - assertEquals("s3/p2", r); - - r = client.doPut(url, "").accept("").contentType("text/p3").getResponseAsString(); - assertEquals("s2/p3", r); - - r = client.doPut(url, "").accept("text/s3").contentType("text/p3").getResponseAsString(); - assertEquals("s3/p3", r); - - try { - r = client.doPut(url+"?noTrace=true", "").accept("").contentType("text/p4").getResponseAsString(); - fail("Exception expected"); - } catch (RestCallException e) { - // Note that parsers defined on method are listed before parsers defined on class. - checkErrorResponse(debug, e, SC_UNSUPPORTED_MEDIA_TYPE, - "Unsupported media-type in request header 'Content-Type': 'text/p4'", - "Supported media-types: ['text/p3','text/p1','text/p2']" - ); - } - - try { - r = client.doPut(url+"?noTrace=true", "").accept("text/s4").contentType("").getResponseAsString(); - fail("Exception expected"); - } catch (RestCallException e) { - // Note that serializers defined on method are listed before serializers defined on class. - checkErrorResponse(debug, e, SC_NOT_ACCEPTABLE, - "Unsupported media-type in request header 'Accept': 'text/s4'", - "Supported media-types: ['text/s3','text/s1','text/s2']" - ); - } - } - - //==================================================================================================== - // Various Accept incantations. - //==================================================================================================== - @Test - public void testAccept() throws Exception { - RestClient client = TestMicroservice.client().contentType("text/p1").build(); - String r; - - String url = URL + "/testAccept"; - - // "*/*" should match the first serializer, not the default serializer. - r = client.doPut(url, "").accept("*/*").getResponseAsString(); - assertEquals("s1/p1", r); - - // "text/*" should match the first serializer, not the default serializer. - r = client.doPut(url, "").accept("text/*").getResponseAsString(); - assertEquals("s1/p1", r); - - try { - r = client.doPut(url+"?noTrace=true", "").accept("bad/*").getResponseAsString(); - fail("Exception expected"); - } catch (RestCallException e) { - checkErrorResponse(debug, e, SC_NOT_ACCEPTABLE, - "Unsupported media-type in request header 'Accept': 'bad/*'", - "Supported media-types: ['text/s1','text/s2']" - ); - } - - r = client.doPut(url, "").accept("bad/*,text/*").getResponseAsString(); - assertEquals("s1/p1", r); - - r = client.doPut(url, "").accept("text/*,bad/*").getResponseAsString(); - assertEquals("s1/p1", r); - - r = client.doPut(url, "").accept("text/s1;q=0.5,text/s2").getResponseAsString(); - assertEquals("s2/p1", r); - - r = client.doPut(url, "").accept("text/s1,text/s2;q=0.5").getResponseAsString(); - assertEquals("s1/p1", r); - - client.closeQuietly(); - } - - //==================================================================================================== - // Test that default Accept and Content-Type headers on method annotation are picked up - // when @RestMethod.parsers/serializers annotations are used. - //==================================================================================================== - @Test - public void testRestMethodParserSerializerAnnotations() throws Exception { - RestClient client = TestMicroservice.DEFAULT_CLIENT; - String r; - - String url = URL + "/testRestMethodParserSerializerAnnotations"; - - r = client.doPut(url, "").accept("").contentType("").getResponseAsString(); - assertEquals("s3/p3", r); - - try { - r = client.doPut(url+"?noTrace=true", "").accept("text/s1").contentType("").getResponseAsString(); - fail("Exception expected"); - } catch (RestCallException e) { - checkErrorResponse(debug, e, SC_NOT_ACCEPTABLE, - "Unsupported media-type in request header 'Accept': 'text/s1'", - "Supported media-types: ['text/s3']" - ); - } - - try { - r = client.doPut(url+"?noTrace=true", "").accept("").contentType("text/p1").getResponseAsString(); - fail("Exception expected"); - } catch (RestCallException e) { - checkErrorResponse(debug, e, SC_UNSUPPORTED_MEDIA_TYPE, - "Unsupported media-type in request header 'Content-Type': 'text/p1'", - "Supported media-types: ['text/p3']" - ); - } - - try { - r = client.doPut(url+"?noTrace=true", "").accept("text/s1").contentType("text/p1").getResponseAsString(); - fail("Exception expected"); - } catch (RestCallException e) { - checkErrorResponse(debug, e, SC_UNSUPPORTED_MEDIA_TYPE, - "Unsupported media-type in request header 'Content-Type': 'text/p1'", - "Supported media-types: ['text/p3']" - ); - } - - try { - r = client.doPut(url+"?noTrace=true", "").accept("text/s2").contentType("").getResponseAsString(); - fail("Exception expected"); - } catch (RestCallException e) { - checkErrorResponse(debug, e, SC_NOT_ACCEPTABLE, - "Unsupported media-type in request header 'Accept': 'text/s2'", - "Supported media-types: ['text/s3']" - ); - } - - try { - r = client.doPut(url+"?noTrace=true", "").accept("").contentType("text/p2").getResponseAsString(); - fail("Exception expected"); - } catch (RestCallException e) { - checkErrorResponse(debug, e, SC_UNSUPPORTED_MEDIA_TYPE, - "Unsupported media-type in request header 'Content-Type': 'text/p2'", - "Supported media-types: ['text/p3']" - ); - } - - try { - r = client.doPut(url+"?noTrace=true", "").accept("text/s2").contentType("text/p2").getResponseAsString(); - fail("Exception expected"); - } catch (RestCallException e) { - checkErrorResponse(debug, e, SC_UNSUPPORTED_MEDIA_TYPE, - "Unsupported media-type in request header 'Content-Type': 'text/p2'", - "Supported media-types: ['text/p3']" - ); - } - - r = client.doPut(url, "").accept("text/s3").contentType("").getResponseAsString(); - assertEquals("s3/p3", r); - - r = client.doPut(url, "").accept("").contentType("text/p3").getResponseAsString(); - assertEquals("s3/p3", r); - - r = client.doPut(url, "").accept("text/s3").contentType("text/p3").getResponseAsString(); - assertEquals("s3/p3", r); - } - - //==================================================================================================== - // Test that default Accept and Content-Type headers on method annotation are picked up - // when @RestMethod.addParsers/addSerializers annotations are used. - //==================================================================================================== - @Test - public void testRestMethodAddParsersSerializersAnnotations() throws Exception { - RestClient client = TestMicroservice.DEFAULT_CLIENT; - String r; - - String url = URL + "/testRestMethodAddParsersSerializersAnnotations"; - - r = client.doPut(url, "").accept("").contentType("").getResponseAsString(); - assertEquals("s3/p3", r); - - r = client.doPut(url, "").accept("text/s1").contentType("").getResponseAsString(); - assertEquals("s1/p3", r); - - r = client.doPut(url, "").accept("").contentType("text/p1").getResponseAsString(); - assertEquals("s3/p1", r); - - r = client.doPut(url, "").accept("text/s1").contentType("text/p1").getResponseAsString(); - assertEquals("s1/p1", r); - - r = client.doPut(url, "").accept("text/s2").contentType("").getResponseAsString(); - assertEquals("s2/p3", r); - - r = client.doPut(url, "").accept("").contentType("text/p2").getResponseAsString(); - assertEquals("s3/p2", r); - - r = client.doPut(url, "").accept("text/s2").contentType("text/p2").getResponseAsString(); - assertEquals("s2/p2", r); - - r = client.doPut(url, "").accept("text/s3").contentType("").getResponseAsString(); - assertEquals("s3/p3", r); - - r = client.doPut(url, "").accept("").contentType("text/p3").getResponseAsString(); - assertEquals("s3/p3", r); - - r = client.doPut(url, "").accept("text/s3").contentType("text/p3").getResponseAsString(); - assertEquals("s3/p3", r); - } -} diff --git a/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/ParamsTest.java b/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/ParamsTest.java index bd7bf6f..72cd276 100644 --- a/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/ParamsTest.java +++ b/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/ParamsTest.java @@ -811,7 +811,7 @@ public class ParamsTest extends RestTestcase { @Test public void testOtherParser() throws Exception { - String r = CLIENT.doGet(URL + "/otherObjects/Parser").getResponseAsString(); + String r = CLIENT.doGet(URL + "/otherObjects/Parser").contentType("application/json").getResponseAsString(); assertEquals("\"org.apache.juneau.json.JsonParser\"", r); } diff --git a/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/_TestSuite.java b/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/_TestSuite.java index 6841615..724cf8d 100644 --- a/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/_TestSuite.java +++ b/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/_TestSuite.java @@ -31,7 +31,6 @@ import org.junit.runners.Suite.*; ClientFuturesTest.class, ClientVersionTest.class, ConfigTest.class, - DefaultContentTypesTest.class, ErrorConditionsTest.class, FormDataTest.class, GroupsTest.class, diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestCallHandler.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestCallHandler.java index b0d007b..6ff40d7 100644 --- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestCallHandler.java +++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestCallHandler.java @@ -167,6 +167,9 @@ public class BasicRestCallHandler implements RestCallHandler { // If not invoked above, see if it's an OPTIONs request if (rc != SC_OK) handleNotFound(rc, req, res); + + if (res.getStatus() == 0) + res.setStatus(rc); } if (res.hasOutput()) { diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestBody.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestBody.java index b5e7c4a..0df1e58 100644 --- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestBody.java +++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestBody.java @@ -343,16 +343,19 @@ public class RequestBody { public ParserMatch getParserMatch() { if (mediaType != null && parser != null) return new ParserMatch(mediaType, parser); + MediaType mt = getMediaType(); + return mt == null ? null : parsers.getParserMatch(mt); + } + + private MediaType getMediaType() { + if (mediaType != null) + return mediaType; MediaType mediaType = headers.getContentType(); - if (isEmpty(mediaType)) { - if (body != null) - mediaType = MediaType.UON; - else - mediaType = MediaType.JSON; - } - return parsers.getParserMatch(mediaType); + if (mediaType == null && body != null) + return MediaType.UON; + return mediaType; } - + /** * Returns the parser matching the request <code>Content-Type</code> header. * @@ -423,7 +426,17 @@ public class RequestBody { ); } } + + if (cm.hasReaderTransform()) + return cm.getReaderTransform().transform(getReader()); + if (cm.hasInputStreamTransform()) + return cm.getInputStreamTransform().transform(getInputStream()); + + MediaType mt = getMediaType(); + if ((isEmpty(mt) || mt.toString().equals("text/plain")) && cm.hasStringTransform()) + return cm.getStringTransform().transform(asString()); + throw new UnsupportedMediaType( "Unsupported media-type in request header ''Content-Type'': ''{0}''\n\tSupported media-types: {1}", headers.getContentType(), req.getParsers().getSupportedMediaTypes() 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 097138a..abc0529 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 @@ -3029,10 +3029,16 @@ public final class RestContext extends BeanContext { try { // Parse the args and invoke the method. Parser p = req.getBody().getParser(); - try (Closeable in = p.isReaderParser() ? req.getReader() : req.getInputStream()) { - Object output = m.invoke(o, p.parseArgs(in, m.getGenericParameterTypes())); - res.setOutput(output); + Object[] args = null; + if (m.getGenericParameterTypes().length == 0) + args = new Object[0]; + else { + try (Closeable in = p.isReaderParser() ? req.getReader() : req.getInputStream()) { + args = p.parseArgs(in, m.getGenericParameterTypes()); + } } + Object output = m.invoke(o, args); + res.setOutput(output); return SC_OK; } catch (Exception e) { throw new InternalServerError(e); diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestParamDefaults.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestParamDefaults.java index a0a33c0..e55623a 100644 --- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestParamDefaults.java +++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestParamDefaults.java @@ -589,21 +589,12 @@ class RestParamDefaults { static final class BodyObject extends RestMethodParam { - private final Transform<Reader,?> readerTransform; - private final Transform<InputStream,?> inputStreamTransform; - protected BodyObject(Method method, Body a, Type type, RestMethodParam existing) { super(BODY, method, null, type, getMetaData(a, castOrNull(existing, BodyObject.class))); - readerTransform = TransformCache.get(Reader.class, getTypeClass()); - inputStreamTransform = TransformCache.get(InputStream.class, getTypeClass()); } @Override /* RestMethodParam */ public Object resolve(RestRequest req, RestResponse res) throws Exception { - if (readerTransform != null) - return readerTransform.transform(req.getReader()); // Also passes Reader through. - if (inputStreamTransform != null) - return inputStreamTransform.transform(req.getInputStream()); // Also passes InputStream through. return req.getBody().asType(type); } diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/Body.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/Body.java index 085fe31..3d6beb3 100644 --- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/Body.java +++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/Body.java @@ -17,15 +17,19 @@ import static java.lang.annotation.RetentionPolicy.*; import java.io.*; import java.lang.annotation.*; +import java.nio.charset.*; +import java.util.logging.*; import org.apache.juneau.rest.*; /** - * Annotation that can be applied to a parameter of a {@link RestMethod @RestMethod} annotated method or POJO class to identify it as the HTTP - * request body converted to a POJO. + * REST request body annotation. + * + * <p> + * Identifies a POJO to be used as the body of an HTTP request. * * <h5 class='section'>Example:</h5> - * <p class='bcode'> + * <p class='bcode w800'> * <ja>@RestMethod</ja>(name=<jsf>POST</jsf>) * <jk>public void</jk> addPerson(<ja>@Body</ja> Person person) { * ... @@ -34,36 +38,81 @@ import org.apache.juneau.rest.*; * * <p> * This is functionally equivalent to the following code... - * <p class='bcode'> + * <p class='bcode w800'> * <ja>@RestMethod</ja>(name=<jsf>POST</jsf>) - * <jk>public void</jk> addPerson(RestRequest req, RestResponse res) { + * <jk>public void</jk> addPerson(RestRequest req) { * Person person = req.getBody().asType(Person.<jk>class</jk>); * ... * } * </p> * + * <p> + * This annotation can be applied to the following: + * <ul class='spaced-list'> + * <li> + * Parameters on a {@link RestMethod @RestMethod}. + * <li> + * POJO classes. + * </ul> + * + * <p> * Any of the following types can be used for the parameter: - * <ul> - * <li>{@link Reader} - * <li>{@link InputStream} - * <li>Primitives (e.g. <code>String</code>, <jk>int</jk>, <jk>boolean</jk>, etc...) - * <li>Beans - * <li>Maps, collections, or arrays of beans or primitives. - * <li>Any object convertible from a {@link Reader} by having one of the following methods: + * <ul class='spaced-list'> + * <li> + * {@link Reader} + * <br><ja>@Body</ja> annotation is optional. + * <br><code>Content-Type</code> is always ignored. + * <li> + * {@link InputStream} + * <br><ja>@Body</ja> annotation is optional. + * <br><code>Content-Type</code> is always ignored. + * <li> + * Any <a class='doclink' href='../../../../../overview-summary.html#juneau-marshall.PojoCategories'>parsable</a> POJO type. + * <br><code>Content-Type</code> is required to identify correct parser. + * <li> + * Objects convertible from {@link Reader} by having one of the following non-deprecated methods: * <ul> * <li><code><jk>public</jk> T(Reader in) {...}</code> * <li><code><jk>public static</jk> T <jsm>create</jsm>(Reader in) {...}</code> * <li><code><jk>public static</jk> T <jsm>fromReader</jsm>(Reader in) {...}</code> * </ul> - * </ul> - * <li>Any object convertible from an {@link InputStream} by having one of the following methods: + * <br><code>Content-Type</code> must not be present or match an existing parser so that it's not parsed as a POJO. + * <li> + * Objects convertible from {@link InputStream} by having one of the following non-deprecated methods: * <ul> * <li><code><jk>public</jk> T(InputStream in) {...}</code> * <li><code><jk>public static</jk> T <jsm>create</jsm>(InputStream in) {...}</code> * <li><code><jk>public static</jk> T <jsm>fromInputStream</jsm>(InputStream in) {...}</code> * </ul> + * <br><code>Content-Type</code> must not be present or match an existing parser so that it's not parsed as a POJO. + * <li> + * Objects convertible from {@link String} (including <code>String</code> itself) by having one of the following non-deprecated methods: + * <ul> + * <li><code><jk>public</jk> T(String in) {...}</code> (e.g. {@link Integer}, {@link Boolean}) + * <li><code><jk>public static</jk> T <jsm>create</jsm>(String in) {...}</code> + * <li><code><jk>public static</jk> T <jsm>fromString</jsm>(String in) {...}</code> + * <li><code><jk>public static</jk> T <jsm>fromValue</jsm>(String in) {...}</code> + * <li><code><jk>public static</jk> T <jsm>valueOf</jsm>(String in) {...}</code> (e.g. enums) + * <li><code><jk>public static</jk> T <jsm>parse</jsm>(String in) {...}</code> (e.g. {@link Level}) + * <li><code><jk>public static</jk> T <jsm>parseString</jsm>(String in) {...}</code> + * <li><code><jk>public static</jk> T <jsm>forName</jsm>(String in) {...}</code> (e.g. {@link Class}, {@link Charset}) + * <li><code><jk>public static</jk> T <jsm>forString</jsm>(String in) {...}</code> + * </ul> + * <br><code>Content-Type</code> must not be present or match an existing parser so that it's not parsed as a POJO. + * </ul> + * + * + * <h5 class='section'>Notes:</h5> + * <ul class='spaced-list'> + * <li> + * Annotation parameter values will be aggregated when used on POJO parent and child classes. + * <br>Values on child classes override values on parent classes. + * <li> + * Annotation parameter values will be aggregated when used on both POJOs and REST methods. + * <br>Values on methods override values on POJO classes. * </ul> * + * * <h5 class='section'>See Also:</h5> * <ul> * <li class='link'><a class="doclink" href="../../../../../overview-summary.html#juneau-rest-server.Body">Overview > juneau-rest-server > @Body</a> diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/mock/MockServletRequest.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/mock/MockServletRequest.java index 142b3a2..5369758 100644 --- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/mock/MockServletRequest.java +++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/mock/MockServletRequest.java @@ -84,7 +84,6 @@ public class MockServletRequest implements HttpServletRequest { */ public static MockServletRequest create() { MockServletRequest r = new MockServletRequest(); - r.header("Accept", "text/json+simple").header("Content-Type", "text/json"); return r; } @@ -110,6 +109,78 @@ public class MockServletRequest implements HttpServletRequest { } /** + * Convenience method for setting <code>Accept</code> and <code>Content-Type</code> headers to <js>"application/json"</js>. + * + * @return This object (for method chaining). + */ + public MockServletRequest json() { + return header("Accept", "application/json").header("Content-Type", "application/json"); + } + + /** + * Convenience method for setting <code>Accept</code> and <code>Content-Type</code> headers to <js>"text/xml"</js>. + * + * @return This object (for method chaining). + */ + public MockServletRequest xml() { + return header("Accept", "text/xml").header("Content-Type", "text/xml"); + } + + /** + * Convenience method for setting <code>Accept</code> and <code>Content-Type</code> headers to <js>"text/html"</js>. + * + * @return This object (for method chaining). + */ + public MockServletRequest html() { + return header("Accept", "text/html").header("Content-Type", "text/html"); + } + + /** + * Convenience method for setting <code>Accept</code> and <code>Content-Type</code> headers to <js>"text/plain"</js>. + * + * @return This object (for method chaining). + */ + public MockServletRequest plainText() { + return header("Accept", "text/plain").header("Content-Type", "text/plain"); + } + + /** + * Convenience method for setting <code>Accept</code> and <code>Content-Type</code> headers to <js>"octal/msgpack"</js>. + * + * @return This object (for method chaining). + */ + public MockServletRequest msgpack() { + return header("Accept", "octal/msgpack").header("Content-Type", "octal/msgpack"); + } + + /** + * Convenience method for setting <code>Accept</code> and <code>Content-Type</code> headers to <js>"text/uon"</js>. + * + * @return This object (for method chaining). + */ + public MockServletRequest uon() { + return header("Accept", "text/uon").header("Content-Type", "text/uon"); + } + + /** + * Convenience method for setting <code>Accept</code> and <code>Content-Type</code> headers to <js>"application/x-www-form-urlencoded"</js>. + * + * @return This object (for method chaining). + */ + public MockServletRequest urlEnc() { + return header("Accept", "application/x-www-form-urlencoded").header("Content-Type", "application/x-www-form-urlencoded"); + } + + /** + * Convenience method for setting <code>Accept</code> and <code>Content-Type</code> headers to <js>"text/yaml"</js>. + * + * @return This object (for method chaining). + */ + public MockServletRequest yaml() { + return header("Accept", "text/yaml").header("Content-Type", "text/yaml"); + } + + /** * Fluent setter. * * @param uri The URI of the request. @@ -140,6 +211,11 @@ public class MockServletRequest implements HttpServletRequest { public MockServletResponse execute() throws Exception { MockServletResponse res = MockServletResponse.create(); restContext.getCallHandler().service(this, res); + + // If the status isn't set, something's broken. + if (res.getStatus() == 0) + throw new RuntimeException("Response status was 0."); + return res; } diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/mock/MockServletResponse.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/mock/MockServletResponse.java index 0e5efb2..f9845c6 100644 --- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/mock/MockServletResponse.java +++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/mock/MockServletResponse.java @@ -12,7 +12,10 @@ // *************************************************************************************************************************** package org.apache.juneau.rest.mock; +import static org.apache.juneau.internal.StringUtils.*; + import java.io.*; +import java.text.*; import java.util.*; import javax.servlet.*; @@ -266,4 +269,45 @@ public class MockServletResponse implements HttpServletResponse { throw new RuntimeException(e); } } + + /** + * Throws an {@link AssertionError} if the response status does not match the expected status. + * + * @param status The expected status. + * @return This object (for method chaining). + * @throws AssertionError Thrown if status does not match. + */ + public MockServletResponse assertStatus(int status) throws AssertionError { + if (getStatus() != status) + throw new AssertionError(MessageFormat.format("Response did not have the expected status. expected=[{0}], actual=[{1}]", status, getStatus())); + return this; + } + + /** + * Throws an {@link AssertionError} if the response body does not contain all of the expected substrings. + * + * @param substrings The expected substrings. + * @return This object (for method chaining). + * @throws AssertionError Thrown if the body does not contain one or more of the expected substrings. + */ + public MockServletResponse assertBodyContains(String...substrings) throws AssertionError { + String text = getBodyAsString(); + for (String substring : substrings) + if (! contains(text, substring)) + throw new AssertionError(MessageFormat.format("Response did not have the expected substring. expected=[{0}], body=[{1}]", substring, text)); + return this; + } + + /** + * Throws an {@link AssertionError} if the response body does not contain the expected text. + * + * @param text The expected text of the body. + * @return This object (for method chaining). + * @throws AssertionError Thrown if the body does not contain the expected text. + */ + public MockServletResponse assertBody(String text) throws AssertionError { + if (! StringUtils.isEquals(text, getBodyAsString())) + throw new AssertionError(MessageFormat.format("Response did not have the expected text. expected=[{0}], actual=[{1}]", text, getBodyAsString())); + return this; + } } diff --git a/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/annotation/BodyAnnotationTest.java b/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/annotation/BodyAnnotationTest.java new file mode 100644 index 0000000..8836379 --- /dev/null +++ b/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/annotation/BodyAnnotationTest.java @@ -0,0 +1,680 @@ +// *************************************************************************************************************************** +// * 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.annotation; + +import static org.apache.juneau.http.HttpMethodName.*; + +import java.io.*; +import java.util.*; + +import org.apache.juneau.internal.*; +import org.apache.juneau.json.*; +import org.apache.juneau.rest.mock.*; +import org.apache.juneau.rest.testutils.*; +import org.apache.juneau.uon.*; +import org.junit.*; +import org.junit.runners.*; + +/** + * Tests the {@link Body} annotation. + */ +@SuppressWarnings({"javadoc","serial"}) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class BodyAnnotationTest { + + //================================================================================================================= + // @Body on parameter + //================================================================================================================= + + @RestResource(serializers=JsonSerializer.Simple.class, parsers=JsonParser.class) + public static class A { + + @RestMethod(name=PUT, path="/String") + public String a01(@Body String b) { + return b; + } + + @RestMethod(name=PUT, path="/Integer") + public Integer a02(@Body Integer b) { + return b; + } + + @RestMethod(name=PUT, path="/int") + public Integer a03(@Body int b) { + return b; + } + + @RestMethod(name=PUT, path="/Boolean") + public Boolean a04(@Body Boolean b) { + return b; + } + + @RestMethod(name=PUT, path="/boolean") + public Boolean a05(@Body boolean b) { + return b; + } + + @RestMethod(name=PUT, path="/float") + public float a06(@Body float f) { + return f; + } + + @RestMethod(name=PUT, path="/Float") + public Float a07(@Body Float f) { + return f; + } + + @RestMethod(name=PUT, path="/Map") + public TreeMap<String,Integer> a08(@Body TreeMap<String,Integer> m) { + return m; + } + + @RestMethod(name=PUT, path="/enum") + public TestEnum a09(@Body TestEnum e) { + return e; + } + + public static class A11 { + public String f1; + } + + @RestMethod(name=PUT, path="/Bean") + public A11 a11(@Body A11 b) { + return b; + } + + @RestMethod(name=PUT, path="/InputStream") + public String a12(@Body InputStream b) throws Exception { + return IOUtils.read(b); + } + + @RestMethod(name=PUT, path="/Reader") + public String a13(@Body Reader b) throws Exception { + return IOUtils.read(b); + } + + @RestMethod(name=PUT, path="/InputStreamTransform") + public A14 a14(@Body A14 b) throws Exception { + return b; + } + public static class A14 { + String s; + public A14(InputStream in) throws Exception { this.s = IOUtils.read(in); } + @Override public String toString() { return s; } + } + + @RestMethod(name=PUT, path="/ReaderTransform") + public A15 a15(@Body A15 b) throws Exception { + return b; + } + public static class A15 { + private String s; + public A15(Reader in) throws Exception { this.s = IOUtils.read(in); } + @Override public String toString() { return s; } + } + + @RestMethod(name=PUT, path="/StringTransform") + public A16 a16(@Body A16 b) throws Exception { return b; } + public static class A16 { + private String s; + public A16(String s) throws Exception { this.s = s; } + @Override public String toString() { return s; } + } + + } + + private static MockRest a = MockRest.create(A.class); + + @Test + public void a01a_onParameter_String() throws Exception { + a.request("PUT", "/String").body("'foo'").json().execute().assertBody("'foo'"); + } + @Test + public void a01b_onParameter_String_noContentType() throws Exception { + // If no Content-Type specified, should be treated as plain-text. + a.request("PUT", "/String").body("'foo'").execute().assertBody("'\\'foo\\''"); + } + @Test + public void a01c_onParameter_String_noContentType_other() throws Exception { + // If Content-Type not matched, should be treated as plain-text. + a.request("PUT", "/String").body("'foo'").contentType("").execute().assertBody("'\\'foo\\''"); + a.request("PUT", "/String").body("'foo'").contentType("text/plain").execute().assertBody("'\\'foo\\''"); + } + @Test + public void a02a_onParameter_Integer() throws Exception { + a.request("PUT", "/Integer").body("123").json().execute().assertBody("123"); + } + @Test + public void a02b_onParameter_Integer_noContentType() throws Exception { + // Integer takes in a String arg, so it can be parsed without Content-Type. + a.request("PUT", "/Integer").body("123").execute().assertBody("123"); + } + @Test + public void a03a_onParameter_int() throws Exception { + a.request("PUT", "/int").body("123").json().execute().assertBody("123"); + } + @Test + public void a03b_onParameter_int_noContentType() throws Exception { + a.request("PUT", "/int?noTrace=true").body("123").execute().assertBodyContains("Unsupported Media Type"); + } + @Test + public void a04a_onParameter_Boolean() throws Exception { + a.request("PUT", "/Boolean").body("true").json().execute().assertBody("true"); + } + @Test + public void a04b_onParameter_Boolean_noContentType() throws Exception { + // Boolean takes in a String arg, so it can be parsed without Content-Type. + a.request("PUT", "/Boolean").body("true").execute().assertBody("true"); + } + @Test + public void a05a_onParameter_boolean() throws Exception { + a.request("PUT", "/boolean").body("true").json().execute().assertBody("true"); + } + @Test + public void a05b_onParameter_boolean_noContentType() throws Exception { + a.request("PUT", "/boolean?noTrace=true").body("true").execute().assertBodyContains("Unsupported Media Type"); + } + @Test + public void a06a_onParameter_float() throws Exception { + a.request("PUT", "/float").body("1.23").json().execute().assertBody("1.23"); + } + @Test + public void a06b_onParameter_float_noContentType() throws Exception { + a.request("PUT", "/float?noTrace=true").body("1.23").execute().assertBodyContains("Unsupported Media Type"); + } + @Test + public void a07a_onParameter_Float() throws Exception { + a.request("PUT", "/Float").body("1.23").json().execute().assertBody("1.23"); + } + @Test + public void a07b_onParameter_Float_noContentType() throws Exception { + // Float takes in a String arg, so it can be parsed without Content-Type. + a.request("PUT", "/Float").body("1.23").execute().assertBody("1.23"); + } + @Test + public void a08a_onParameter_Map() throws Exception { + a.request("PUT", "/Map").body("{foo:123}").json().execute().assertBody("{foo:123}"); + } + @Test + public void a08b_onParameter_Map_noContentType() throws Exception { + a.request("PUT", "/Map?noTrace=true").body("{foo:123}").execute().assertBodyContains("Unsupported Media Type"); + } + @Test + public void a09a_onParameter_enum() throws Exception { + a.request("PUT", "/enum").body("'ONE'").json().execute().assertBody("'ONE'"); + } + @Test + public void a09b_onParameter_enum_noContentType() throws Exception { + a.request("PUT", "/enum").body("ONE").execute().assertBody("'ONE'"); + } + @Test + public void a11a_onParameter_Bean() throws Exception { + a.request("PUT", "/Bean").body("{f1:'a'}").json().execute().assertBody("{f1:'a'}"); + } + @Test + public void a11b_onParameter_Bean_noContentType() throws Exception { + a.request("PUT", "/Bean?noTrace=true").body("{f1:'a'}").execute().assertBodyContains("Unsupported Media Type"); + } + @Test + public void a12a_onParameter_InputStream() throws Exception { + // Content-Type should always be ignored. + a.request("PUT", "/InputStream").body("'a'").json().execute().assertBody("'\\'a\\''"); + } + @Test + public void a12b_onParameter_InputStream_noContentType() throws Exception { + a.request("PUT", "/InputStream").body("'a'").execute().assertBody("'\\'a\\''"); + } + @Test + public void a13a_onParameter_Reader() throws Exception { + // Content-Type should always be ignored. + a.request("PUT", "/Reader").body("'a'").json().execute().assertBody("'\\'a\\''"); + } + @Test + public void a13b_onParameter_Reader_noContentType() throws Exception { + a.request("PUT", "/Reader").body("'a'").execute().assertBody("'\\'a\\''"); + } + @Test + public void a14a_onParameter_InputStreamTransform() throws Exception { + // Input stream transform requests must not specify Content-Type or else gets resolved as POJO. + a.request("PUT", "/InputStreamTransform?noTrace=true").body("'a'").json().execute().assertBodyContains("Bad Request"); + } + @Test + public void a14b_onParameter_InputStreamTransform_noContentType() throws Exception { + a.request("PUT", "/InputStreamTransform").body("'a'").execute().assertBody("'\\'a\\''"); + } + @Test + public void a15a_onParameter_ReaderTransform() throws Exception { + // Reader transform requests must not specify Content-Type or else gets resolved as POJO. + a.request("PUT", "/ReaderTransform?noTrace=true").body("'a'").json().execute().assertBodyContains("Bad Request"); + } + @Test + public void a15b_onParameter_ReaderTransform_noContentType() throws Exception { + a.request("PUT", "/ReaderTransform").body("'a'").execute().assertBody("'\\'a\\''"); + } + @Test + public void a16a_onParameter_StringTransform() throws Exception { + // When Content-Type specified and matched, treated as a parsed POJO. + a.request("PUT", "/StringTransform").body("'a'").json().execute().assertBody("'a'"); + } + @Test + public void a16b_onParameter_StringTransform_noContentType() throws Exception { + // When Content-Type not matched, treated as plain text. + a.request("PUT", "/StringTransform").body("'a'").execute().assertBody("'\\'a\\''"); + } + + + //================================================================================================================= + // @Body on POJO + //================================================================================================================= + + @RestResource(serializers=JsonSerializer.Simple.class, parsers=JsonParser.class) + public static class B { + + @RestMethod(name=PUT, path="/StringTransform") + public B01 b01(B01 b) { + return b; + } + @Body + public static class B01 { + private String val; + public B01(String val) { this.val = val; } + @Override public String toString() { return val; } + } + + @RestMethod(name=PUT, path="/Bean") + public B02 b02(B02 b) { + return b; + } + @Body + public static class B02 { + public String f1; + } + + @RestMethod(name=PUT, path="/BeanList") + public B03 b03(B03 b) { + return b; + } + @Body + public static class B03 extends LinkedList<B02> {} + + @RestMethod(name=PUT, path="/InputStreamTransform") + public B04 b04(B04 b) throws Exception { + return b; + } + @Body + public static class B04 { + String s; + public B04(InputStream in) throws Exception { this.s = IOUtils.read(in); } + @Override public String toString() { return s; } + } + + @RestMethod(name=PUT, path="/ReaderTransform") + public B05 b05(B05 b) throws Exception { + return b; + } + @Body + public static class B05 { + private String s; + public B05(Reader in) throws Exception { this.s = IOUtils.read(in); } + @Override public String toString() { return s; } + } + + } + + private static MockRest b = MockRest.create(B.class); + + @Test + public void b01a_onPojo_StringTransform() throws Exception { + b.request("PUT", "/StringTransform").body("'foo'").json().execute().assertBody("'foo'"); + } + @Test + public void b01b_onPojo_StringTransform_noContentType() throws Exception { + // When Content-Type not matched, treated as plain text. + b.request("PUT", "/StringTransform").body("'foo'").execute().assertBody("'\\'foo\\''"); + } + @Test + public void b02a_onPojo_Bean() throws Exception { + b.request("PUT", "/Bean").body("{f1:'a'}").json().execute().assertBody("{f1:'a'}"); + } + @Test + public void b02b_onPojo_Bean_noContentType() throws Exception { + b.request("PUT", "/Bean?noTrace=true").body("{f1:'a'}").execute().assertBodyContains("Unsupported Media Type"); + } + @Test + public void b03a_onPojo_BeanList() throws Exception { + b.request("PUT", "/BeanList").body("[{f1:'a'}]").json().execute().assertBody("[{f1:'a'}]"); + } + @Test + public void b03b_onPojo_BeanList_noContentType() throws Exception { + b.request("PUT", "/BeanList?noTrace=true").body("[{f1:'a'}]").execute().assertBodyContains("Unsupported Media Type"); + } + @Test + public void b04a_onPojo_InputStreamTransform() throws Exception { + b.request("PUT", "/InputStreamTransform").body("a").execute().assertBody("'a'"); + } + @Test + public void b04b_onPojo_InputStreamTransform_withContentType() throws Exception { + // When Content-Type matched, treated as parsed POJO. + b.request("PUT", "/InputStreamTransform?noTrace=true").body("a").json().execute().assertBodyContains("Bad Request"); + } + @Test + public void b05a_onPojo_ReaderTransform() throws Exception { + b.request("PUT", "/ReaderTransform").body("a").execute().assertBody("'a'"); + } + @Test + public void b05b_onPojo_ReaderTransform_withContentType() throws Exception { + // When Content-Type matched, treated as parsed POJO. + b.request("PUT", "/ReaderTransform?noTrace=true").body("a").json().execute().assertBodyContains("Bad Request"); + } + + + //================================================================================================================= + // Basic tests using @Body parameter + //================================================================================================================= + + public void c01_bodyParam_String() throws Exception { + a.request("PUT", "/String?body=foo").execute().assertBody("'foo'"); + a.request("PUT", "/String?body=null").execute().assertBody("null"); + a.request("PUT", "/String?body=").execute().assertBody("''"); + } + @Test + public void c02_bodyParam_Integer() throws Exception { + a.request("PUT", "/Integer?body=123").execute().assertBody("123"); + a.request("PUT", "/Integer?body=-123").execute().assertBody("-123"); + a.request("PUT", "/Integer?body=null").execute().assertBody("null"); + a.request("PUT", "/Integer?body=").execute().assertBody("null"); + a.request("PUT", "/Integer?body=bad&noTrace=true").execute().assertStatus(400); + } + @Test + public void c03_bodyParam_int() throws Exception { + a.request("PUT", "/int?body=123").execute().assertBody("123"); + a.request("PUT", "/int?body=-123").execute().assertBody("-123"); + a.request("PUT", "/int?body=null").execute().assertBody("0"); + a.request("PUT", "/int?body=").execute().assertBody("0"); + a.request("PUT", "/int?body=bad&noTrace=true").execute().assertStatus(400); + } + @Test + public void c04_bodyParam_Boolean() throws Exception { + a.request("PUT", "/Boolean?body=true").execute().assertBody("true"); + a.request("PUT", "/Boolean?body=false").execute().assertBody("false"); + a.request("PUT", "/Boolean?body=null").execute().assertBody("null"); + a.request("PUT", "/Boolean?body=").execute().assertBody("null"); + a.request("PUT", "/Boolean?body=bad&noTrace=true").execute().assertStatus(400); + } + @Test + public void c05_bodyParam_boolean() throws Exception { + a.request("PUT", "/boolean?body=true").execute().assertBody("true"); + a.request("PUT", "/boolean?body=false").execute().assertBody("false"); + a.request("PUT", "/boolean?body=null").execute().assertBody("false"); + a.request("PUT", "/boolean?body=").execute().assertBody("false"); + a.request("PUT", "/boolean?body=bad&noTrace=true").execute().assertStatus(400); + } + @Test + public void c06_bodyParam_Float() throws Exception { + a.request("PUT", "/Float?body=1.23").execute().assertBody("1.23"); + a.request("PUT", "/Float?body=-1.23").execute().assertBody("-1.23"); + a.request("PUT", "/Float?body=null").execute().assertBody("null"); + a.request("PUT", "/Float?body=").execute().assertBody("null"); + a.request("PUT", "/Float?body=bad&noTrace=true").execute().assertStatus(400); + } + @Test + public void c07_bodyParam_float() throws Exception { + a.request("PUT", "/float?body=1.23").execute().assertBody("1.23"); + a.request("PUT", "/float?body=-1.23").execute().assertBody("-1.23"); + a.request("PUT", "/float?body=null").execute().assertBody("0.0"); + a.request("PUT", "/float?body=").execute().assertBody("0.0"); + a.request("PUT", "/float?body=bad&noTrace=true").execute().assertStatus(400); + } + @Test + public void c08_bodyParam_Map() throws Exception { + a.request("PUT", "/Map?body=(foo=123)").execute().assertBody("{foo:123}"); + a.request("PUT", "/Map?body=()").execute().assertBody("{}"); + a.request("PUT", "/Map?body=null").execute().assertBody("null"); + a.request("PUT", "/Map?body=").execute().assertBody("null"); + a.request("PUT", "/Map?body=bad&noTrace=true").execute().assertStatus(400); + } + @Test + public void c09_bodyParam_enum() throws Exception { + a.request("PUT", "/enum?body=ONE").execute().assertBody("'ONE'"); + a.request("PUT", "/enum?body=TWO").execute().assertBody("'TWO'"); + a.request("PUT", "/enum?body=null").execute().assertBody("null"); + a.request("PUT", "/enum?body=").execute().assertBody("null"); + a.request("PUT", "/enum?body=bad&noTrace=true").execute().assertStatus(400); + } + @Test + public void c11_bodyParam_Bean() throws Exception { + a.request("PUT", "/Bean?body=(f1=a)").execute().assertBody("{f1:'a'}"); + a.request("PUT", "/Bean?body=()").execute().assertBody("{}"); + a.request("PUT", "/Bean?body=null").execute().assertBody("null"); + a.request("PUT", "/Bean?body=").execute().assertBody("null"); + a.request("PUT", "/Bean?body=bad&noTrace=true").execute().assertStatus(400); + } + @Test + public void c12_bodyParam_InputStream() throws Exception { + a.request("PUT", "/InputStream?body=a").execute().assertBody("'a'"); + a.request("PUT", "/InputStream?body=null").execute().assertBody("'null'"); + a.request("PUT", "/InputStream?body=").execute().assertBody("''"); + } + @Test + public void c13_bodyParam_Reader() throws Exception { + a.request("PUT", "/Reader?body=a").execute().assertBody("'a'"); + a.request("PUT", "/Reader?body=null").execute().assertBody("'null'"); + a.request("PUT", "/Reader?body=").execute().assertBody("''"); + } + + // It's not currently possible to pass in a &body parameter for InputStream/Reader transforms. + + + //================================================================================================================= + // No serializers or parsers needed when using only streams and readers. + //================================================================================================================= + + @RestResource + public static class D { + + @RestMethod(name=PUT, path="/String") + public Reader d01(@Body Reader b) throws Exception { + return b; + } + + @RestMethod(name=PUT, path="/InputStream") + public InputStream d02(@Body InputStream b) throws Exception { + return b; + } + + @RestMethod(name=PUT, path="/Reader") + public Reader d03(@Body Reader b) throws Exception { + return b; + } + + @RestMethod(name=PUT, path="/StringTransform") + public Reader d04(@Body D04 b) throws Exception { + return new StringReader(b.toString()); + } + public static class D04 { + private String s; + public D04(String in) throws Exception { this.s = in; } + @Override public String toString() { return s; } + } + + @RestMethod(name=PUT, path="/InputStreamTransform") + public Reader d05(@Body D05 b) throws Exception { + return new StringReader(b.toString()); + } + public static class D05 { + String s; + public D05(InputStream in) throws Exception { this.s = IOUtils.read(in); } + @Override public String toString() { return s; } + } + + @RestMethod(name=PUT, path="/ReaderTransform") + public Reader d06(@Body D06 b) throws Exception { + return new StringReader(b.toString()); + } + public static class D06 { + private String s; + public D06(Reader in) throws Exception{ this.s = IOUtils.read(in); } + @Override public String toString() { return s; } + } + + @RestMethod(name=PUT, path="/StringTransformBodyOnPojo") + public Reader d07(D07 b) throws Exception { + return new StringReader(b.toString()); + } + @Body + public static class D07 { + private String s; + public D07(String in) throws Exception { this.s = in; } + @Override public String toString() { return s; } + } + + @RestMethod(name=PUT, path="/InputStreamTransformBodyOnPojo") + public Reader d08(D08 b) throws Exception { + return new StringReader(b.toString()); + } + @Body + public static class D08 { + String s; + public D08(InputStream in) throws Exception { this.s = IOUtils.read(in); } + @Override public String toString() { return s; } + } + + @RestMethod(name=PUT, path="/ReaderTransformBodyOnPojo") + public Reader d09(D09 b) throws Exception { + return new StringReader(b.toString()); + } + @Body + public static class D09 { + private String s; + public D09(Reader in) throws Exception{ this.s = IOUtils.read(in); } + @Override public String toString() { return s; } + } + } + + private static MockRest d = MockRest.create(D.class); + + @Test + public void d01a_noMediaTypes_String() throws Exception { + d.request("PUT", "/String").body("a").execute().assertBody("a"); + } + @Test + public void d01b_noMediaTypes_String_withContentType() throws Exception { + d.request("PUT", "/String").body("a").json().execute().assertBody("a"); + } + @Test + public void d02a_noMediaTypes_InputStream() throws Exception { + d.request("PUT", "/InputStream").body("a").execute().assertBody("a"); + } + @Test + public void d02b_noMediaTypes_InputStream_withContentType() throws Exception { + d.request("PUT", "/InputStream").body("a").json().execute().assertBody("a"); + } + @Test + public void d03a_noMediaTypes_Reader() throws Exception { + d.request("PUT", "/Reader").body("a").execute().assertBody("a"); + } + @Test + public void d03b_noMediaTypes_Reader_withContentType() throws Exception { + d.request("PUT", "/Reader").body("a").json().execute().assertBody("a"); + } + @Test + public void d04a_noMediaTypes_StringTransform() throws Exception { + d.request("PUT", "/StringTransform").body("a").execute().assertBody("a"); + } + @Test + public void d04b_noMediaTypes_StringTransform_withContentType() throws Exception { + d.request("PUT", "/StringTransform?noTrace=true").body("a").json().execute().assertStatus(415); + } + @Test + public void d05a_noMediaTypes_InputStreamTransform() throws Exception { + d.request("PUT", "/InputStreamTransform").body("a").execute().assertBody("a"); + } + @Test + public void d05b_noMediaTypes_InputStreamTransform_withContentType() throws Exception { + d.request("PUT", "/InputStreamTransform").body("a").json().execute().assertBody("a"); + } + @Test + public void d06a_noMediaTypes_ReaderTransform() throws Exception { + d.request("PUT", "/ReaderTransform").body("a").execute().assertBody("a"); + } + @Test + public void d06b_noMediaTypes_ReaderTransform_withContentType() throws Exception { + d.request("PUT", "/ReaderTransform").body("a").json().execute().assertBody("a"); + } + @Test + public void d07a_noMediaTypes_StringTransformBodyOnPojo() throws Exception { + d.request("PUT", "/StringTransformBodyOnPojo").body("a").execute().assertBody("a"); + } + @Test + public void d07b_noMediaTypes_StringTransformBodyOnPojo_withContentType() throws Exception { + d.request("PUT", "/StringTransformBodyOnPojo?noTrace=true").body("a").json().execute().assertStatus(415); + } + @Test + public void d08a_noMediaTypes_InputStreamTransformBodyOnPojo() throws Exception { + d.request("PUT", "/InputStreamTransformBodyOnPojo").body("a").execute().assertBody("a"); + } + @Test + public void d08b_noMediaTypes_InputStreamTransformBodyOnPojo_withContentType() throws Exception { + d.request("PUT", "/InputStreamTransformBodyOnPojo").body("a").json().execute().assertBody("a"); + } + @Test + public void d09a_noMediaTypes_ReaderTransformBodyOnPojo() throws Exception { + d.request("PUT", "/ReaderTransformBodyOnPojo").body("a").execute().assertBody("a"); + } + @Test + public void d09b_noMediaTypes_ReaderTransformBodyOnPojo_withContentType() throws Exception { + d.request("PUT", "/ReaderTransformBodyOnPojo").body("a").json().execute().assertBody("a"); + } + + + //================================================================================================================= + // Complex POJOs + //================================================================================================================= + + @RestResource(serializers=JsonSerializer.Simple.class, parsers=JsonParser.class) + public static class E { + + @RestMethod(name=PUT, path="/B") + public DTOs.B testPojo1(@Body DTOs.B b) { + return b; + } + + @RestMethod(name=PUT, path="/C") + public DTOs.C testPojo2(@Body DTOs.C c) { + return c; + } + } + + private static MockRest e = MockRest.create(E.class); + + @Test + public void e01_complexPojos_B_body() throws Exception { + String expected = "{f01:['a','b'],f02:['c','d'],f03:[1,2],f04:[3,4],f05:[['e','f'],['g','h']],f06:[['i','j'],['k','l']],f07:[{a:'a',b:1,c:true},{a:'a',b:1,c:true}],f08:[{a:'a',b:1,c:true},{a:'a',b:1,c:true}],f09:[[{a:'a',b:1,c:true}],[{a:'a',b:1,c:true}]],f10:[[{a:'a',b:1,c:true}],[{a:'a',b:1,c:true}]],f11:['a','b'],f12:['c','d'],f13:[1,2],f14:[3,4],f15:[['e','f'],['g','h']],f16:[['i','j'],['k','l']],f17:[{a:'a',b:1,c:true},{a:'a',b:1,c:true}],f18:[{a:'a',b:1,c:true},{a:'a',b:1,c:true} [...] + e.request("PUT", "/B").body(JsonSerializer.DEFAULT_LAX.toString(DTOs.B.INSTANCE)).json().execute().assertBody(expected); + } + @Test + public void e02_complexPojos_B_bodyParam() throws Exception { + String expected = "{f01:['a','b'],f02:['c','d'],f03:[1,2],f04:[3,4],f05:[['e','f'],['g','h']],f06:[['i','j'],['k','l']],f07:[{a:'a',b:1,c:true},{a:'a',b:1,c:true}],f08:[{a:'a',b:1,c:true},{a:'a',b:1,c:true}],f09:[[{a:'a',b:1,c:true}],[{a:'a',b:1,c:true}]],f10:[[{a:'a',b:1,c:true}],[{a:'a',b:1,c:true}]],f11:['a','b'],f12:['c','d'],f13:[1,2],f14:[3,4],f15:[['e','f'],['g','h']],f16:[['i','j'],['k','l']],f17:[{a:'a',b:1,c:true},{a:'a',b:1,c:true}],f18:[{a:'a',b:1,c:true},{a:'a',b:1,c:true} [...] + e.request("PUT", "/B?body=" + UonSerializer.DEFAULT.serialize(DTOs.B.INSTANCE)).body("a").execute().assertBody(expected); + } + @Test + public void e03_complexPojos_C_body() throws Exception { + String expected = "{f01:['a','b'],f02:['c','d'],f03:[1,2],f04:[3,4],f05:[['e','f'],['g','h']],f06:[['i','j'],['k','l']],f07:[{a:'a',b:1,c:true},{a:'a',b:1,c:true}],f08:[{a:'a',b:1,c:true},{a:'a',b:1,c:true}],f09:[[{a:'a',b:1,c:true}],[{a:'a',b:1,c:true}]],f10:[[{a:'a',b:1,c:true}],[{a:'a',b:1,c:true}]],f11:['a','b'],f12:['c','d'],f13:[1,2],f14:[3,4],f15:[['e','f'],['g','h']],f16:[['i','j'],['k','l']],f17:[{a:'a',b:1,c:true},{a:'a',b:1,c:true}],f18:[{a:'a',b:1,c:true},{a:'a',b:1,c:true} [...] + e.request("PUT", "/C").body(JsonSerializer.DEFAULT_LAX.toString(DTOs.B.INSTANCE)).json().execute().assertBody(expected); + } + @Test + public void e04_complexPojos_C_bodyParam() throws Exception { + String expected = "{f01:['a','b'],f02:['c','d'],f03:[1,2],f04:[3,4],f05:[['e','f'],['g','h']],f06:[['i','j'],['k','l']],f07:[{a:'a',b:1,c:true},{a:'a',b:1,c:true}],f08:[{a:'a',b:1,c:true},{a:'a',b:1,c:true}],f09:[[{a:'a',b:1,c:true}],[{a:'a',b:1,c:true}]],f10:[[{a:'a',b:1,c:true}],[{a:'a',b:1,c:true}]],f11:['a','b'],f12:['c','d'],f13:[1,2],f14:[3,4],f15:[['e','f'],['g','h']],f16:[['i','j'],['k','l']],f17:[{a:'a',b:1,c:true},{a:'a',b:1,c:true}],f18:[{a:'a',b:1,c:true},{a:'a',b:1,c:true} [...] + e.request("PUT", "/C?body=" + UonSerializer.DEFAULT.serialize(DTOs.B.INSTANCE)).body("a").execute().assertBody(expected); + } +} diff --git a/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/annotation/BodyTest.java b/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/annotation/BodyTest.java deleted file mode 100644 index 07707bb..0000000 --- a/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/annotation/BodyTest.java +++ /dev/null @@ -1,533 +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.annotation; - -import static org.apache.juneau.http.HttpMethodName.*; -import static org.junit.Assert.*; - -import java.io.*; -import java.util.*; - - -import org.apache.juneau.internal.*; -import org.apache.juneau.json.*; -import org.apache.juneau.rest.mock.*; -import org.apache.juneau.rest.testutils.*; -import org.apache.juneau.uon.*; -import org.junit.*; -import org.junit.runners.*; - -/** - * Tests the {@link Body} annotation. - */ -@SuppressWarnings({"javadoc","serial"}) -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -public class BodyTest { - - //----------------------------------------------------------------------------------------------------------------- - // @Body on parameter - //----------------------------------------------------------------------------------------------------------------- - - @RestResource(serializers=JsonSerializer.Simple.class, parsers=JsonParser.class) - public static class A { - - @RestMethod(name=PUT, path="/String") - public String a01(@Body String b) { - return b; - } - - @RestMethod(name=PUT, path="/Integer") - public Integer a02(@Body Integer b) { - return b; - } - - @RestMethod(name=PUT, path="/int") - public Integer a03(@Body int b) { - return b; - } - - @RestMethod(name=PUT, path="/Boolean") - public Boolean a04(@Body Boolean b) { - return b; - } - - @RestMethod(name=PUT, path="/boolean") - public Boolean a05(@Body boolean b) { - return b; - } - - @RestMethod(name=PUT, path="/float") - public float a06(@Body float f) { - return f; - } - - @RestMethod(name=PUT, path="/Float") - public Float a07(@Body Float f) { - return f; - } - - @RestMethod(name=PUT, path="/Map") - public TreeMap<String,Integer> a08(@Body TreeMap<String,Integer> m) { - return m; - } - - @RestMethod(name=PUT, path="/enum") - public TestEnum a09(@Body TestEnum e) { - return e; - } - - public static class A11 { - public String f1; - } - - @RestMethod(name=PUT, path="/bean") - public A11 a11(@Body A11 b) { - return b; - } - - @RestMethod(name=PUT, path="/inputStream") - public String a12(@Body InputStream b) throws Exception { - return IOUtils.read(b); - } - - @RestMethod(name=PUT, path="/reader") - public String a13(@Body Reader b) throws Exception { - return IOUtils.read(b); - } - - public static class A14 { - String s; - - public A14(InputStream in) throws Exception { - this.s = IOUtils.read(in); - } - - @Override /* Object */ - public String toString() { - return s; - } - } - - @RestMethod(name=PUT, path="/inputStreamTransform") - public A14 a14(@Body A14 b) throws Exception { - return b; - } - - public static class A15 { - private String s; - - public A15(Reader in) throws Exception { - this.s = IOUtils.read(in); - } - - @Override /* Object */ - public String toString() { - return s; - } - } - - @RestMethod(name=PUT, path="/readerTransform") - public A15 a15(@Body A15 b) throws Exception { - return b; - } - } - - private MockRest a = MockRest.create(A.class); - - @Test - public void a01_onParameter_String() throws Exception { - assertEquals("'foo'", a.request("PUT", "/String").body("'foo'").execute().getBodyAsString()); - } - @Test - public void a02_onParameter_Integer() throws Exception { - assertEquals("123", a.request("PUT", "/Integer").body("123").execute().getBodyAsString()); - } - @Test - public void a03_onParameter_int() throws Exception { - assertEquals("123", a.request("PUT", "/int").body("123").execute().getBodyAsString()); - } - @Test - public void a04_onParameter_Boolean() throws Exception { - assertEquals("true", a.request("PUT", "/Boolean").body("true").execute().getBodyAsString()); - } - @Test - public void a05_onParameter_boolean() throws Exception { - assertEquals("true", a.request("PUT", "/boolean").body("true").execute().getBodyAsString()); - } - @Test - public void a06_onParameter_float() throws Exception { - assertEquals("1.23", a.request("PUT", "/float").body("1.23").execute().getBodyAsString()); - } - @Test - public void a07_onParameter_Float() throws Exception { - assertEquals("1.23", a.request("PUT", "/Float").body("1.23").execute().getBodyAsString()); - } - @Test - public void a08_onParameter_Map() throws Exception { - assertEquals("{foo:123}", a.request("PUT", "/Map").body("{foo:123}").execute().getBodyAsString()); - } - @Test - public void a09_onParameter_enum() throws Exception { - assertEquals("'ONE'", a.request("PUT", "/enum").body("'ONE'").execute().getBodyAsString()); - } - @Test - public void a11_onParameter_bean() throws Exception { - assertEquals("{f1:'a'}", a.request("PUT", "/bean").body("{f1:'a'}").execute().getBodyAsString()); - } - @Test - public void a12_onParameter_inputStream() throws Exception { - assertEquals("'a'", a.request("PUT", "/inputStream").body("a").execute().getBodyAsString()); - } - @Test - public void a13_onParameter_reader() throws Exception { - assertEquals("'a'", a.request("PUT", "/reader").body("a").execute().getBodyAsString()); - } - @Test - public void a14_onParameter_inputStreamTransform() throws Exception { - assertEquals("'a'", a.request("PUT", "/inputStreamTransform").body("a").execute().getBodyAsString()); - } - @Test - public void a15_onParameter_readerTransform() throws Exception { - assertEquals("'a'", a.request("PUT", "/readerTransform").body("a").execute().getBodyAsString()); - } - - - //----------------------------------------------------------------------------------------------------------------- - // @Body on POJO - //----------------------------------------------------------------------------------------------------------------- - - @RestResource(serializers=JsonSerializer.Simple.class, parsers=JsonParser.class) - public static class B { - - @Body - public static class B01 { - private String val; - - public B01(String val) { - this.val = val; - } - - @Override - public String toString() { - return val; - } - } - - @RestMethod(name=PUT, path="/string") - public B01 simple(B01 b) { - return b; - } - - @Body - public static class B02 { - public String f1; - } - - @RestMethod(name=PUT, path="/bean") - public B02 b02(B02 b) { - return b; - } - - @Body - public static class B03 extends LinkedList<B02> {} - - @RestMethod(name=PUT, path="/beanList") - public B03 b03(B03 b) { - return b; - } - - @Body - public static class B04 { - String s; - - public B04(InputStream in) throws Exception { - this.s = IOUtils.read(in); - } - - @Override /* Object */ - public String toString() { - return s; - } - } - - @RestMethod(name=PUT, path="/inputStreamTransform") - public B04 b04(B04 b) throws Exception { - return b; - } - - @Body - public static class B05 { - private String s; - - public B05(Reader in) throws Exception { - this.s = IOUtils.read(in); - } - - @Override /* Object */ - public String toString() { - return s; - } - } - - @RestMethod(name=PUT, path="/readerTransform") - public B05 b05(B05 b) throws Exception { - return b; - } - } - - private MockRest b = MockRest.create(B.class); - - @Test - public void b01_onPojo_string() throws Exception { - assertEquals("'foo'", b.request("PUT", "/string").body("'foo'").execute().getBodyAsString()); - } - @Test - public void b02_onPojo_bean() throws Exception { - assertEquals("{f1:'a'}", b.request("PUT", "/bean").body("{f1:'a'}").execute().getBodyAsString()); - } - @Test - public void b03_onPojo_beanList() throws Exception { - assertEquals("[{f1:'a'}]", b.request("PUT", "/beanList").body("[{f1:'a'}]").execute().getBodyAsString()); - } - @Test - public void b04_onPojo_inputStream() throws Exception { - assertEquals("'a'", b.request("PUT", "/inputStreamTransform").body("a").execute().getBodyAsString()); - } - @Test - public void b05_onPojo_reader() throws Exception { - assertEquals("'a'", b.request("PUT", "/readerTransform").body("a").execute().getBodyAsString()); - } - - - //----------------------------------------------------------------------------------------------------------------- - // Basic tests using @Body parameter - //----------------------------------------------------------------------------------------------------------------- - - public void c01_bodyParam_String() throws Exception { - assertEquals("'foo'", a.request("PUT", "/String?body=foo").execute().getBodyAsString()); - assertEquals("null", a.request("PUT", "/String?body=null").execute().getBodyAsString()); - assertEquals("''", a.request("PUT", "/String?body=").execute().getBodyAsString()); - } - @Test - public void c02_bodyParam_Integer() throws Exception { - assertEquals("123", a.request("PUT", "/Integer?body=123").execute().getBodyAsString()); - assertEquals("-123", a.request("PUT", "/Integer?body=-123").execute().getBodyAsString()); - assertEquals("null", a.request("PUT", "/Integer?body=null").execute().getBodyAsString()); - assertEquals("null", a.request("PUT", "/Integer?body=").execute().getBodyAsString()); - assertEquals(400, a.request("PUT", "/Integer?body=bad&noTrace=true").execute().getStatus()); - } - @Test - public void c03_bodyParam_int() throws Exception { - assertEquals("123", a.request("PUT", "/int?body=123").execute().getBodyAsString()); - assertEquals("-123", a.request("PUT", "/int?body=-123").execute().getBodyAsString()); - assertEquals("0", a.request("PUT", "/int?body=null").execute().getBodyAsString()); - assertEquals("0", a.request("PUT", "/int?body=").execute().getBodyAsString()); - assertEquals(400, a.request("PUT", "/int?body=bad&noTrace=true").execute().getStatus()); - } - @Test - public void c04_bodyParam_Boolean() throws Exception { - assertEquals("true", a.request("PUT", "/Boolean?body=true").execute().getBodyAsString()); - assertEquals("false", a.request("PUT", "/Boolean?body=false").execute().getBodyAsString()); - assertEquals("null", a.request("PUT", "/Boolean?body=null").execute().getBodyAsString()); - assertEquals("null", a.request("PUT", "/Boolean?body=").execute().getBodyAsString()); - assertEquals(400, a.request("PUT", "/Boolean?body=bad&noTrace=true").execute().getStatus()); - } - @Test - public void c05_bodyParam_boolean() throws Exception { - assertEquals("true", a.request("PUT", "/boolean?body=true").execute().getBodyAsString()); - assertEquals("false", a.request("PUT", "/boolean?body=false").execute().getBodyAsString()); - assertEquals("false", a.request("PUT", "/boolean?body=null").execute().getBodyAsString()); - assertEquals("false", a.request("PUT", "/boolean?body=").execute().getBodyAsString()); - assertEquals(400, a.request("PUT", "/boolean?body=bad&noTrace=true").execute().getStatus()); - } - @Test - public void c06_bodyParam_Float() throws Exception { - assertEquals("1.23", a.request("PUT", "/Float?body=1.23").execute().getBodyAsString()); - assertEquals("-1.23", a.request("PUT", "/Float?body=-1.23").execute().getBodyAsString()); - assertEquals("null", a.request("PUT", "/Float?body=null").execute().getBodyAsString()); - assertEquals("null", a.request("PUT", "/Float?body=").execute().getBodyAsString()); - assertEquals(400, a.request("PUT", "/Float?body=bad&noTrace=true").execute().getStatus()); - } - @Test - public void c07_bodyParam_float() throws Exception { - assertEquals("1.23", a.request("PUT", "/float?body=1.23").execute().getBodyAsString()); - assertEquals("-1.23", a.request("PUT", "/float?body=-1.23").execute().getBodyAsString()); - assertEquals("0.0", a.request("PUT", "/float?body=null").execute().getBodyAsString()); - assertEquals("0.0", a.request("PUT", "/float?body=").execute().getBodyAsString()); - assertEquals(400, a.request("PUT", "/float?body=bad&noTrace=true").execute().getStatus()); - } - @Test - public void c08_bodyParam_Map() throws Exception { - assertEquals("{foo:123}", a.request("PUT", "/Map?body=(foo=123)").execute().getBodyAsString()); - assertEquals("{}", a.request("PUT", "/Map?body=()").execute().getBodyAsString()); - assertEquals("null", a.request("PUT", "/Map?body=null").execute().getBodyAsString()); - assertEquals("null", a.request("PUT", "/Map?body=").execute().getBodyAsString()); - assertEquals(400, a.request("PUT", "/Map?body=bad&noTrace=true").execute().getStatus()); - } - @Test - public void c09_bodyParam_enum() throws Exception { - assertEquals("'ONE'", a.request("PUT", "/enum?body=ONE").execute().getBodyAsString()); - assertEquals("'TWO'", a.request("PUT", "/enum?body=TWO").execute().getBodyAsString()); - assertEquals("null", a.request("PUT", "/enum?body=null").execute().getBodyAsString()); - assertEquals("null", a.request("PUT", "/enum?body=").execute().getBodyAsString()); - assertEquals(400, a.request("PUT", "/enum?body=bad&noTrace=true").execute().getStatus()); - } - @Test - public void c11_bodyParam_bean() throws Exception { - assertEquals("{f1:'a'}", a.request("PUT", "/bean?body=(f1=a)").execute().getBodyAsString()); - assertEquals("{}", a.request("PUT", "/bean?body=()").execute().getBodyAsString()); - assertEquals("null", a.request("PUT", "/bean?body=null").execute().getBodyAsString()); - assertEquals("null", a.request("PUT", "/bean?body=").execute().getBodyAsString()); - assertEquals(400, a.request("PUT", "/bean?body=bad&noTrace=true").execute().getStatus()); - } - @Test - public void c12_bodyParam_inputStream() throws Exception { - assertEquals("'a'", a.request("PUT", "/inputStream?body=a").execute().getBodyAsString()); - assertEquals("'null'", a.request("PUT", "/inputStream?body=null").execute().getBodyAsString()); - assertEquals("''", a.request("PUT", "/inputStream?body=").execute().getBodyAsString()); - } - @Test - public void c13_bodyParam_reader() throws Exception { - assertEquals("'a'", a.request("PUT", "/reader?body=a").execute().getBodyAsString()); - assertEquals("'null'", a.request("PUT", "/reader?body=null").execute().getBodyAsString()); - assertEquals("''", a.request("PUT", "/reader?body=").execute().getBodyAsString()); - } - @Test - public void c14_bodyParam_inputStreamTransform() throws Exception { - assertEquals("'a'", a.request("PUT", "/inputStreamTransform?body=a").execute().getBodyAsString()); - assertEquals("'null'", a.request("PUT", "/inputStreamTransform?body=null").execute().getBodyAsString()); - assertEquals("''", a.request("PUT", "/inputStreamTransform?body=").execute().getBodyAsString()); - } - @Test - public void c15_bodyParam_readerTransform() throws Exception { - assertEquals("'a'", a.request("PUT", "/readerTransform?body=a").execute().getBodyAsString()); - assertEquals("'null'", a.request("PUT", "/readerTransform?body=null").execute().getBodyAsString()); - assertEquals("''", a.request("PUT", "/readerTransform?body=").execute().getBodyAsString()); - } - - - //----------------------------------------------------------------------------------------------------------------- - // No serializers or parsers needed when using only streams and readers. - //----------------------------------------------------------------------------------------------------------------- - - @RestResource - public static class D { - - @RestMethod(name=PUT, path="/inputStream") - public InputStream d01(@Body InputStream b) throws Exception { - return b; - } - - @RestMethod(name=PUT, path="/reader") - public Reader d02(@Body Reader b) throws Exception { - return b; - } - - public static class D03 { - String s; - - public D03(InputStream in) throws Exception { - this.s = IOUtils.read(in); - } - - @Override /* Object */ - public String toString() { - return s; - } - } - - @RestMethod(name=PUT, path="/inputStreamTransform") - public Reader d03(@Body D03 b) throws Exception { - return new StringReader(b.toString()); - } - - public static class D04 { - private String s; - - public D04(Reader in) throws Exception { - this.s = IOUtils.read(in); - } - - @Override /* Object */ - public String toString() { - return s; - } - } - - @RestMethod(name=PUT, path="/readerTransform") - public Reader d04(@Body D04 b) throws Exception { - return new StringReader(b.toString()); - } - } - - private MockRest d = MockRest.create(D.class); - - @Test - public void d01_noMediaTypes_inputStream() throws Exception { - assertEquals("a", d.request("PUT", "/inputStream").body("a").execute().getBodyAsString()); - } - @Test - public void d02_noMediaTypes_reader() throws Exception { - assertEquals("a", d.request("PUT", "/reader").body("a").execute().getBodyAsString()); - } - @Test - public void d03_noMediaTypes_inputStreamTransform() throws Exception { - assertEquals("a", d.request("PUT", "/inputStreamTransform").body("a").execute().getBodyAsString()); - } - @Test - public void d04_noMediaTypes_readerTransform() throws Exception { - assertEquals("a", d.request("PUT", "/readerTransform").body("a").execute().getBodyAsString()); - } - - - //----------------------------------------------------------------------------------------------------------------- - // Complex POJOs - //----------------------------------------------------------------------------------------------------------------- - - @RestResource(serializers=JsonSerializer.Simple.class, parsers=JsonParser.class) - public static class E { - - @RestMethod(name=PUT, path="/B") - public DTOs.B testPojo1(@Body DTOs.B b) { - return b; - } - - @RestMethod(name=PUT, path="/C") - public DTOs.C testPojo2(@Body DTOs.C c) { - return c; - } - } - - private MockRest e = MockRest.create(E.class); - - @Test - public void e01_complexPojos_B_body() throws Exception { - String expected = "{f01:['a','b'],f02:['c','d'],f03:[1,2],f04:[3,4],f05:[['e','f'],['g','h']],f06:[['i','j'],['k','l']],f07:[{a:'a',b:1,c:true},{a:'a',b:1,c:true}],f08:[{a:'a',b:1,c:true},{a:'a',b:1,c:true}],f09:[[{a:'a',b:1,c:true}],[{a:'a',b:1,c:true}]],f10:[[{a:'a',b:1,c:true}],[{a:'a',b:1,c:true}]],f11:['a','b'],f12:['c','d'],f13:[1,2],f14:[3,4],f15:[['e','f'],['g','h']],f16:[['i','j'],['k','l']],f17:[{a:'a',b:1,c:true},{a:'a',b:1,c:true}],f18:[{a:'a',b:1,c:true},{a:'a',b:1,c:true} [...] - assertEquals(expected, e.request("PUT", "/B").body(JsonSerializer.DEFAULT_LAX.toString(DTOs.B.INSTANCE)).execute().getBodyAsString()); - } - @Test - public void e02_complexPojos_B_bodyParam() throws Exception { - String expected = "{f01:['a','b'],f02:['c','d'],f03:[1,2],f04:[3,4],f05:[['e','f'],['g','h']],f06:[['i','j'],['k','l']],f07:[{a:'a',b:1,c:true},{a:'a',b:1,c:true}],f08:[{a:'a',b:1,c:true},{a:'a',b:1,c:true}],f09:[[{a:'a',b:1,c:true}],[{a:'a',b:1,c:true}]],f10:[[{a:'a',b:1,c:true}],[{a:'a',b:1,c:true}]],f11:['a','b'],f12:['c','d'],f13:[1,2],f14:[3,4],f15:[['e','f'],['g','h']],f16:[['i','j'],['k','l']],f17:[{a:'a',b:1,c:true},{a:'a',b:1,c:true}],f18:[{a:'a',b:1,c:true},{a:'a',b:1,c:true} [...] - assertEquals(expected, e.request("PUT", "/B?body=" + UonSerializer.DEFAULT.serialize(DTOs.B.INSTANCE)).body("a").execute().getBodyAsString()); - } - @Test - public void e03_complexPojos_C_body() throws Exception { - String expected = "{f01:['a','b'],f02:['c','d'],f03:[1,2],f04:[3,4],f05:[['e','f'],['g','h']],f06:[['i','j'],['k','l']],f07:[{a:'a',b:1,c:true},{a:'a',b:1,c:true}],f08:[{a:'a',b:1,c:true},{a:'a',b:1,c:true}],f09:[[{a:'a',b:1,c:true}],[{a:'a',b:1,c:true}]],f10:[[{a:'a',b:1,c:true}],[{a:'a',b:1,c:true}]],f11:['a','b'],f12:['c','d'],f13:[1,2],f14:[3,4],f15:[['e','f'],['g','h']],f16:[['i','j'],['k','l']],f17:[{a:'a',b:1,c:true},{a:'a',b:1,c:true}],f18:[{a:'a',b:1,c:true},{a:'a',b:1,c:true} [...] - assertEquals(expected, e.request("PUT", "/C").body(JsonSerializer.DEFAULT_LAX.toString(DTOs.B.INSTANCE)).execute().getBodyAsString()); - } - @Test - public void e04_complexPojos_C_bodyParam() throws Exception { - String expected = "{f01:['a','b'],f02:['c','d'],f03:[1,2],f04:[3,4],f05:[['e','f'],['g','h']],f06:[['i','j'],['k','l']],f07:[{a:'a',b:1,c:true},{a:'a',b:1,c:true}],f08:[{a:'a',b:1,c:true},{a:'a',b:1,c:true}],f09:[[{a:'a',b:1,c:true}],[{a:'a',b:1,c:true}]],f10:[[{a:'a',b:1,c:true}],[{a:'a',b:1,c:true}]],f11:['a','b'],f12:['c','d'],f13:[1,2],f14:[3,4],f15:[['e','f'],['g','h']],f16:[['i','j'],['k','l']],f17:[{a:'a',b:1,c:true},{a:'a',b:1,c:true}],f18:[{a:'a',b:1,c:true},{a:'a',b:1,c:true} [...] - assertEquals(expected, e.request("PUT", "/C?body=" + UonSerializer.DEFAULT.serialize(DTOs.B.INSTANCE)).body("a").execute().getBodyAsString()); - } -} diff --git a/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/annotation/DefaultContentTypesTest.java b/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/annotation/DefaultContentTypesTest.java new file mode 100644 index 0000000..3a9907b --- /dev/null +++ b/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/annotation/DefaultContentTypesTest.java @@ -0,0 +1,384 @@ +// *************************************************************************************************************************** +// * 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.annotation; + +import static org.apache.juneau.http.HttpMethodName.*; + +import org.apache.juneau.*; +import org.apache.juneau.parser.*; +import org.apache.juneau.rest.mock.*; +import org.apache.juneau.serializer.*; +import org.junit.*; +import org.junit.runners.*; + + +/** + * Tests related to @RestRequest(defaultRequestHeaders). + */ +@SuppressWarnings({"javadoc"}) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class DefaultContentTypesTest { + + //================================================================================================================= + // Setup classes + //================================================================================================================= + + public static class DummyParser extends ReaderParser { + + String name; + + DummyParser(PropertyStore ps, String name, String...consumes) { + super(ps, consumes); + this.name = name; + } + + @Override /* Parser */ + public ReaderParserSession createSession(ParserSessionArgs args) { + return new ReaderParserSession(args) { + @Override /* ParserSession */ + @SuppressWarnings("unchecked") + protected <T> T doParse(ParserPipe pipe, ClassMeta<T> type) throws Exception { + return (T)name; + } + }; + } + } + + public static class DummySerializer extends WriterSerializer { + + String name; + + DummySerializer(PropertyStore ps, String name, String produces) { + super(ps, produces, null); + this.name = name; + } + + @Override /* Serializer */ + public WriterSerializerSession createSession(SerializerSessionArgs args) { + return new WriterSerializerSession(args) { + @Override /* SerializerSession */ + protected void doSerialize(SerializerPipe out, Object o) throws Exception { + out.getWriter().write(name + "/" + o); + } + }; + } + } + + public static class P1 extends DummyParser { public P1(PropertyStore ps) {super(ps, "p1", "text/p1");}} + public static class P2 extends DummyParser { public P2(PropertyStore ps) {super(ps, "p2", "text/p2");}} + public static class P3 extends DummyParser { public P3(PropertyStore ps) {super(ps, "p3", "text/p3");}} + public static class S1 extends DummySerializer { public S1(PropertyStore ps) {super(ps, "s1", "text/s1");}} + public static class S2 extends DummySerializer { public S2(PropertyStore ps) {super(ps, "s2", "text/s2");}} + public static class S3 extends DummySerializer { public S3(PropertyStore ps) {super(ps, "s3", "text/s3");}} + + + //==================================================================================================== + // Test that default Accept and Content-Type headers on servlet annotation are picked up. + //==================================================================================================== + + @RestResource( + defaultRequestHeaders={" Accept : text/s2 "," Content-Type : text/p2 "}, + parsers={P1.class,P2.class}, serializers={S1.class,S2.class} + ) + public static class A { + @RestMethod(name=PUT) + public String a01(@Body String in) { + return in; + } + } + + private static MockRest a = MockRest.create(A.class); + + @Test + public void a01_defaultHeadersOnServletAnnotation_valid() throws Exception { + a.request("PUT", "/").execute().assertBody("s2/p2"); + a.request("PUT", "/").accept("text/s1").execute().assertBody("s1/p2"); + a.request("PUT", "/").contentType("text/p1").execute().assertBody("s2/p1"); + a.request("PUT", "/").accept("text/s1").contentType("text/p1").execute().assertBody("s1/p1"); + a.request("PUT", "/").accept("text/s2").execute().assertBody("s2/p2"); + a.request("PUT", "/").contentType("text/p2").execute().assertBody("s2/p2"); + a.request("PUT", "/").accept("text/s2").contentType("text/p2").execute().assertBody("s2/p2"); + } + + @Test + public void a02_defaultHeadersOnServletAnnotation_invalid() throws Exception { + a.request("PUT", "?noTrace=true").accept("text/s3").execute().assertStatus(406).assertBodyContains("Unsupported media-type in request header 'Accept': 'text/s3'"); + a.request("PUT", "?noTrace=true").contentType("text/p3").execute().assertStatus(415).assertBodyContains("Unsupported media-type in request header 'Content-Type': 'text/p3'"); + a.request("PUT", "?noTrace=true").accept("text/s3").contentType("text/p3").execute().assertStatus(415).assertBodyContains("Unsupported media-type in request header 'Content-Type': 'text/p3'"); + } + + //==================================================================================================== + // Test that default Accept and Content-Type headers on servlet annotation are picked up + // when @RestMethod.parsers/serializers annotations are used. + //==================================================================================================== + + @RestResource( + defaultRequestHeaders={" Accept : text/s2 "," Content-Type : text/p2 "}, + parsers={P1.class,P2.class}, serializers={S1.class,S2.class} + ) + public static class B { + @RestMethod(name=PUT, parsers=P3.class, serializers=S3.class) + public String b(@Body String in) { + return in; + } + } + + private static MockRest b = MockRest.create(B.class); + + @Test + public void b01_restMethodWithParsersSerializers_valid() throws Exception { + b.request("PUT", "/").accept("text/s3").contentType("text/p3").execute().assertBody("s3/p3"); + } + + @Test + public void b02_restMethodWithParsersSerializers_invalid() throws Exception { + b.request("PUT", "?noTrace=true").execute() + .assertStatus(415) + .assertBodyContains( + "Unsupported media-type in request header 'Content-Type': 'text/p2'", + "Supported media-types: ['text/p3']" + ); + b.request("PUT", "?noTrace=true").accept("text/s1").execute() + .assertStatus(415) + .assertBodyContains( + "Unsupported media-type in request header 'Content-Type': 'text/p2'", + "Supported media-types: ['text/p3']" + ); + b.request("PUT", "?noTrace=true").contentType("text/p1").execute() + .assertStatus(415) + .assertBodyContains( + "Unsupported media-type in request header 'Content-Type': 'text/p1'", + "Supported media-types: ['text/p3']" + ); + b.request("PUT", "?noTrace=true").accept("text/s1").contentType("text/p1").execute() + .assertStatus(415) + .assertBodyContains( + "Unsupported media-type in request header 'Content-Type': 'text/p1'", + "Supported media-types: ['text/p3']" + ); + b.request("PUT", "?noTrace=true").accept("text/s1").accept("text/s2").execute() + .assertStatus(415) + .assertBodyContains( + "Unsupported media-type in request header 'Content-Type': 'text/p2'", + "Supported media-types: ['text/p3']" + ); + b.request("PUT", "?noTrace=true").accept("text/s1").contentType("text/p2").execute() + .assertStatus(415) + .assertBodyContains( + "Unsupported media-type in request header 'Content-Type': 'text/p2'", + "Supported media-types: ['text/p3']" + ); + b.request("PUT", "?noTrace=true").accept("text/s2").contentType("text/p2").execute() + .assertStatus(415) + .assertBodyContains( + "Unsupported media-type in request header 'Content-Type': 'text/p2'", + "Supported media-types: ['text/p3']" + ); + b.request("PUT", "?noTrace=true").accept("text/s3").execute() + .assertStatus(415) + .assertBodyContains( + "Unsupported media-type in request header 'Content-Type': 'text/p2'", + "Supported media-types: ['text/p3']" + ); + b.request("PUT", "?noTrace=true").contentType("text/p3").execute() + .assertStatus(406) + .assertBodyContains( + "Unsupported media-type in request header 'Accept': 'text/s2'", + "Supported media-types: ['text/s3']" + ); + } + + + //==================================================================================================== + // Test that default Accept and Content-Type headers on servlet annotation are picked up + // when @RestMethod.addParsers/addSerializers annotations are used. + //==================================================================================================== + + @RestResource( + defaultRequestHeaders={" Accept : text/s2 "," Content-Type : text/p2 "}, + parsers={P1.class,P2.class}, serializers={S1.class,S2.class} + ) + public static class C { + @RestMethod(name=PUT, parsers=P3.class, serializers=S3.class, inherit="SERIALIZERS,PARSERS") + public String c(@Body String in) { + return in; + } + } + + private static MockRest c = MockRest.create(C.class); + + @Test + public void c01_restMethodAddParsersSerializersInherit() throws Exception { + c.request("PUT", "/").execute().assertBody("s2/p2"); + c.request("PUT", "/").accept("text/s1").execute().assertBody("s1/p2"); + c.request("PUT", "/").contentType("text/p1").execute().assertBody("s2/p1"); + c.request("PUT", "/").accept("text/s1").contentType("text/p1").execute().assertBody("s1/p1"); + c.request("PUT", "/").accept("text/s2").execute().assertBody("s2/p2"); + c.request("PUT", "/").contentType("text/p2").execute().assertBody("s2/p2"); + c.request("PUT", "/").accept("text/s2").contentType("text/p2").execute().assertBody("s2/p2"); + c.request("PUT", "/").accept("text/s3").execute().assertBody("s3/p2"); + c.request("PUT", "/").contentType("text/p3").execute().assertBody("s2/p3"); + c.request("PUT", "/").accept("text/s3").contentType("text/p3").execute().assertBody("s3/p3"); + } + + @Test + public void c02_restMethodAddParsersSerializersInherit_invalid() throws Exception { + c.request("PUT", "?noTrace=true").contentType("text/p4").execute() + .assertStatus(415) + .assertBodyContains( + "Unsupported media-type in request header 'Content-Type': 'text/p4'", + "Supported media-types: ['text/p3','text/p1','text/p2']" + ); + c.request("PUT", "?noTrace=true").accept("text/s4").execute() + .assertStatus(406) + .assertBodyContains( + "Unsupported media-type in request header 'Accept': 'text/s4'", + "Supported media-types: ['text/s3','text/s1','text/s2']" + ); + } + + + //==================================================================================================== + // Various Accept incantations. + //==================================================================================================== + @RestResource( + defaultRequestHeaders={" Accept : text/s2 "," Content-Type : text/p2 "}, + parsers={P1.class,P2.class}, serializers={S1.class,S2.class} + ) + public static class D { + @RestMethod(name=PUT) + public String d(@Body String in) { + return in; + } + } + + private static MockRest d = MockRest.create(D.class); + + @Test + public void d01_accept_valid() throws Exception { + // "*/*" should match the first serializer, not the default serializer. + d.request("PUT", "/").contentType("text/p1").accept("*/*").execute().assertBody("s1/p1"); + // "text/*" should match the first serializer, not the default serializer. + d.request("PUT", "/").contentType("text/p1").accept("text/*").execute().assertBody("s1/p1"); + d.request("PUT", "/").contentType("text/p1").accept("bad/*,text/*").execute().assertBody("s1/p1"); + d.request("PUT", "/").contentType("text/p1").accept("text/*,bad/*").execute().assertBody("s1/p1"); + d.request("PUT", "/").contentType("text/p1").accept("text/s1;q=0.5,text/s2").execute().assertBody("s2/p1"); + d.request("PUT", "/").contentType("text/p1").accept("text/s1,text/s2;q=0.5").execute().assertBody("s1/p1"); + } + @Test + public void d02_accept_invalid() throws Exception { + d.request("PUT", "?noTrace=true").contentType("text/p1").accept("bad/*").execute() + .assertStatus(406) + .assertBodyContains( + "Unsupported media-type in request header 'Accept': 'bad/*'", + "Supported media-types: ['text/s1','text/s2']" + ); + } + + + //==================================================================================================== + // Test that default Accept and Content-Type headers on method annotation are picked up + // when @RestMethod.parsers/serializers annotations are used. + //==================================================================================================== + @RestResource( + defaultRequestHeaders={" Accept : text/s2 "," Content-Type : text/p2 "}, + parsers={P1.class,P2.class}, serializers={S1.class,S2.class} + ) + public static class E { + @RestMethod(name=PUT, defaultRequestHeaders={"Accept: text/s3","Content-Type: text/p3"}, parsers=P3.class, serializers=S3.class) + public String e(@Body String in) { + return in; + } + } + + private static MockRest e = MockRest.create(E.class); + + @Test + public void e01_restMethodParserSerializerAnnotations_valid() throws Exception { + e.request("PUT", "/").execute().assertBody("s3/p3"); + e.request("PUT", "/").accept("text/s3").execute().assertBody("s3/p3"); + e.request("PUT", "/").contentType("text/p3").execute().assertBody("s3/p3"); + e.request("PUT", "/").accept("text/s3").contentType("text/p3").execute().assertBody("s3/p3"); + } + @Test + public void e02_restMethodParserSerializerAnnotations_invalid() throws Exception { + e.request("PUT", "?noTrace=true").accept("text/s1").execute() + .assertStatus(406) + .assertBodyContains( + "Unsupported media-type in request header 'Accept': 'text/s1'", + "Supported media-types: ['text/s3']" + ); + e.request("PUT", "?noTrace=true").contentType("text/p1").execute() + .assertStatus(415) + .assertBodyContains( + "Unsupported media-type in request header 'Content-Type': 'text/p1'", + "Supported media-types: ['text/p3']" + ); + e.request("PUT", "?noTrace=true").accept("text/s1").contentType("text/p1").execute() + .assertStatus(415) + .assertBodyContains( + "Unsupported media-type in request header 'Content-Type': 'text/p1'", + "Supported media-types: ['text/p3']" + ); + e.request("PUT", "?noTrace=true").accept("text/s2").execute() + .assertStatus(406) + .assertBodyContains( + "Unsupported media-type in request header 'Accept': 'text/s2'", + "Supported media-types: ['text/s3']" + ); + e.request("PUT", "?noTrace=true").contentType("text/p2").execute() + .assertStatus(415) + .assertBodyContains( + "Unsupported media-type in request header 'Content-Type': 'text/p2'", + "Supported media-types: ['text/p3']" + ); + e.request("PUT", "?noTrace=true").accept("text/s2").contentType("text/p2").execute() + .assertStatus(415) + .assertBodyContains( + "Unsupported media-type in request header 'Content-Type': 'text/p2'", + "Supported media-types: ['text/p3']" + ); + } + + + //==================================================================================================== + // Test that default Accept and Content-Type headers on method annotation are picked up + // when @RestMethod.addParsers/addSerializers annotations are used. + //==================================================================================================== + @RestResource( + defaultRequestHeaders={" Accept : text/s2 "," Content-Type : text/p2 "}, + parsers={P1.class,P2.class}, serializers={S1.class,S2.class} + ) + public static class F { + @RestMethod(name=PUT, defaultRequestHeaders={"Accept: text/s3","Content-Type: text/p3"}, parsers=P3.class, serializers=S3.class, inherit="SERIALIZERS,PARSERS") + public String f(@Body String in) { + return in; + } + } + + private static MockRest f = MockRest.create(F.class); + + @Test + public void f01_restMethodAddParsersSerializersAnnotations_valid() throws Exception { + f.request("PUT", "/").execute().assertBody("s3/p3"); + f.request("PUT", "/").accept("text/s1").execute().assertBody("s1/p3"); + f.request("PUT", "/").contentType("text/p1").execute().assertBody("s3/p1"); + f.request("PUT", "/").accept("text/s1").contentType("text/p1").execute().assertBody("s1/p1"); + f.request("PUT", "/").accept("text/s2").execute().assertBody("s2/p3"); + f.request("PUT", "/").contentType("text/p2").execute().assertBody("s3/p2"); + f.request("PUT", "/").accept("text/s2").contentType("text/p2").execute().assertBody("s2/p2"); + f.request("PUT", "/").accept("text/s3").execute().assertBody("s3/p3"); + f.request("PUT", "/").contentType("text/p3").execute().assertBody("s3/p3"); + f.request("PUT", "/").accept("text/s3").contentType("text/p3").execute().assertBody("s3/p3"); + } +} diff --git a/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/annotation/StatusCodesTest.java b/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/annotation/StatusCodesTest.java new file mode 100644 index 0000000..546844a --- /dev/null +++ b/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/annotation/StatusCodesTest.java @@ -0,0 +1,48 @@ +// *************************************************************************************************************************** +// * 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.annotation; + +import static org.apache.juneau.http.HttpMethodName.*; + +import java.io.*; + +import org.apache.juneau.rest.mock.*; +import org.junit.*; + +/** + * Validates that the correct status codes are returned on REST requests. + */ +@SuppressWarnings("javadoc") +public class StatusCodesTest { + + //================================================================================================================= + // OK + //================================================================================================================= + + @RestResource + public static class A { + @RestMethod(name=PUT) + public Reader a01(@Body String b) { + return new StringReader(b); + } + } + + private static MockRest a = MockRest.create(A.class); + + @Test + public void a01a_OK() throws Exception { + a.request("PUT", "/").body("foo").execute().assertStatus(200); + } + + // TODO - Test all the status codes +} -- To stop receiving notification emails like this one, please contact jamesbog...@apache.org.