New @RequestBean remoteable annotation Project: http://git-wip-us.apache.org/repos/asf/incubator-juneau/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-juneau/commit/788b8488 Tree: http://git-wip-us.apache.org/repos/asf/incubator-juneau/tree/788b8488 Diff: http://git-wip-us.apache.org/repos/asf/incubator-juneau/diff/788b8488
Branch: refs/heads/master Commit: 788b848861d23b01de4c18d1667db62adcbb205b Parents: cdebb74 Author: JamesBognar <[email protected]> Authored: Wed May 24 19:06:04 2017 -0400 Committer: JamesBognar <[email protected]> Committed: Wed May 24 19:06:04 2017 -0400 ---------------------------------------------------------------------- .../main/java/org/apache/juneau/BeanMeta.java | 4 +- .../org/apache/juneau/BeanPropertyMeta.java | 31 +- .../org/apache/juneau/internal/ClassUtils.java | 44 + .../java/org/apache/juneau/remoteable/Body.java | 20 +- .../org/apache/juneau/remoteable/FormData.java | 29 +- .../apache/juneau/remoteable/FormDataIfNE.java | 2 +- .../org/apache/juneau/remoteable/Header.java | 29 +- .../apache/juneau/remoteable/HeaderIfNE.java | 2 +- .../java/org/apache/juneau/remoteable/Path.java | 28 +- .../org/apache/juneau/remoteable/Query.java | 31 +- .../org/apache/juneau/remoteable/QueryIfNE.java | 2 +- .../juneau/remoteable/RemoteableMethodMeta.java | 18 +- .../apache/juneau/remoteable/RequestBean.java | 85 + juneau-core/src/main/javadoc/overview.html | 12 + .../org/apache/juneau/rest/client/RestCall.java | 2 +- .../apache/juneau/rest/client/RestClient.java | 46 + .../rest/test/ThirdPartyProxyResource.java | 92 + .../juneau/rest/test/ThirdPartyProxyTest.java | 2279 +++++++++++++++--- .../org/apache/juneau/rest/RestContext.java | 16 +- 19 files changed, 2446 insertions(+), 326 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/788b8488/juneau-core/src/main/java/org/apache/juneau/BeanMeta.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/BeanMeta.java b/juneau-core/src/main/java/org/apache/juneau/BeanMeta.java index 6ca660c..cd1c0a6 100644 --- a/juneau-core/src/main/java/org/apache/juneau/BeanMeta.java +++ b/juneau-core/src/main/java/org/apache/juneau/BeanMeta.java @@ -194,7 +194,7 @@ public class BeanMeta<T> { if (ctx.isNotABean(c)) return "Class matches exclude-class list"; - if (! cVis.isVisible(c.getModifiers())) + if (! (cVis.isVisible(c.getModifiers()) || c.isAnonymousClass())) return "Class is not public"; if (c.isAnnotationPresent(BeanIgnore.class)) @@ -557,7 +557,7 @@ public class BeanMeta<T> { Class<?>[] pt = m.getParameterTypes(); Class<?> rt = m.getReturnType(); boolean isGetter = false, isSetter = false; - BeanProperty bp = m.getAnnotation(BeanProperty.class); + BeanProperty bp = getMethodAnnotation(BeanProperty.class, m); String bpName = bp == null ? "" : bp.name(); if (pt.length == 0) { if (n.startsWith("get") && (! rt.equals(Void.TYPE))) { http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/788b8488/juneau-core/src/main/java/org/apache/juneau/BeanPropertyMeta.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/BeanPropertyMeta.java b/juneau-core/src/main/java/org/apache/juneau/BeanPropertyMeta.java index 409373a..e520799 100644 --- a/juneau-core/src/main/java/org/apache/juneau/BeanPropertyMeta.java +++ b/juneau-core/src/main/java/org/apache/juneau/BeanPropertyMeta.java @@ -132,7 +132,7 @@ public class BeanPropertyMeta { } if (getter != null) { - BeanProperty p = getter.getAnnotation(BeanProperty.class); + BeanProperty p = getMethodAnnotation(BeanProperty.class, getter); if (rawTypeMeta == null) rawTypeMeta = f.resolveClassMeta(p, getter.getGenericReturnType(), typeVarImpls); isUri |= (rawTypeMeta.isUri() || getter.isAnnotationPresent(org.apache.juneau.annotation.URI.class)); @@ -146,7 +146,7 @@ public class BeanPropertyMeta { } if (setter != null) { - BeanProperty p = setter.getAnnotation(BeanProperty.class); + BeanProperty p = getMethodAnnotation(BeanProperty.class, setter); if (rawTypeMeta == null) rawTypeMeta = f.resolveClassMeta(p, setter.getGenericParameterTypes()[0], typeVarImpls); isUri |= (rawTypeMeta.isUri() || setter.isAnnotationPresent(org.apache.juneau.annotation.URI.class)); @@ -224,7 +224,7 @@ public class BeanPropertyMeta { if (c == Null.class) return null; try { - if (ClassUtils.isParentClass(PojoSwap.class, c)) { + if (isParentClass(PojoSwap.class, c)) { return (PojoSwap)c.newInstance(); } throw new RuntimeException("TODO - Surrogate swaps not yet supported."); @@ -892,17 +892,38 @@ public class BeanPropertyMeta { appendAnnotations(a, field.getType(), l); } if (getter != null) { - addIfNotNull(l, getter.getAnnotation(a)); + addIfNotNull(l, getMethodAnnotation(a, getter)); appendAnnotations(a, getter.getReturnType(), l); } if (setter != null) { - addIfNotNull(l, setter.getAnnotation(a)); + addIfNotNull(l, getMethodAnnotation(a, setter)); appendAnnotations(a, setter.getReturnType(), l); } + appendAnnotations(a, this.getBeanMeta().getClassMeta().getInnerClass(), l); return l; } + /** + * Returns the specified annotation on the field or methods that define this property. + * <p> + * This method will search up the parent class/interface hierarchy chain to search for the annotation on + * overridden getters and setters. + * + * @param a The annotation to search for. + * @return The annotation, or <jk>null</jk> if it wasn't found. + */ + public <A extends Annotation> A getAnnotation(Class<A> a) { + A t = null; + if (field != null) + t = field.getAnnotation(a); + if (t == null && getter != null) + t = getMethodAnnotation(a, getter); + if (t == null && setter != null) + t = getMethodAnnotation(a, setter); + return t; + } + private Object transform(BeanSession session, Object o) throws SerializeException { // First use swap defined via @BeanProperty. if (swap != null) http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/788b8488/juneau-core/src/main/java/org/apache/juneau/internal/ClassUtils.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/internal/ClassUtils.java b/juneau-core/src/main/java/org/apache/juneau/internal/ClassUtils.java index 1f2788b..d176b92 100644 --- a/juneau-core/src/main/java/org/apache/juneau/internal/ClassUtils.java +++ b/juneau-core/src/main/java/org/apache/juneau/internal/ClassUtils.java @@ -13,6 +13,7 @@ package org.apache.juneau.internal; import java.io.*; +import java.lang.annotation.*; import java.lang.reflect.*; import java.util.*; @@ -329,6 +330,49 @@ public final class ClassUtils { return Modifier.isPublic(c.getModifiers()); } + /** + * Returns the specified annotation on the specified method. + * <p> + * Similar to {@link Method#getAnnotation(Class)}, but searches up the parent hierarchy for the annotation + * defined on parent classes and interfaces. + * <p> + * Normally, annotations defined on methods of parent classes and interfaces are not inherited by the child + * methods. + * This utility method gets around that limitation by searching the class hierarchy for the "same" method. + * + * @param a The annotation to search for. + * @param m The method to search. + * @return The annotation, or <jk>null</jk> if it wasn't found. + */ + public static <T extends Annotation> T getMethodAnnotation(Class<T> a, Method m) { + T t = m.getAnnotation(a); + if (t != null) + return t; + Class<?> pc = m.getDeclaringClass().getSuperclass(); + if (pc != null) { + for (Method m2 : pc.getDeclaredMethods()) { + if (isSameMethod(m, m2)) { + t = getMethodAnnotation(a, m2); + if (t != null) + return t; + } + } + } + for (Class<?> ic : m.getDeclaringClass().getInterfaces()) { + for (Method m2 : ic.getDeclaredMethods()) { + if (isSameMethod(m, m2)) { + t = getMethodAnnotation(a, m2); + if (t != null) + return t; + } + } + } + return null; + } + + private static boolean isSameMethod(Method m1, Method m2) { + return m1.getName().equals(m2.getName()) && Arrays.equals(m1.getParameterTypes(), m2.getParameterTypes()); + } /** * Locates the no-arg constructor for the specified class. http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/788b8488/juneau-core/src/main/java/org/apache/juneau/remoteable/Body.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/remoteable/Body.java b/juneau-core/src/main/java/org/apache/juneau/remoteable/Body.java index a73b694..3266cfa 100644 --- a/juneau-core/src/main/java/org/apache/juneau/remoteable/Body.java +++ b/juneau-core/src/main/java/org/apache/juneau/remoteable/Body.java @@ -41,9 +41,27 @@ import org.apache.juneau.serializer.*; * <li><code>HttpEntity</code> - Bypass Juneau serialization and pass HttpEntity directly to HttpClient. * <li><code>NameValuePairs</code> - Converted to a URL-encoded FORM post. * </ul> + * <p> + * The annotation can also be applied to a bean property field or getter when the argument is annotated with + * {@link RequestBean @RequestBean}: + * <p> + * <h5 class='section'>Example:</h5> + * <p class='bcode'> + * <ja>@Remoteable</ja>(path=<js>"/myproxy"</js>) + * <jk>public interface</jk> MyProxy { + * + * <ja>@RemoteMethod</ja>(path=<js>"/mymethod"</js>) + * String myProxyMethod(<ja>@RequestBean</ja> MyRequestBean bean); + * } + * + * <jk>public interface</jk> MyRequestBean { + * <ja>@Body</ja> + * MyPojo getMyPojo(); + * } + * </p> */ @Documented -@Target(PARAMETER) +@Target({PARAMETER,FIELD,METHOD}) @Retention(RUNTIME) @Inherited public @interface Body {} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/788b8488/juneau-core/src/main/java/org/apache/juneau/remoteable/FormData.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/remoteable/FormData.java b/juneau-core/src/main/java/org/apache/juneau/remoteable/FormData.java index 780e64d..23983c3 100644 --- a/juneau-core/src/main/java/org/apache/juneau/remoteable/FormData.java +++ b/juneau-core/src/main/java/org/apache/juneau/remoteable/FormData.java @@ -17,6 +17,7 @@ import static java.lang.annotation.RetentionPolicy.*; import java.lang.annotation.*; +import org.apache.juneau.annotation.*; import org.apache.juneau.urlencoding.*; /** @@ -47,9 +48,35 @@ import org.apache.juneau.urlencoding.*; * <li>A bean - Individual name-value pairs. * Values are converted to text using {@link UrlEncodingSerializer#serializePart(Object, Boolean, Boolean)}. * </ul> + * <p> + * The annotation can also be applied to a bean property field or getter when the argument is annotated with + * {@link RequestBean @RequestBean}: + * <p> + * <h5 class='section'>Example:</h5> + * <p class='bcode'> + * <ja>@Remoteable</ja>(path=<js>"/myproxy"</js>) + * <jk>public interface</jk> MyProxy { + * + * <ja>@RemoteMethod</ja>(path=<js>"/mymethod"</js>) + * String myProxyMethod(<ja>@RequestBean</ja> MyRequestBean bean); + * } + * + * <jk>public interface</jk> MyRequestBean { + * <ja>@FormData</ja> + * String getFoo(); + * + * <ja>@FormData</ja> + * MyPojo getBar(); + * } + * </p> + * <p> + * When used in a request bean, the {@link #value()} can be used to override the form data parameter name. + * It can also be overridden via the {@link BeanProperty#name @BeanProperty.name()} annotation. + * A name of <js>"*"</js> where the bean property value is a map or bean will cause the individual entries in the + * map or bean to be expanded to form data parameters. */ @Documented -@Target(PARAMETER) +@Target({PARAMETER,FIELD,METHOD}) @Retention(RUNTIME) @Inherited public @interface FormData { http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/788b8488/juneau-core/src/main/java/org/apache/juneau/remoteable/FormDataIfNE.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/remoteable/FormDataIfNE.java b/juneau-core/src/main/java/org/apache/juneau/remoteable/FormDataIfNE.java index e38ac6a..d043217 100644 --- a/juneau-core/src/main/java/org/apache/juneau/remoteable/FormDataIfNE.java +++ b/juneau-core/src/main/java/org/apache/juneau/remoteable/FormDataIfNE.java @@ -21,7 +21,7 @@ import java.lang.annotation.*; * Identical to {@link FormData @FormData} except skips values if they're null/blank. */ @Documented -@Target(PARAMETER) +@Target({PARAMETER,FIELD,METHOD}) @Retention(RUNTIME) @Inherited public @interface FormDataIfNE { http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/788b8488/juneau-core/src/main/java/org/apache/juneau/remoteable/Header.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/remoteable/Header.java b/juneau-core/src/main/java/org/apache/juneau/remoteable/Header.java index d414eb1..3fae979 100644 --- a/juneau-core/src/main/java/org/apache/juneau/remoteable/Header.java +++ b/juneau-core/src/main/java/org/apache/juneau/remoteable/Header.java @@ -17,6 +17,7 @@ import static java.lang.annotation.RetentionPolicy.*; import java.lang.annotation.*; +import org.apache.juneau.annotation.*; import org.apache.juneau.urlencoding.*; /** @@ -44,9 +45,35 @@ import org.apache.juneau.urlencoding.*; * <li>A bean - Individual name-value pairs. * Values are converted to text using {@link UrlEncodingSerializer#serializePart(Object, Boolean, Boolean)}. * </ul> + * <p> + * The annotation can also be applied to a bean property field or getter when the argument is annotated with + * {@link RequestBean @RequestBean}: + * <p> + * <h5 class='section'>Example:</h5> + * <p class='bcode'> + * <ja>@Remoteable</ja>(path=<js>"/myproxy"</js>) + * <jk>public interface</jk> MyProxy { + * + * <ja>@RemoteMethod</ja>(path=<js>"/mymethod"</js>) + * String myProxyMethod(<ja>@RequestBean</ja> MyRequestBean bean); + * } + * + * <jk>public interface</jk> MyRequestBean { + * <ja>@Header</ja>(<js>"Foo"</js>) + * String getFoo(); + * + * <ja>@Header</ja>(<js>"Bar"</js>) + * MyPojo getBar(); + * } + * </p> + * <p> + * When used in a request bean, the {@link #value()} can be used to override the header name. + * It can also be overridden via the {@link BeanProperty#name @BeanProperty.name()} annotation. + * A name of <js>"*"</js> where the bean property value is a map or bean will cause the individual entries in the + * map or bean to be expanded to headers. */ @Documented -@Target(PARAMETER) +@Target({PARAMETER,FIELD,METHOD}) @Retention(RUNTIME) @Inherited public @interface Header { http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/788b8488/juneau-core/src/main/java/org/apache/juneau/remoteable/HeaderIfNE.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/remoteable/HeaderIfNE.java b/juneau-core/src/main/java/org/apache/juneau/remoteable/HeaderIfNE.java index 350ddcf..2aaf711 100644 --- a/juneau-core/src/main/java/org/apache/juneau/remoteable/HeaderIfNE.java +++ b/juneau-core/src/main/java/org/apache/juneau/remoteable/HeaderIfNE.java @@ -21,7 +21,7 @@ import java.lang.annotation.*; * Identical to {@link Header @Header} except skips values if they're null/blank. */ @Documented -@Target(PARAMETER) +@Target({PARAMETER,FIELD,METHOD}) @Retention(RUNTIME) @Inherited public @interface HeaderIfNE { http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/788b8488/juneau-core/src/main/java/org/apache/juneau/remoteable/Path.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/remoteable/Path.java b/juneau-core/src/main/java/org/apache/juneau/remoteable/Path.java index 0420e54..47c6eea 100644 --- a/juneau-core/src/main/java/org/apache/juneau/remoteable/Path.java +++ b/juneau-core/src/main/java/org/apache/juneau/remoteable/Path.java @@ -17,6 +17,7 @@ import static java.lang.annotation.RetentionPolicy.*; import java.lang.annotation.*; +import org.apache.juneau.annotation.*; import org.apache.juneau.urlencoding.*; /** @@ -41,9 +42,32 @@ import org.apache.juneau.urlencoding.*; * <li>A bean - Individual name-value pairs. * Values are converted to text using {@link UrlEncodingSerializer#serializePart(Object, Boolean, Boolean)}. * </ul> -*/ + * <p> + * The annotation can also be applied to a bean property field or getter when the argument is annotated with + * {@link RequestBean @RequestBean}: + * <p> + * <h5 class='section'>Example:</h5> + * <p class='bcode'> + * <ja>@Remoteable</ja>(path=<js>"/myproxy"</js>) + * <jk>public interface</jk> MyProxy { + * + * <ja>@RemoteMethod</ja>(path=<js>"/mymethod1/{foo}"</js>) + * String myProxyMethod(<ja>@RequestBean</ja> MyRequestBean bean); + * } + * + * <jk>public interface</jk> MyRequestBean { + * <ja>@Path</ja> + * String getFoo(); + * } + * </p> + * <p> + * When used in a request bean, the {@link #value()} can be used to override the path variable name. + * It can also be overridden via the {@link BeanProperty#name @BeanProperty.name()} annotation. + * A name of <js>"*"</js> where the bean property value is a map or bean will cause the individual entries in the + * map or bean to be expanded to path variables. + */ @Documented -@Target(PARAMETER) +@Target({PARAMETER,FIELD,METHOD}) @Retention(RUNTIME) @Inherited public @interface Path { http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/788b8488/juneau-core/src/main/java/org/apache/juneau/remoteable/Query.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/remoteable/Query.java b/juneau-core/src/main/java/org/apache/juneau/remoteable/Query.java index bd7e4e7..5729412 100644 --- a/juneau-core/src/main/java/org/apache/juneau/remoteable/Query.java +++ b/juneau-core/src/main/java/org/apache/juneau/remoteable/Query.java @@ -17,6 +17,7 @@ import static java.lang.annotation.RetentionPolicy.*; import java.lang.annotation.*; +import org.apache.juneau.annotation.*; import org.apache.juneau.urlencoding.*; /** @@ -48,9 +49,35 @@ import org.apache.juneau.urlencoding.*; * Values are converted to text using {@link UrlEncodingSerializer#serializePart(Object, Boolean, Boolean)}. * <li>{@link String} - Treated as a query string. * </ul> -*/ + * <p> + * The annotation can also be applied to a bean property field or getter when the argument is annotated with + * {@link RequestBean @RequestBean}: + * <p> + * <h5 class='section'>Example:</h5> + * <p class='bcode'> + * <ja>@Remoteable</ja>(path=<js>"/myproxy"</js>) + * <jk>public interface</jk> MyProxy { + * + * <ja>@RemoteMethod</ja>(path=<js>"/mymethod"</js>) + * String myProxyMethod(<ja>@RequestBean</ja> MyRequestBean bean); + * } + * + * <jk>public interface</jk> MyRequestBean { + * <ja>@Query</ja> + * String getFoo(); + * + * <ja>@Query</ja> + * MyPojo getBar(); + * } + * </p> + * <p> + * When used in a request bean, the {@link #value()} can be used to override the query parameter name. + * It can also be overridden via the {@link BeanProperty#name @BeanProperty.name()} annotation. + * A name of <js>"*"</js> where the bean property value is a map or bean will cause the individual entries in the + * map or bean to be expanded to query parameters. + */ @Documented -@Target(PARAMETER) +@Target({PARAMETER,FIELD,METHOD}) @Retention(RUNTIME) @Inherited public @interface Query { http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/788b8488/juneau-core/src/main/java/org/apache/juneau/remoteable/QueryIfNE.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/remoteable/QueryIfNE.java b/juneau-core/src/main/java/org/apache/juneau/remoteable/QueryIfNE.java index 59ac3d2..c2cc2fd 100644 --- a/juneau-core/src/main/java/org/apache/juneau/remoteable/QueryIfNE.java +++ b/juneau-core/src/main/java/org/apache/juneau/remoteable/QueryIfNE.java @@ -21,7 +21,7 @@ import java.lang.annotation.*; * Identical to {@link Query @Query} except skips values if they're null/blank. */ @Documented -@Target(PARAMETER) +@Target({PARAMETER,FIELD,METHOD}) @Retention(RUNTIME) @Inherited public @interface QueryIfNE { http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/788b8488/juneau-core/src/main/java/org/apache/juneau/remoteable/RemoteableMethodMeta.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/remoteable/RemoteableMethodMeta.java b/juneau-core/src/main/java/org/apache/juneau/remoteable/RemoteableMethodMeta.java index 9504d37..bad4686 100644 --- a/juneau-core/src/main/java/org/apache/juneau/remoteable/RemoteableMethodMeta.java +++ b/juneau-core/src/main/java/org/apache/juneau/remoteable/RemoteableMethodMeta.java @@ -30,7 +30,7 @@ public class RemoteableMethodMeta { private final String httpMethod; private final String url; private final RemoteMethodArg[] pathArgs, queryArgs, headerArgs, formDataArgs; - private final Integer[] otherArgs; + private final Integer[] requestBeanArgs, otherArgs; private final Integer bodyArg; /** @@ -47,6 +47,7 @@ public class RemoteableMethodMeta { this.queryArgs = b.queryArgs.toArray(new RemoteMethodArg[b.queryArgs.size()]); this.formDataArgs = b.formDataArgs.toArray(new RemoteMethodArg[b.formDataArgs.size()]); this.headerArgs = b.headerArgs.toArray(new RemoteMethodArg[b.headerArgs.size()]); + this.requestBeanArgs = b.requestBeanArgs.toArray(new Integer[b.requestBeanArgs.size()]); this.otherArgs = b.otherArgs.toArray(new Integer[b.otherArgs.size()]); this.bodyArg = b.bodyArg; } @@ -58,7 +59,9 @@ public class RemoteableMethodMeta { queryArgs = new LinkedList<RemoteMethodArg>(), headerArgs = new LinkedList<RemoteMethodArg>(), formDataArgs = new LinkedList<RemoteMethodArg>(); - private List<Integer> otherArgs = new LinkedList<Integer>(); + private List<Integer> + requestBeanArgs = new LinkedList<Integer>(), + otherArgs = new LinkedList<Integer>(); private Integer bodyArg; private Builder(String restUrl, Method m) { @@ -106,6 +109,9 @@ public class RemoteableMethodMeta { } else if (ca == HeaderIfNE.class) { HeaderIfNE h = (HeaderIfNE)a; annotated = headerArgs.add(new RemoteMethodArg(h.value(), index, true)); + } else if (ca == RequestBean.class) { + annotated = true; + requestBeanArgs.add(index); } else if (ca == Body.class) { annotated = true; if (bodyArg == null) @@ -173,6 +179,14 @@ public class RemoteableMethodMeta { } /** + * Returns the {@link RequestBean @RequestBean} annotated arguments on this Java method. + * @return A list of zero-indexed argument indices. + */ + public Integer[] getRequestBeanArgs() { + return requestBeanArgs; + } + + /** * Returns the remaining non-annotated arguments on this Java method. * @return A list of zero-indexed argument indices. */ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/788b8488/juneau-core/src/main/java/org/apache/juneau/remoteable/RequestBean.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/remoteable/RequestBean.java b/juneau-core/src/main/java/org/apache/juneau/remoteable/RequestBean.java new file mode 100644 index 0000000..3da7f6c --- /dev/null +++ b/juneau-core/src/main/java/org/apache/juneau/remoteable/RequestBean.java @@ -0,0 +1,85 @@ +// *************************************************************************************************************************** +// * 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.remoteable; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.*; + +import java.lang.annotation.*; + +/** + * Annotation applied to Java method arguments of interface proxies to denote a bean with remoteable annotations. + * <p> + * <h5 class='section'>Example:</h5> + * <p class='bcode'> + * <ja>@Remoteable</ja>(path=<js>"/myproxy"</js>) + * <jk>public interface</jk> MyProxy { + * + * <ja>@RemoteMethod</ja>(path=<js>"/mymethod/{p1}/{p2}"</js>) + * String myProxyMethod(<ja>@RequestBean</ja> MyRequestBean bean); + * } + * + * <jk>public interface</jk> MyRequestBean { + * + * <ja>@Path</ja> + * String getP1(); + * + * <ja>@Path</ja>(<js>"p2"</js>) + * String getX(); + * + * <ja>@Query</ja> + * String getQ1(); + * + * <ja>@Query</ja> + * <ja>@BeanProperty</ja>(name=<js>"q2"</js>) + * String getQuery2(); + * + * <ja>@QueryIfNE</ja>(<js>"q3"</js>) + * String getQuery3(); + * + * <ja>@QueryIfNE</ja> + * Map<String,Object> getExtraQueries(); + * + * <ja>@FormData</ja> + * String getF1(); + * + * <ja>@FormData</ja> + * <ja>@BeanProperty</ja>(name=<js>"f2"</js>) + * String getFormData2(); + * + * <ja>@FormDataIfNE</ja>(<js>"f3"</js>) + * String getFormData3(); + * + * <ja>@FormDataIfNE</ja> + * Map<String,Object> getExtraFormData(); + * + * <ja>@Header</ja> + * String getH1(); + * + * <ja>@Header</ja> + * <ja>@BeanProperty</ja>(name=<js>"H2"</js>) + * String getHeader2(); + * + * <ja>@HeaderIfNE</ja>(<js>"H3"</js>) + * String getHeader3(); + * + * <ja>@HeaderIfNE</ja> + * Map<String,Object> getExtraHeaders(); + * } + * </p> + */ +@Documented +@Target(PARAMETER) +@Retention(RUNTIME) +@Inherited +public @interface RequestBean {} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/788b8488/juneau-core/src/main/javadoc/overview.html ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/javadoc/overview.html b/juneau-core/src/main/javadoc/overview.html index ff4ce10..f5b4f86 100644 --- a/juneau-core/src/main/javadoc/overview.html +++ b/juneau-core/src/main/javadoc/overview.html @@ -6126,6 +6126,16 @@ <li>New {@link org.apache.juneau.html.annotation.Html#render() @Html.render()} annotation and {@link org.apache.juneau.html.HtmlRender} class that allows you to customize the HTML output and CSS style on bean properties:<br> <img class='bordered' src='doc-files/HtmlRender_1.png'> + <li>{@link org.apache.juneau.annotation.BeanProperty @BeanProperty} annotation can now be applied to getters + and setters defined on interfaces. + <li>New methods on {@link org.apache.juneau.serializer.SerializerSession} and {@link org.apache.juneau.parser.ParserSession} + for retrieving context and runtime-override properties: + <ul> + <li>{@link org.apache.juneau.Session#getProperty(String)} + <li>{@link org.apache.juneau.Session#getProperty(String,String)} + <li>{@link org.apache.juneau.Session#getProperty(Class,String)} + <li>{@link org.apache.juneau.Session#getProperty(Class,String,Object)} + </ul> </ul> <h6 class='topic'>org.apache.juneau.rest</h6> @@ -6254,6 +6264,8 @@ <h6 class='topic'>org.apache.juneau.rest.client</h6> <ul class='spaced-list'> <li>New {@link org.apache.juneau.remoteable.Path @Path} annotation for specifying path variables on remoteable interfaces. + <li>New {@link org.apache.juneau.remoteable.RequestBean @RequestBean} annotation for specifying beans with remoteable annotations + defined on properties. <li>The following annotations (and related methods on RestCall) can now take <code>NameValuePairs</code> and beans as input when using <js>"*"</js> as the name. <br>{@link org.apache.juneau.remoteable.FormData @FormData},{@link org.apache.juneau.remoteable.FormDataIfNE @FormDataIfNE}, http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/788b8488/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestCall.java ---------------------------------------------------------------------- diff --git a/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestCall.java b/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestCall.java index 14c7207..f06a172 100644 --- a/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestCall.java +++ b/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestCall.java @@ -198,7 +198,7 @@ public final class RestCall { } else if (isBean(value)){ return query(name, toBeanMap(value), skipIfEmpty); } else { - throw new RuntimeException("Invalid name passed to query(name,value,skipIfEmpty)."); + throw new RuntimeException("Invalid name passed to query(name,value,skipIfEmpty): ("+name+","+value+","+skipIfEmpty+")"); } return this; } http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/788b8488/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java ---------------------------------------------------------------------- diff --git a/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java b/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java index b026ff4..dbcc294 100644 --- a/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java +++ b/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java @@ -552,6 +552,46 @@ public class RestClient extends CoreObject { if (rmm.getBodyArg() != null) rc.input(args[rmm.getBodyArg()]); + if (rmm.getRequestBeanArgs().length > 0) { + BeanSession bs = getBeanContext().createSession(); + + for (Integer i : rmm.getRequestBeanArgs()) { + BeanMap<?> bm = bs.toBeanMap(args[i]); + for (BeanPropertyValue bpv : bm.getValues(true)) { + BeanPropertyMeta pMeta = bpv.getMeta(); + Object val = bpv.getValue(); + + Path p = pMeta.getAnnotation(Path.class); + if (p != null) + rc.path(getName(p.value(), pMeta), val); + + Query q1 = pMeta.getAnnotation(Query.class); + if (q1 != null) + rc.query(getName(q1.value(), pMeta), val, false); + + QueryIfNE q2 = pMeta.getAnnotation(QueryIfNE.class); + if (q2 != null) + rc.query(getName(q2.value(), pMeta), val, true); + + FormData f1 = pMeta.getAnnotation(FormData.class); + if (f1 != null) + rc.formData(getName(f1.value(), pMeta), val, false); + + FormDataIfNE f2 = pMeta.getAnnotation(FormDataIfNE.class); + if (f2 != null) + rc.formData(getName(f2.value(), pMeta), val, true); + + org.apache.juneau.remoteable.Header h1 = pMeta.getAnnotation(org.apache.juneau.remoteable.Header.class); + if (h1 != null) + rc.header(getName(h1.value(), pMeta), val, false); + + HeaderIfNE h2 = pMeta.getAnnotation(HeaderIfNE.class); + if (h2 != null) + rc.header(getName(h2.value(), pMeta), val, true); + } + } + } + if (rmm.getOtherArgs().length > 0) { Object[] otherArgs = new Object[rmm.getOtherArgs().length]; int i = 0; @@ -576,6 +616,12 @@ public class RestClient extends CoreObject { } } + private static String getName(String name, BeanPropertyMeta pMeta) { + if ("*".equals(name) && ! pMeta.getClassMeta().isMapOrBean()) + name = pMeta.getName(); + return name; + } + private Pattern absUrlPattern = Pattern.compile("^\\w+\\:\\/\\/.*"); UrlEncodingSerializer getUrlEncodingSerializer() { http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/788b8488/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/ThirdPartyProxyResource.java ---------------------------------------------------------------------- diff --git a/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/ThirdPartyProxyResource.java b/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/ThirdPartyProxyResource.java index 4a48379..532f292 100644 --- a/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/ThirdPartyProxyResource.java +++ b/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/ThirdPartyProxyResource.java @@ -1032,7 +1032,99 @@ public class ThirdPartyProxyResource extends ResourceJena { return "OK"; } + //-------------------------------------------------------------------------------- + // RequestBean tests + //-------------------------------------------------------------------------------- + + @RestMethod(name="POST", path="/reqBeanPath/{a}/{b}") + public String reqBeanPath( + @Path("a") int a, + @Path("b") String b + ) throws Exception { + + assertEquals(1, a); + assertEquals("foo", b); + + return "OK"; + } + + @RestMethod(name="POST", path="/reqBeanQuery") + public String reqBeanQuery( + @Query("a") int a, + @Query("b") String b + ) throws Exception { + + assertEquals(1, a); + assertEquals("foo", b); + return "OK"; + } + + @RestMethod(name="POST", path="/reqBeanQueryIfNE") + public String reqBeanQueryIfNE( + @Query("a") String a, + @Query("b") String b, + @Query("c") String c + ) throws Exception { + + assertEquals("foo", a); + assertNull(b); + assertNull(c); + + return "OK"; + } + + @RestMethod(name="POST", path="/reqBeanFormData") + public String reqBeanFormData( + @FormData("a") int a, + @FormData("b") String b + ) throws Exception { + + assertEquals(1, a); + assertEquals("foo", b); + + return "OK"; + } + + @RestMethod(name="POST", path="/reqBeanFormDataIfNE") + public String reqBeanFormDataIfNE( + @FormData("a") String a, + @FormData("b") String b, + @FormData("c") String c + ) throws Exception { + + assertEquals("foo", a); + assertNull(b); + assertNull(c); + + return "OK"; + } + + @RestMethod(name="POST", path="/reqBeanHeader") + public String reqBeanHeader( + @Header("a") int a, + @Header("b") String b + ) throws Exception { + + assertEquals(1, a); + assertEquals("foo", b); + + return "OK"; + } + + @RestMethod(name="POST", path="/reqBeanHeaderIfNE") + public String reqBeanHeaderIfNE( + @Header("a") String a, + @Header("b") String b, + @Header("c") String c + ) throws Exception { + + assertEquals("foo", a); + assertNull(b); + assertNull(c); + + return "OK"; + } //-------------------------------------------------------------------------------- // Test return types. //--------------------------------------------------------------------------------
