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 b15e810 Tests b15e810 is described below commit b15e810fd1862cdbadf2aad38f238f54314c0f79 Author: JamesBognar <jamesbog...@apache.org> AuthorDate: Wed May 9 18:49:19 2018 -0400 Tests --- .../apache/juneau/utils/TransformCacheTest.java | 170 ++++++++++++ .../org/apache/juneau/internal/ClassUtils.java | 98 ++----- .../java/org/apache/juneau/internal/Transform.java | 30 +++ .../org/apache/juneau/internal/TransformCache.java | 186 +++++++++++++ .../apache/juneau/rest/test/NoParserInputTest.java | 13 +- .../org/apache/juneau/rest/RestMethodParam.java | 52 +++- .../org/apache/juneau/rest/RestParamDefaults.java | 300 ++++++++++----------- .../org/apache/juneau/rest/annotation/Body.java | 6 +- .../juneau/rest/BasicRestInfoProviderTest.java | 17 +- .../apache/juneau/rest/annotation/BodyTest.java | 157 +++++++++-- 10 files changed, 740 insertions(+), 289 deletions(-) diff --git a/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/utils/TransformCacheTest.java b/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/utils/TransformCacheTest.java new file mode 100644 index 0000000..49c16ed --- /dev/null +++ b/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/utils/TransformCacheTest.java @@ -0,0 +1,170 @@ +// *************************************************************************************************************************** +// * 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.utils; + +import static org.apache.juneau.internal.TransformCache.*; +import static org.junit.Assert.*; + +import org.junit.*; + +public class TransformCacheTest { + + //----------------------------------------------------------------------------------------------------------------- + // Constructors. + //----------------------------------------------------------------------------------------------------------------- + + public static class A { + private String f; + public A(String f) { + this.f = f; + } + public A(int f) { + this.f = String.valueOf(f); + } + public A(Integer f) { + this.f = String.valueOf(f); + } + } + @Test + public void stringConstructor() { + assertEquals("foo", get(String.class, A.class).transform("foo").f); + } + @Test + public void intConstructor() { + assertEquals("1", get(int.class, A.class).transform(1).f); + } + @Test + public void integerConstructor() { + assertEquals("2", get(Integer.class, A.class).transform(2).f); + } + + //----------------------------------------------------------------------------------------------------------------- + // fromString methods. + //----------------------------------------------------------------------------------------------------------------- + + public static class D1 { + private String f; + public static D1 create(String f) { + D1 d = new D1(); d.f = f; return d; + } + } + @Test + public void fromString_create() { + assertEquals("foo", get(String.class, D1.class).transform("foo").f); + } + + public static class D2 { + private String f; + public static D2 fromString(String f) { + D2 d = new D2(); d.f = f; return d; + } + } + @Test + public void fromString_fromString() { + assertEquals("foo", get(String.class, D2.class).transform("foo").f); + } + + public static class D3 { + private String f; + public static D3 fromValue(String f) { + D3 d = new D3(); d.f = f; return d; + } + } + @Test + public void fromString_fromValue() { + assertEquals("foo", get(String.class, D3.class).transform("foo").f); + } + + public static class D4 { + private String f; + public static D4 valueOf(String f) { + D4 d = new D4(); d.f = f; return d; + } + } + @Test + public void fromString_valueOf() { + assertEquals("foo", get(String.class, D4.class).transform("foo").f); + } + + public static class D5 { + private String f; + public static D5 parse(String f) { + D5 d = new D5(); d.f = f; return d; + } + } + @Test + public void fromString_parse() { + assertEquals("foo", get(String.class, D5.class).transform("foo").f); + } + + public static class D6 { + private String f; + public static D6 parseString(String f) { + D6 d = new D6(); d.f = f; return d; + } + } + @Test + public void fromString_parseString() { + assertEquals("foo", get(String.class, D6.class).transform("foo").f); + } + + public static class D7 { + private String f; + public static D7 forName(String f) { + D7 d = new D7(); d.f = f; return d; + } + } + @Test + public void fromString_forName() { + assertEquals("foo", get(String.class, D7.class).transform("foo").f); + } + + public static class D8 { + private String f; + public static D8 forString(String f) { + D8 d = new D8(); d.f = f; return d; + } + } + @Test + public void fromString_forString() { + assertEquals("foo", get(String.class, D8.class).transform("foo").f); + } + + //----------------------------------------------------------------------------------------------------------------- + // fromX methods. + //----------------------------------------------------------------------------------------------------------------- + + public static class X {} + + public static class E1 { + private String f; + public static E1 create(X x) { + E1 e = new E1(); e.f = "ok"; return e; + } + } + @Test + public void fromX_create() { + assertEquals("ok", get(X.class, E1.class).transform(new X()).f); + } + + public static class E2 { + private String f; + public static E2 fromX(X x) { + E2 e = new E2(); e.f = "ok"; return e; + } + } + @Test + public void fromX_fromX() { + assertEquals("ok", get(X.class, E2.class).transform(new X()).f); + } +} diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/ClassUtils.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/ClassUtils.java index 7ebbe94..da6c49b 100644 --- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/ClassUtils.java +++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/ClassUtils.java @@ -28,31 +28,6 @@ import org.apache.juneau.utils.*; public final class ClassUtils { private static final Map<Class<?>,ConstructorCacheEntry> CONSTRUCTOR_CACHE = new ConcurrentHashMap<>(); - private static final Map<Class<?>,Stringify<?>> STRINGIFY_CACHE = new ConcurrentHashMap<>(); - - // Special cases. - static { - - // TimeZone doesn't follow any standard conventions. - STRINGIFY_CACHE.put(TimeZone.class, new Stringify<TimeZone>(TimeZone.class){ - @Override - public TimeZone fromString(String s) { - return TimeZone.getTimeZone(s); - } - @Override - public String toString(TimeZone tz) { - return tz.getID(); - } - }); - - // Locale(String) doesn't work on strings like "ja_JP". - STRINGIFY_CACHE.put(Locale.class, new Stringify<Locale>(Locale.class){ - @Override - public Locale fromString(String s) { - return Locale.forLanguageTag(s.replace('_', '-')); - } - }); - } /** * Given the specified list of objects, return readable names for the class types of the objects. @@ -1897,6 +1872,21 @@ public final class ClassUtils { } /** + * Find the public static creator method on the specified class. + * + * @param oc The created type. + * @param ic The argument type. + * @param name The method name. + * @return The static method, or <jk>null</jk> if it couldn't be found. + */ + public static Method findPublicStaticCreateMethod(Class<?> oc, Class<?> ic, String name) { + for (Method m : oc.getMethods()) + if (isAll(m, STATIC, PUBLIC, NOT_DEPRECATED) && hasName(m, name) && hasReturnType(m, oc) && hasArgs(m, ic)) + return m; + return null; + } + + /** * Constructs a new instance of the specified class from the specified string. * * <p> @@ -1912,9 +1902,9 @@ public final class ClassUtils { * @param s The string to create the instance from. * @return A new object instance, or <jk>null</jk> if a method for converting the string to an object could not be found. */ - @SuppressWarnings({ "unchecked" }) public static <T> T fromString(Class<T> c, String s) { - return (T)getStringify(c).fromString(s); + Transform<String,T> t = TransformCache.get(String.class, c); + return t == null ? null : t.transform(s); } /** @@ -1932,7 +1922,8 @@ public final class ClassUtils { public static String toString(Object o) { if (o == null) return null; - return getStringify(o.getClass()).toString(o); + Transform<Object,String> t = (Transform<Object,String>)TransformCache.get(o.getClass(), String.class); + return t == null ? o.toString() : t.transform(o); } /** @@ -1992,57 +1983,6 @@ public final class ClassUtils { } } - @SuppressWarnings({ "unchecked", "rawtypes" }) - private static Stringify getStringify(Class c) { - Stringify fs = STRINGIFY_CACHE.get(c); - if (fs == null) { - for (Iterator<Class<?>> i = getParentClasses(c, false, true); i.hasNext(); ) { - Class c2 = i.next(); - fs = STRINGIFY_CACHE.get(c2); - if (fs != null) { - STRINGIFY_CACHE.put(c, fs); - break; - } - } - if (fs == null) { - fs = new Stringify(c); - STRINGIFY_CACHE.put(c, fs); - } - } - return fs; - } - - @SuppressWarnings({"unchecked","rawtypes"}) - private static class Stringify<T> { - final Constructor<?> constructor; - final Method fromStringMethod; - final Class<? extends Enum> enumClass; - - Stringify(Class<?> c) { - enumClass = c.isEnum() ? (Class<? extends Enum>)c : null; - fromStringMethod = enumClass != null ? null : findPublicFromStringMethod(c); - constructor = enumClass != null || fromStringMethod != null ? null : findPublicConstructor(c, String.class); - } - - public T fromString(String s) { - try { - if (fromStringMethod != null) - return (T)fromStringMethod.invoke(null, s); - if (constructor != null) - return (T)constructor.newInstance(s); - if (enumClass != null) - return (T)Enum.valueOf(enumClass, s); - return null; - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - public String toString(T t) { - return t.toString(); - } - } - /** * Returns the simple name of a class. * diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/Transform.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/Transform.java new file mode 100644 index 0000000..9d673aa --- /dev/null +++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/Transform.java @@ -0,0 +1,30 @@ +// *************************************************************************************************************************** +// * 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.internal; + +/** + * An interface for creating objects from other objects such as a <code>String</code> or <code>Reader</code>. + * + * @param <I> Input type. + * @param <O> Output type. + */ +public interface Transform<I,O> { + + /** + * Method for instantiating an object from another object. + * + * @param in The input object. + * @return The output object. + */ + public O transform(I in); +} diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/TransformCache.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/TransformCache.java new file mode 100644 index 0000000..0869e05 --- /dev/null +++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/TransformCache.java @@ -0,0 +1,186 @@ +// *************************************************************************************************************************** +// * 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.internal; + +import java.util.concurrent.*; +import java.lang.reflect.*; +import java.util.*; + +/** + * Cache of object that convert POJOs to and from common types such as strings, readers, and input streams. + */ +public class TransformCache { + private static final ConcurrentHashMap<Class<?>,Map<Class<?>,Transform<?,?>>> CACHE = new ConcurrentHashMap<>(); + + /** + * Represents a non-existent transform. + */ + public static final Transform<Object,Object> NULL = new Transform<Object,Object>() { + @Override + public Object transform(Object in) { + return null; + } + }; + + // Special cases. + static { + + // TimeZone doesn't follow any standard conventions. + add(String.class, TimeZone.class, + new Transform<String,TimeZone>() { + @Override public TimeZone transform(String in) { + return TimeZone.getTimeZone(in); + } + } + ); + add(TimeZone.class, String.class, + new Transform<TimeZone,String>() { + @Override public String transform(TimeZone in) { + return in.getID(); + } + } + ); + + // Locale(String) doesn't work on strings like "ja_JP". + add(String.class, Locale.class, + new Transform<String,Locale>() { + @Override + public Locale transform(String in) { + return Locale.forLanguageTag(in.replace('_', '-')); + } + } + ); + } + + /** + * Adds a transform for the specified input/output types. + * + * @param ic The input type. + * @param oc The output type. + * @param t The transform for converting the input to the output. + */ + public static synchronized void add(Class<?> ic, Class<?> oc, Transform<?,?> t) { + Map<Class<?>,Transform<?,?>> m = CACHE.get(oc); + if (m == null) { + m = new ConcurrentHashMap<>(); + CACHE.put(oc, m); + } + m.put(ic, t); + } + + /** + * Returns the transform for converting the specified input type to the specified output type. + * + * @param ic The input type. + * @param oc The output type. + * @return The transform for performing the conversion, or <jk>null</jk> if the conversion cannot be made. + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static <I,O> Transform<I,O> get(final Class<I> ic, final Class<O> oc) { + + if (ic == null || oc == null) + return null; + + Map<Class<?>,Transform<?,?>> m = CACHE.get(oc); + if (m == null) { + m = new ConcurrentHashMap<>(); + CACHE.putIfAbsent(oc, m); + m = CACHE.get(oc); + } + + Transform t = m.get(ic); + if (t != null) + return t == NULL ? null : t; + + for (Iterator<Class<?>> i = ClassUtils.getParentClasses(ic, false, true); i.hasNext(); ) { + Class pic = i.next(); + t = m.get(pic); + if (t != null) { + m.put(pic, t); + return t == NULL ? null : t; + } + } + + if (ic == oc) { + t = new Transform<I,O>() { + @Override public O transform(I in) { + return (O)in; + } + }; + } else if (ic == String.class) { + if (oc.isEnum()) { + t = new Transform<String,O>() { + @Override + public O transform(String in) { + return (O)Enum.valueOf((Class<? extends Enum>)oc, in); + } + }; + } else { + final Method fromStringMethod = ClassUtils.findPublicFromStringMethod(oc); + if (fromStringMethod != null) { + t = new Transform<String,O>() { + @Override + public O transform(String in) { + try { + return (O)fromStringMethod.invoke(null, in); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + }; + } + } + } + + if (t == null) { + Method createMethod = ClassUtils.findPublicStaticCreateMethod(oc, ic, "create"); + if (createMethod == null) + createMethod = ClassUtils.findPublicStaticCreateMethod(oc, ic, "from" + ic.getSimpleName()); + if (createMethod != null) { + final Method cm = createMethod; + t = new Transform<I,O>() { + @Override + public O transform(I in) { + try { + return (O)cm.invoke(null, in); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + }; + } else { + final Constructor<?> c = ClassUtils.findPublicConstructor(oc, ic); + if (c != null && ! ClassUtils.isDeprecated(c)) { + t = new Transform<I,O>() { + @Override + public O transform(I in) { + try { + return (O)c.newInstance(in); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + }; + } + + } + } + + if (t == null) + t = NULL; + + m.put(ic, t); + + return t == NULL ? null : t; + } +} diff --git a/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/NoParserInputTest.java b/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/NoParserInputTest.java index 0c9b2a3..fa0ce0a 100644 --- a/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/NoParserInputTest.java +++ b/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/NoParserInputTest.java @@ -12,8 +12,6 @@ // *************************************************************************************************************************** package org.apache.juneau.rest.test; -import static javax.servlet.http.HttpServletResponse.*; -import static org.apache.juneau.rest.test.TestUtils.*; import static org.junit.Assert.*; import org.apache.juneau.rest.client.*; @@ -45,17 +43,10 @@ public class NoParserInputTest extends RestTestcase { //==================================================================================================== // @Body annotated PushbackReader. - // This should always fail since the servlet reader is not a pushback reader. //==================================================================================================== @Test public void testPushbackReader() throws Exception { - try { - plainTextClient.doPut(URL + "/testPushbackReader?noTrace=true", "foo").getResponseAsString(); - fail("Exception expected"); - } catch (RestCallException e) { - checkErrorResponse(debug, e, SC_BAD_REQUEST, - "Invalid argument type passed to the following method:", - "'public java.lang.String org.apache.juneau.rest.test.NoParserInputResource.testPushbackReader(java.io.PushbackReader) throws java.lang.Exception'"); - } + String r = plainTextClient.doPut(URL + "/testPushbackReader", "foo").getResponseAsString(); + assertEquals("foo", r); } } diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethodParam.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethodParam.java index ebaf289..31692dc 100644 --- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethodParam.java +++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethodParam.java @@ -115,38 +115,67 @@ import org.apache.juneau.utils.*; public abstract class RestMethodParam { final RestParamType paramType; + final Method method; final String name; final Type type; + final Class<?> c; final ObjectMap metaData; /** * Constructor. * * @param paramType The Swagger parameter type. + * @param method The method on which the parameter resides. + * @param name + * The parameter name. + * Can be <jk>null</jk> if parameter doesn't have a name (e.g. the request body). + * @param type The object type to convert the parameter to. + * @param metaData Swagger metadata. + */ + protected RestMethodParam(RestParamType paramType, Method method, String name, Type type, ObjectMap metaData) { + this.paramType = paramType; + this.method = method; + this.name = name; + this.type = type; + this.c = type instanceof Class ? (Class<?>)type : type instanceof ParameterizedType ? (Class<?>)((ParameterizedType)type).getRawType() : null; + this.metaData = metaData; + } + + /** + * Constructor. + * + * @param paramType The Swagger parameter type. + * @param type The object type to convert the parameter to. + */ + protected RestMethodParam(RestParamType paramType, Type type) { + this(paramType, null, null, type, ObjectMap.EMPTY_MAP); + } + + /** + * Constructor. + * + * @param paramType The Swagger parameter type. * @param name * The parameter name. * Can be <jk>null</jk> if parameter doesn't have a name (e.g. the request body). * @param type The object type to convert the parameter to. */ protected RestMethodParam(RestParamType paramType, String name, Type type) { - this(paramType, name, type, ObjectMap.EMPTY_MAP); + this(paramType, null, name, type, ObjectMap.EMPTY_MAP); } /** * Constructor. * * @param paramType The Swagger parameter type. + * @param method The method on which the parameter resides. * @param name * The parameter name. * Can be <jk>null</jk> if parameter doesn't have a name (e.g. the request body). * @param type The object type to convert the parameter to. - * @param metaData Swagger metadata. */ - protected RestMethodParam(RestParamType paramType, String name, Type type, ObjectMap metaData) { - this.paramType = paramType; - this.name = name; - this.type = type; - this.metaData = metaData; + protected RestMethodParam(RestParamType paramType, Method method, String name, Type type) { + this(paramType, method, name, type, ObjectMap.EMPTY_MAP); } /** @@ -207,6 +236,15 @@ public abstract class RestMethodParam { } /** + * Returns the parameter class type. + * + * @return the parameter class type. + */ + public Class<?> getTypeClass() { + return c; + } + + /** * Performs validation on the parameter. * * @throws InternalServerError 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 aef5e5b..a0a33c0 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 @@ -145,7 +145,7 @@ class RestParamDefaults { static final class HttpServletRequestObject extends RestMethodParam { protected HttpServletRequestObject() { - super(OTHER, null, HttpServletRequest.class); + super(OTHER, HttpServletRequest.class); } @Override /* RestMethodParam */ @@ -157,7 +157,7 @@ class RestParamDefaults { static final class HttpServletResponseObject extends RestMethodParam { protected HttpServletResponseObject() { - super(OTHER, null, HttpServletResponse.class); + super(OTHER, HttpServletResponse.class); } @Override /* RestMethodParam */ @@ -169,7 +169,7 @@ class RestParamDefaults { static final class RestRequestObject extends RestMethodParam { protected RestRequestObject() { - super(OTHER, null, RestRequest.class); + super(OTHER, RestRequest.class); } @Override /* RestMethodParam */ @@ -181,7 +181,7 @@ class RestParamDefaults { static final class RestResponseObject extends RestMethodParam { protected RestResponseObject() { - super(OTHER, null, RestResponse.class); + super(OTHER, RestResponse.class); } @Override /* RestMethodParam */ @@ -547,11 +547,15 @@ class RestParamDefaults { //------------------------------------------------------------------------------------------------------------------- static final class PathObject extends RestMethodParam { - private final Method method; protected PathObject(Method method, Path a, Type type, PropertyStore ps, RestMethodParam existing) { - super(PATH, firstNonEmpty(a.name(), a.value(), existing == null ? null : existing.name), type, getMetaData(a, castOrNull(existing, PathObject.class))); - this.method = method; + super(PATH, method, firstNonEmpty(a.name(), a.value(), existing == null ? null : existing.name), type, getMetaData(a, castOrNull(existing, PathObject.class))); + } + + @Override /* RestMethodParam */ + public void validate() throws InternalServerError { + if (isEmpty(name)) + throw new InternalServerError("@Path used without name or value on method ''{0}''.", method); } @Override /* RestMethodParam */ @@ -581,22 +585,25 @@ class RestParamDefaults { .appendSkipEmpty("example", joinnl(a.example())) ; } - - @Override /* RestMethodParqm */ - public void validate() throws InternalServerError { - if (isEmpty(name)) - throw new InternalServerError("@Path used without name or value on method ''{0}''.", method); - } } 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, null, type, getMetaData(a, castOrNull(existing, BodyObject.class))); + 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); } @@ -633,16 +640,20 @@ class RestParamDefaults { } static final class HeaderObject extends RestMethodParam { - private final Method method; private final HttpPartParser partParser; protected HeaderObject(Method method, Header a, Type type, PropertyStore ps, RestMethodParam existing) { - super(HEADER, firstNonEmpty(a.name(), a.value(), existing == null ? null : existing.name), type, getMetaData(a, castOrNull(existing, HeaderObject.class))); - this.method = method; + super(HEADER, method, firstNonEmpty(a.name(), a.value(), existing == null ? null : existing.name), type, getMetaData(a, castOrNull(existing, HeaderObject.class))); this.partParser = a.parser() == HttpPartParser.Null.class ? null : ClassUtils.newInstance(HttpPartParser.class, a.parser(), true, ps); } @Override /* RestMethodParam */ + public void validate() throws InternalServerError { + if (isEmpty(name)) + throw new InternalServerError("@Header used without name or value on method ''{0}''.", method); + } + + @Override /* RestMethodParam */ public Object resolve(RestRequest req, RestResponse res) throws Exception { return req.getHeaders().get(partParser, name, type); } @@ -676,51 +687,42 @@ class RestParamDefaults { .appendSkipEmpty("example", joinnl(a.example())) ; } - - @Override /* RestMethodParqm */ - public void validate() throws InternalServerError { - if (isEmpty(name)) - throw new InternalServerError("@Header used without name or value on method ''{0}''.", method); - } } static final class ResponseHeaderObject extends RestMethodParam { - private final Method method; final HttpPartSerializer partSerializer; protected ResponseHeaderObject(Method method, ResponseHeader a, Type type, PropertyStore ps, RestMethodParam existing) { - super(RESPONSE_HEADER, firstNonEmpty(a.name(), a.value(), existing == null ? null : existing.name), type, getMetaData(a, castOrNull(existing, ResponseHeaderObject.class))); - this.method = method; + super(RESPONSE_HEADER, method, firstNonEmpty(a.name(), a.value(), existing == null ? null : existing.name), type, getMetaData(a, castOrNull(existing, ResponseHeaderObject.class))); this.partSerializer = a.serializer() == HttpPartSerializer.Null.class ? null : ClassUtils.newInstance(HttpPartSerializer.class, a.serializer(), true, ps); } + @Override /* RestMethodParam */ + public void validate() throws InternalServerError { + if (isEmpty(name)) + throw new InternalServerError("@ResponseHeader used without name or value on method ''{0}''.", method); + if (getTypeClass() == null) + throw new InternalServerError("Invalid type {0} specified with @ResponseHeader annotation. It must be a subclass of Value.", type); + if (ClassUtils.findNoArgConstructor(getTypeClass(), Visibility.PUBLIC) == null) + throw new InternalServerError("Invalid type {0} specified with @ResponseHeader annotation. It must have a public no-arg constructor.", type); + } + @SuppressWarnings({ "unchecked", "rawtypes" }) @Override /* RestMethodParam */ public Object resolve(RestRequest req, final RestResponse res) throws Exception { - if (type instanceof Class) { - Class<?> c = (Class<?>)type; - if (ClassUtils.isParentClass(Value.class, c)) { - try { - Value<Object> v = (Value<Object>)c.newInstance(); - v.listener(new ValueListener() { - @Override - public void onSet(Object newValue) { - res.setHeader(name, partSerializer.serialize(HttpPartType.HEADER, newValue)); - } - }); - String def = getMetaData().getString("default"); - if (def != null) { - Class<?> pc = ClassUtils.resolveParameterType(Value.class, 0, c); - v.set(JsonParser.DEFAULT.parse(def, req.getBeanSession().getClassMeta(pc))); - } - - return v; - } catch (Exception e) { - throw new RestException(500, "Invalid type {0} specified with @ResponseHeader annotation. It must have a public no-arg constructor.", type); - } + Value<Object> v = (Value<Object>)getTypeClass().newInstance(); + v.listener(new ValueListener() { + @Override + public void onSet(Object newValue) { + res.setHeader(name, partSerializer.serialize(HttpPartType.HEADER, newValue)); } + }); + String def = getMetaData().getString("default"); + if (def != null) { + Class<?> pc = ClassUtils.resolveParameterType(Value.class, 0, c); + v.set(JsonParser.DEFAULT.parse(def, req.getBeanSession().getClassMeta(pc))); } - throw new RestException(500, "Invalid type {0} specified with @ResponseHeader annotation. It must be a subclass of Value.", type); + return v; } public HttpPartSerializer getPartSerializer() { @@ -763,47 +765,38 @@ class RestParamDefaults { } return om; } - - @Override /* RestMethodParqm */ - public void validate() throws InternalServerError { - if (isEmpty(name)) - throw new InternalServerError("@ResponseHeader used without name or value on method ''{0}''.", method); - } } static final class ResponseObject extends RestMethodParam { protected ResponseObject(Method method, Response a, Type type, PropertyStore ps, RestMethodParam existing) { - super(RESPONSE, "body", type, getMetaData(a, castOrNull(existing, ResponseObject.class))); + super(RESPONSE, method, "body", type, getMetaData(a, castOrNull(existing, ResponseObject.class))); + } + + @Override /* RestMethodParam */ + public void validate() throws InternalServerError { + if (getTypeClass() == null) + throw new InternalServerError("Invalid type {0} specified with @Response annotation. It must be a subclass of Value.", type); + if (ClassUtils.findNoArgConstructor(getTypeClass(), Visibility.PUBLIC) == null) + throw new InternalServerError("Invalid type {0} specified with @Response annotation. It must have a public no-arg constructor.", type); } @SuppressWarnings({ "unchecked", "rawtypes" }) @Override /* RestMethodParam */ public Object resolve(RestRequest req, final RestResponse res) throws Exception { - if (type instanceof Class) { - Class<?> c = (Class<?>)type; - if (ClassUtils.isParentClass(Value.class, c)) { - try { - Value<Object> v = (Value<Object>)c.newInstance(); - v.listener(new ValueListener() { - @Override - public void onSet(Object newValue) { - res.setOutput(newValue); - } - }); - String def = getMetaData().getString("default"); - if (def != null) { - Class<?> pc = ClassUtils.resolveParameterType(Value.class, 0, c); - v.set(JsonParser.DEFAULT.parse(def, req.getBeanSession().getClassMeta(pc))); - } - - return v; - } catch (Exception e) { - throw new RestException(500, "Invalid type {0} specified with @ResponseHeader annotation. It must have a public no-arg constructor.", type); - } + Value<Object> v = (Value<Object>)c.newInstance(); + v.listener(new ValueListener() { + @Override + public void onSet(Object newValue) { + res.setOutput(newValue); } + }); + String def = getMetaData().getString("default"); + if (def != null) { + Class<?> pc = ClassUtils.resolveParameterType(Value.class, 0, c); + v.set(JsonParser.DEFAULT.parse(def, req.getBeanSession().getClassMeta(pc))); } - throw new RestException(500, "Invalid type {0} specified with @ResponseHeader annotation. It must be a subclass of Value.", type); + return v; } private static ObjectMap getMetaData(Response a, ResponseObject existing) { @@ -826,36 +819,33 @@ class RestParamDefaults { static final class ResponseStatusObject extends RestMethodParam { protected ResponseStatusObject(Method method, Status a, Type type, PropertyStore ps, RestMethodParam existing) { - super(RESPONSE_STATUS, "", type, getMetaData(a, castOrNull(existing, ResponseStatusObject.class))); + super(RESPONSE_STATUS, method, "", type, getMetaData(a, castOrNull(existing, ResponseStatusObject.class))); + } + + @Override /* RestMethodParam */ + public void validate() throws InternalServerError { + if (getTypeClass() == null) + throw new InternalServerError("Invalid type {0} specified with @ResponseStatus annotation. It must be a subclass of Value.", type); + if (ClassUtils.findNoArgConstructor(getTypeClass(), Visibility.PUBLIC) == null) + throw new InternalServerError("Invalid type {0} specified with @ResponseStatus annotation. It must have a public no-arg constructor.", type); } @SuppressWarnings({ "unchecked", "rawtypes" }) @Override /* RestMethodParam */ public Object resolve(RestRequest req, final RestResponse res) throws Exception { - if (type instanceof Class) { - Class<?> c = (Class<?>)type; - if (ClassUtils.isParentClass(Value.class, c)) { - try { - Value<Object> v = (Value<Object>)c.newInstance(); - v.listener(new ValueListener() { - @Override - public void onSet(Object newValue) { - res.setStatus(Integer.parseInt(newValue.toString())); - } - }); - String def = getMetaData().getString("default"); - if (def != null) { - Class<?> pc = ClassUtils.resolveParameterType(Value.class, 0, c); - v.set(JsonParser.DEFAULT.parse(def, req.getBeanSession().getClassMeta(pc))); - } - - return v; - } catch (Exception e) { - throw new RestException(500, "Invalid type {0} specified with @Status annotation. It must have a public no-arg constructor.", type); - } + Value<Object> v = (Value<Object>)c.newInstance(); + v.listener(new ValueListener() { + @Override + public void onSet(Object newValue) { + res.setStatus(Integer.parseInt(newValue.toString())); } + }); + String def = getMetaData().getString("default"); + if (def != null) { + Class<?> pc = ClassUtils.resolveParameterType(Value.class, 0, c); + v.set(JsonParser.DEFAULT.parse(def, req.getBeanSession().getClassMeta(pc))); } - throw new RestException(500, "Invalid type {0} specified with @Status annotation. It must be a subclass of Value.", type); + return v; } private static ObjectMap getMetaData(Status a, ResponseStatusObject existing) { @@ -886,18 +876,22 @@ class RestParamDefaults { } static final class FormDataObject extends RestMethodParam { - private final Method method; private final boolean multiPart; private final HttpPartParser partParser; - private final Type type; protected FormDataObject(Method method, FormData a, Type type, PropertyStore ps, RestMethodParam existing) { - super(FORM_DATA, firstNonEmpty(a.name(), a.value(), existing == null ? null : existing.name), type, getMetaData(a, castOrNull(existing, FormDataObject.class))); - this.method = method; - this.type = type; + super(FORM_DATA, method, firstNonEmpty(a.name(), a.value(), existing == null ? null : existing.name), type, getMetaData(a, castOrNull(existing, FormDataObject.class))); this.multiPart = a.multipart(); this.partParser = a.parser() == HttpPartParser.Null.class ? null : ClassUtils.newInstance(HttpPartParser.class, a.parser(), true, ps); } + + @Override /* RestMethodParam */ + public void validate() throws InternalServerError { + if (isEmpty(name)) + throw new InternalServerError("@FormData used without name or value on method ''{0}''.", method); + if (multiPart && ! isCollection(type)) + throw new InternalServerError("Use of multipart flag on @FormData parameter that's not an array or Collection on method ''{0}''", method); + } @Override /* RestMethodParam */ public Object resolve(RestRequest req, RestResponse res) throws Exception { @@ -935,31 +929,27 @@ class RestParamDefaults { .appendSkipEmpty("example", joinnl(a.example())) ; } - - @Override /* RestMethodParqm */ - public void validate() throws InternalServerError { - if (isEmpty(name)) - throw new InternalServerError("@FormData used without name or value on method ''{0}''.", method); - if (multiPart && ! isCollection(type)) - throw new InternalServerError("Use of multipart flag on @FormData parameter that's not an array or Collection on method ''{0}''", method); - } } static final class QueryObject extends RestMethodParam { private final boolean multiPart; - private final Type type; - private final Method method; private final HttpPartParser partParser; protected QueryObject(Method method, Query a, Type type, PropertyStore ps, RestMethodParam existing) { - super(QUERY, firstNonEmpty(a.name(), a.value(), existing == null ? null : existing.name), type, getMetaData(a, castOrNull(existing, QueryObject.class))); - this.type = type; - this.method = method; + super(QUERY, method, firstNonEmpty(a.name(), a.value(), existing == null ? null : existing.name), type, getMetaData(a, castOrNull(existing, QueryObject.class))); this.multiPart = a.multipart(); this.partParser = a.parser() == HttpPartParser.Null.class ? null : ClassUtils.newInstance(HttpPartParser.class, a.parser(), true, ps); } @Override /* RestMethodParam */ + public void validate() throws InternalServerError { + if (isEmpty(name)) + throw new InternalServerError("@Query used without name or value on method ''{0}''.", method); + if (multiPart && ! isCollection(type)) + throw new InternalServerError("Use of multipart flag on @Query parameter that's not an array or Collection on method ''{0}''", method); + } + + @Override /* RestMethodParam */ public Object resolve(RestRequest req, RestResponse res) throws Exception { if (multiPart) return req.getQuery().getAll(partParser, name, type); @@ -995,20 +985,12 @@ class RestParamDefaults { .appendSkipEmpty("example", joinnl(a.example())) ; } - - @Override /* RestMethodParqm */ - public void validate() throws InternalServerError { - if (isEmpty(name)) - throw new InternalServerError("@Query used without name or value on method ''{0}''.", method); - if (multiPart && ! isCollection(type)) - throw new InternalServerError("Use of multipart flag on @Query parameter that's not an array or Collection on method ''{0}''", method); - } } static final class HasFormDataObject extends RestMethodParam { protected HasFormDataObject(Method method, HasFormData a, Type type) throws ServletException { - super(FORM_DATA, firstNonEmpty(a.name(), a.value()), type); + super(FORM_DATA, method, firstNonEmpty(a.name(), a.value()), type); if (type != Boolean.class && type != boolean.class) throw new RestServletException("Use of @HasForm annotation on parameter that is not a boolean on method ''{0}''", method); } @@ -1023,7 +1005,7 @@ class RestParamDefaults { static final class HasQueryObject extends RestMethodParam { protected HasQueryObject(Method method, HasQuery a, Type type) throws ServletException { - super(QUERY, firstNonEmpty(a.name(), a.value()), type); + super(QUERY, method, firstNonEmpty(a.name(), a.value()), type); if (type != Boolean.class && type != boolean.class) throw new RestServletException("Use of @HasQuery annotation on parameter that is not a boolean on method ''{0}''", method); } @@ -1036,23 +1018,21 @@ class RestParamDefaults { } static final class PathRemainderObject extends RestMethodParam { - - protected PathRemainderObject(Method method, Type type) throws ServletException { - super(OTHER, null, null); - if (type != String.class) - throw new RestServletException("Use of @PathRemainder annotation on parameter that is not a String on method ''{0}''", method); + + protected PathRemainderObject(Method method, Type type) { + super(OTHER, method, null, type); } - + @Override /* RestMethodParam */ public Object resolve(RestRequest req, RestResponse res) throws Exception { - return req.getPathMatch().getRemainder(); + return ClassUtils.fromString(getTypeClass(), req.getPathMatch().getRemainder()); } } static final class RestRequestPropertiesObject extends RestMethodParam { protected RestRequestPropertiesObject() { - super(OTHER, null, RequestProperties.class); + super(OTHER, RequestProperties.class); } @Override /* RestMethodParam */ @@ -1068,7 +1048,7 @@ class RestParamDefaults { static final class ResourceBundleObject extends RestMethodParam { protected ResourceBundleObject() { - super(OTHER, null, ResourceBundle.class); + super(OTHER, ResourceBundle.class); } @Override /* RestMethodParam */ @@ -1080,7 +1060,7 @@ class RestParamDefaults { static final class MessageBundleObject extends RestMethodParam { protected MessageBundleObject() { - super(OTHER, null, MessageBundle.class); + super(OTHER, MessageBundle.class); } @Override /* RestMethodParam */ @@ -1092,7 +1072,7 @@ class RestParamDefaults { static final class InputStreamObject extends RestMethodParam { protected InputStreamObject() { - super(OTHER, null, InputStream.class); + super(OTHER, InputStream.class); } @Override /* RestMethodParam */ @@ -1104,7 +1084,7 @@ class RestParamDefaults { static final class ServletInputStreamObject extends RestMethodParam { protected ServletInputStreamObject() { - super(OTHER, null, ServletInputStream.class); + super(OTHER, ServletInputStream.class); } @Override /* RestMethodParam */ @@ -1116,7 +1096,7 @@ class RestParamDefaults { static final class ReaderObject extends RestMethodParam { protected ReaderObject() { - super(OTHER, null, Reader.class); + super(OTHER, Reader.class); } @Override /* RestMethodParam */ @@ -1128,7 +1108,7 @@ class RestParamDefaults { static final class OutputStreamObject extends RestMethodParam { protected OutputStreamObject() { - super(OTHER, null, OutputStream.class); + super(OTHER, OutputStream.class); } @Override /* RestMethodParam */ @@ -1140,7 +1120,7 @@ class RestParamDefaults { static final class ServletOutputStreamObject extends RestMethodParam { protected ServletOutputStreamObject() { - super(OTHER, null, ServletOutputStream.class); + super(OTHER, ServletOutputStream.class); } @Override /* RestMethodParam */ @@ -1152,7 +1132,7 @@ class RestParamDefaults { static final class WriterObject extends RestMethodParam { protected WriterObject() { - super(OTHER, null, Writer.class); + super(OTHER, Writer.class); } @Override /* RestMethodParam */ @@ -1164,7 +1144,7 @@ class RestParamDefaults { static final class RequestHeadersObject extends RestMethodParam { protected RequestHeadersObject() { - super(OTHER, null, RequestHeaders.class); + super(OTHER, RequestHeaders.class); } @Override /* RestMethodParam */ @@ -1176,7 +1156,7 @@ class RestParamDefaults { static final class RequestQueryObject extends RestMethodParam { protected RequestQueryObject() { - super(OTHER, null, RequestQuery.class); + super(OTHER, RequestQuery.class); } @Override /* RestMethodParam */ @@ -1188,7 +1168,7 @@ class RestParamDefaults { static final class RequestFormDataObject extends RestMethodParam { protected RequestFormDataObject() { - super(OTHER, null, RequestFormData.class); + super(OTHER, RequestFormData.class); } @Override /* RestMethodParam */ @@ -1200,7 +1180,7 @@ class RestParamDefaults { static final class HttpMethodObject extends RestMethodParam { protected HttpMethodObject() { - super(OTHER, null, HttpMethod.class); + super(OTHER, HttpMethod.class); } @Override /* RestMethodParam */ @@ -1212,7 +1192,7 @@ class RestParamDefaults { static final class RestLoggerObject extends RestMethodParam { protected RestLoggerObject() { - super(OTHER, null, RestLogger.class); + super(OTHER, RestLogger.class); } @Override /* RestMethodParam */ @@ -1224,7 +1204,7 @@ class RestParamDefaults { static final class RestContextObject extends RestMethodParam { protected RestContextObject() { - super(OTHER, null, RestContext.class); + super(OTHER, RestContext.class); } @Override /* RestMethodParam */ @@ -1236,7 +1216,7 @@ class RestParamDefaults { static final class ParserObject extends RestMethodParam { protected ParserObject() { - super(OTHER, null, Parser.class); + super(OTHER, Parser.class); } @Override /* RestMethodParam */ @@ -1248,7 +1228,7 @@ class RestParamDefaults { static final class ReaderParserObject extends RestMethodParam { protected ReaderParserObject() { - super(OTHER, null, ReaderParser.class); + super(OTHER, ReaderParser.class); } @Override /* RestMethodParam */ @@ -1260,7 +1240,7 @@ class RestParamDefaults { static final class InputStreamParserObject extends RestMethodParam { protected InputStreamParserObject() { - super(OTHER, null, InputStreamParser.class); + super(OTHER, InputStreamParser.class); } @Override /* RestMethodParam */ @@ -1272,7 +1252,7 @@ class RestParamDefaults { static final class LocaleObject extends RestMethodParam { protected LocaleObject() { - super(OTHER, null, Locale.class); + super(OTHER, Locale.class); } @Override /* RestMethodParam */ @@ -1284,7 +1264,7 @@ class RestParamDefaults { static final class SwaggerObject extends RestMethodParam { protected SwaggerObject() { - super(OTHER, null, Swagger.class); + super(OTHER, Swagger.class); } @Override /* RestMethodParam */ @@ -1296,7 +1276,7 @@ class RestParamDefaults { static final class RequestPathMatchObject extends RestMethodParam { protected RequestPathMatchObject() { - super(OTHER, null, RequestPathMatch.class); + super(OTHER, RequestPathMatch.class); } @Override /* RestMethodParam */ @@ -1308,7 +1288,7 @@ class RestParamDefaults { static final class RequestBodyObject extends RestMethodParam { protected RequestBodyObject() { - super(BODY, null, RequestBody.class); + super(BODY, RequestBody.class); } @Override /* RestMethodParam */ @@ -1320,7 +1300,7 @@ class RestParamDefaults { static final class ConfigObject extends RestMethodParam { protected ConfigObject() { - super(OTHER, null, Config.class); + super(OTHER, Config.class); } @Override /* RestMethodParam */ @@ -1332,7 +1312,7 @@ class RestParamDefaults { static final class UriContextObject extends RestMethodParam { protected UriContextObject() { - super(OTHER, null, UriContext.class); + super(OTHER, UriContext.class); } @Override /* RestMethodParam */ @@ -1344,7 +1324,7 @@ class RestParamDefaults { static final class UriResolverObject extends RestMethodParam { protected UriResolverObject() { - super(OTHER, null, UriResolver.class); + super(OTHER, UriResolver.class); } @Override /* RestMethodParam */ 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 9013981..9714566 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 @@ -21,13 +21,13 @@ import java.lang.annotation.*; import org.apache.juneau.rest.*; /** - * Annotation that can be applied to a parameter of a {@link RestMethod @RestMethod} annotated method to identify it as the HTTP + * 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. * * <h5 class='section'>Example:</h5> * <p class='bcode'> * <ja>@RestMethod</ja>(name=<jsf>POST</jsf>) - * <jk>public void</jk> doPostPerson(RestRequest req, RestResponse res, <ja>@Body</ja> Person person) { + * <jk>public void</jk> addPerson(<ja>@Body</ja> Person person) { * ... * } * </p> @@ -36,7 +36,7 @@ import org.apache.juneau.rest.*; * This is functionally equivalent to the following code... * <p class='bcode'> * <ja>@RestMethod</ja>(name=<jsf>POST</jsf>) - * <jk>public void</jk> doPostPerson(RestRequest req, RestResponse res) { + * <jk>public void</jk> addPerson(RestRequest req, RestResponse res) { * Person person = req.getBody().asType(Person.<jk>class</jk>); * ... * } diff --git a/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/BasicRestInfoProviderTest.java b/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/BasicRestInfoProviderTest.java index eb21173..17867f8 100644 --- a/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/BasicRestInfoProviderTest.java +++ b/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/BasicRestInfoProviderTest.java @@ -30,6 +30,9 @@ import org.apache.juneau.utils.*; import org.junit.*; import org.junit.runners.*; +/** + * Tests the {@link BasicRestInfoProvider} class. + */ @SuppressWarnings("javadoc") @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class BasicRestInfoProviderTest { @@ -6615,12 +6618,6 @@ public class BasicRestInfoProviderTest { @RestMethod(name=GET,path="/schema5") public void ub05e(@Response UB05e b) {} - @RestMethod(name=GET,path="/schema6") - public void ub05f(@Response Integer b) {} - - @RestMethod(name=GET,path="/schema7") - public void ub05g(@Response Boolean b) {} - public static class UB07 {} @RestMethod(name=GET,path="/headers1") @@ -6689,14 +6686,6 @@ public class BasicRestInfoProviderTest { assertObjectEquals("{type:'string'}", getSwagger(new UB()).getPaths().get("/schema5").get("get").getResponse(200).getSchema()); } @Test - public void ub05f_Response_onParameter_schema6() throws Exception { - assertObjectEquals("{format:'int32',type:'integer'}", getSwagger(new UB()).getPaths().get("/schema6").get("get").getResponse(200).getSchema()); - } - @Test - public void ub05g_Response_onParameter_schema7() throws Exception { - assertObjectEquals("{type:'boolean'}", getSwagger(new UB()).getPaths().get("/schema7").get("get").getResponse(200).getSchema()); - } - @Test public void ub07_Response_onParameter_headers1() throws Exception { assertObjectEquals("{foo:{type:'a'}}", getSwagger(new UB()).getPaths().get("/headers1").get("get").getResponse(200).getHeaders()); } 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 index dd76d36..f4aadef 100644 --- 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 @@ -15,21 +15,31 @@ 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 javax.servlet.http.*; +import org.apache.juneau.internal.*; import org.apache.juneau.json.*; import org.apache.juneau.rest.*; import org.apache.juneau.rest.util.*; import org.junit.*; import org.junit.runners.*; -@SuppressWarnings("javadoc") +/** + * Tests the {@link Body} annotation. + */ +@SuppressWarnings({"javadoc","serial"}) @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class BodyTest { - - private void call(Object resource, HttpServletRequest req, HttpServletResponse res) throws Exception { - RestContext rc = RestContext.create(resource).build(); - rc.getCallHandler().service(req, res); + + private static Map<Class<?>,RestContext> CONTEXTS = new HashMap<>(); + + private static void call(Class<?> c, HttpServletRequest req, HttpServletResponse res) throws Exception { + if (! CONTEXTS.containsKey(c)) + CONTEXTS.put(c, RestContext.create(c.newInstance()).build()); + CONTEXTS.get(c).getCallHandler().service(req, res); } //----------------------------------------------------------------------------------------------------------------- @@ -62,36 +72,86 @@ public class BodyTest { public A04 a04(@Body A04 b) { return b; } + + public static class A05 { + String s; + + public A05(InputStream in) throws Exception { + this.s = IOUtils.read(in); + } + + @Override /* Object */ + public String toString() { + return s; + } + } + + @RestMethod(name=PUT, path="/inputStream") + public A05 a05(@Body A05 b) throws Exception { + return b; + } + + public static class A06 { + private String s; + + public A06(Reader in) throws Exception { + this.s = IOUtils.read(in); + } + + @Override /* Object */ + public String toString() { + return s; + } + } + + @RestMethod(name=PUT, path="/reader") + public A06 a06(@Body A06 b) throws Exception { + return b; + } } @Test public void a01_onParameter_string() throws Exception { MockServletRequest req = MockServletRequest.create("PUT", "/string").body("'foo'"); MockServletResponse res = MockServletResponse.create(); - call(new A(), req, res); + call(A.class, req, res); assertEquals("'foo'", res.getBodyAsString()); } @Test public void a02_onParameter_integer() throws Exception { MockServletRequest req = MockServletRequest.create("PUT", "/integer").body("123"); MockServletResponse res = MockServletResponse.create(); - call(new A(), req, res); + call(A.class, req, res); assertEquals("123", res.getBodyAsString()); } @Test public void a03_onParameter_boolean() throws Exception { MockServletRequest req = MockServletRequest.create("PUT", "/boolean").body("true"); MockServletResponse res = MockServletResponse.create(); - call(new A(), req, res); + call(A.class, req, res); assertEquals("true", res.getBodyAsString()); } @Test public void a04_onParameter_bean() throws Exception { MockServletRequest req = MockServletRequest.create("PUT", "/bean").body("{f1:'a'}"); MockServletResponse res = MockServletResponse.create(); - call(new A(), req, res); + call(A.class, req, res); assertEquals("{f1:'a'}", res.getBodyAsString()); } + @Test + public void a05_onParameter_inputStream() throws Exception { + MockServletRequest req = MockServletRequest.create("PUT", "/inputStream").body("a"); + MockServletResponse res = MockServletResponse.create(); + call(A.class, req, res); + assertEquals("'a'", res.getBodyAsString()); + } + @Test + public void a06_onParameter_reader() throws Exception { + MockServletRequest req = MockServletRequest.create("PUT", "/reader").body("a"); + MockServletResponse res = MockServletResponse.create(); + call(A.class, req, res); + assertEquals("'a'", res.getBodyAsString()); + } //----------------------------------------------------------------------------------------------------------------- // @Body on POJO @@ -128,20 +188,87 @@ public class BodyTest { 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="/inputStream") + 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="/reader") + public B05 b05(B05 b) throws Exception { + return b; + } } @Test - public void a01_onPojo_string() throws Exception { - MockServletRequest req = MockServletRequest.create("PUT", "/string").body("'foo'"); + public void b01_onPojo_string() throws Exception { + MockServletRequest req = MockServletRequest.create(PUT, "/string").body("'foo'"); MockServletResponse res = MockServletResponse.create(); - call(new B(), req, res); + call(B.class, req, res); assertEquals("'foo'", res.getBodyAsString()); } @Test - public void a04_onPojo_bean() throws Exception { - MockServletRequest req = MockServletRequest.create("PUT", "/bean").body("{f1:'a'}"); + public void b02_onPojo_bean() throws Exception { + MockServletRequest req = MockServletRequest.create(PUT, "/bean").body("{f1:'a'}"); MockServletResponse res = MockServletResponse.create(); - call(new B(), req, res); + call(B.class, req, res); assertEquals("{f1:'a'}", res.getBodyAsString()); } + @Test + public void b03_onPojo_beanList() throws Exception { + MockServletRequest req = MockServletRequest.create(PUT, "/beanList").body("[{f1:'a'}]"); + MockServletResponse res = MockServletResponse.create(); + call(B.class, req, res); + assertEquals("[{f1:'a'}]", res.getBodyAsString()); + } + @Test + public void b04_onPojo_inputStream() throws Exception { + MockServletRequest req = MockServletRequest.create(PUT, "/inputStream").body("a"); + MockServletResponse res = MockServletResponse.create(); + call(B.class, req, res); + assertEquals("'a'", res.getBodyAsString()); + } + @Test + public void b05_onPojo_reader() throws Exception { + MockServletRequest req = MockServletRequest.create(PUT, "/reader").body("a"); + MockServletResponse res = MockServletResponse.create(); + call(B.class, req, res); + assertEquals("'a'", res.getBodyAsString()); + } } -- To stop receiving notification emails like this one, please contact jamesbog...@apache.org.