Repository: incubator-juneau Updated Branches: refs/heads/master 1174420cf -> 9db2e03fa
http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/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 ab660b0..161751a 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 @@ -53,6 +53,7 @@ import org.apache.juneau.utils.*; * <li><a class="doclink" href="package-summary.html#RestClient">org.apache.juneau.rest.client > REST client API</a> for more information and code examples. * </ul> */ +@SuppressWarnings("hiding") public final class RestCall { private final RestClient client; // The client that created this call. @@ -74,6 +75,9 @@ public final class RestCall { private TeeOutputStream outputStreams = new TeeOutputStream(); private boolean isClosed = false; private boolean isFailed = false; + private Object input; + private Serializer serializer; + private Parser parser; /** * Constructs a REST call with the specified method name. @@ -90,6 +94,8 @@ public final class RestCall { this.retryOn = client.retryOn; this.retries = client.retries; this.retryInterval = client.retryInterval; + this.serializer = client.serializer; + this.parser = client.parser; } /** @@ -107,17 +113,33 @@ public final class RestCall { * @throws RestCallException If a retry was attempted, but the entity was not repeatable. */ public RestCall input(final Object input) throws RestCallException { + this.input = input; + return this; + } - if (! (request instanceof HttpEntityEnclosingRequestBase)) - throw new RestCallException(0, "Method does not support content entity.", request.getMethod(), request.getURI(), null); - - HttpEntity entity = (input instanceof HttpEntity) ? (HttpEntity)input : new RestRequestEntity(input, client.serializer); - - ((HttpEntityEnclosingRequestBase)request).setEntity(entity); - - if (retries > 1 && ! entity.isRepeatable()) - throw new RestCallException("Rest call set to retryable, but entity is not repeatable."); + /** + * Specifies the serializer to use on this call. + * <p> + * Overrides the serializer specified on the {@link RestClient}. + * + * @param serializer The serializer used to serialize POJOs to the body of the HTTP request. + * @return This object (for method chaining). + */ + public RestCall serializer(Serializer serializer) { + this.serializer = serializer; + return this; + } + /** + * Specifies the parser to use on this call. + * <p> + * Overrides the parser specified on the {@link RestClient}. + * + * @param parser The parser used to parse POJOs from the body of the HTTP response. + * @return This object (for method chaining). + */ + public RestCall parser(Parser parser) { + this.parser = parser; return this; } @@ -520,13 +542,14 @@ public final class RestCall { * @return This object (for method chaining). * @throws RestCallException If current entity is not repeatable. */ - @SuppressWarnings("hiding") public RestCall retryable(int retries, long interval, RetryOn retryOn) throws RestCallException { if (request instanceof HttpEntityEnclosingRequestBase) { - HttpEntity e = ((HttpEntityEnclosingRequestBase)request).getEntity(); - if (e != null && ! e.isRepeatable()) - throw new RestCallException("Attempt to make call retryable, but entity is not repeatable."); - } + if (input != null && input instanceof HttpEntity) { + HttpEntity e = (HttpEntity)input; + if (e != null && ! e.isRepeatable()) + throw new RestCallException("Attempt to make call retryable, but entity is not repeatable."); + } + } this.retries = retries; this.retryInterval = interval; this.retryOn = (retryOn == null ? RetryOn.DEFAULT : retryOn); @@ -885,6 +908,16 @@ public final class RestCall { isConnected = true; try { + + if (input != null) { + if (! (request instanceof HttpEntityEnclosingRequestBase)) + throw new RestCallException(0, "Method does not support content entity.", request.getMethod(), request.getURI(), null); + HttpEntity entity = (input instanceof HttpEntity) ? (HttpEntity)input : new RestRequestEntity(input, getSerializer()); + ((HttpEntityEnclosingRequestBase)request).setEntity(entity); + if (retries > 1 && ! entity.isRepeatable()) + throw new RestCallException("Rest call set to retryable, but entity is not repeatable."); + } + int sc = 0; while (retries > 0) { retries--; @@ -1028,9 +1061,9 @@ public final class RestCall { * @throws RestCallException If no parser was defined on the client. */ protected Parser getParser() throws RestCallException { - if (client.parser == null) + if (parser == null) throw new RestCallException(0, "No parser defined on client", request.getMethod(), request.getURI(), null); - return client.parser; + return parser; } /** @@ -1040,9 +1073,9 @@ public final class RestCall { * @throws RestCallException If no serializer was defined on the client. */ protected Serializer getSerializer() throws RestCallException { - if (client.serializer == null) + if (serializer == null) throw new RestCallException(0, "No serializer defined on client", request.getMethod(), request.getURI(), null); - return client.serializer; + return serializer; } /** @@ -1114,13 +1147,33 @@ public final class RestCall { } /** - * Converts the output from the connection into an object of the specified class using the registered {@link Parser}. + * Same as {@link #getResponse(Type, Type...)} except optimized for a non-parameterized class. + * <p> + * This is the preferred parse method for simple types since you don't need to cast the results. + * <h5 class='section'>Examples:</h5> + * <p class='bcode'> + * <jc>// Parse into a string.</jc> + * String s = restClient.doGet(url).getResponse(String.<jk>class</jk>); + * + * <jc>// Parse into a bean.</jc> + * MyBean b = restClient.doGet(url).getResponse(MyBean.<jk>class</jk>); * - * @param type The class to convert the input to. - * @param <T> The class to convert the input to. - * @return The parsed output. + * <jc>// Parse into a bean array.</jc> + * MyBean[] ba = restClient.doGet(url).getResponse(MyBean[].<jk>class</jk>); + * + * <jc>// Parse into a linked-list of objects.</jc> + * List l = restClient.doGet(url).getResponse(LinkedList.<jk>class</jk>); + * + * <jc>// Parse into a map of object keys/values.</jc> + * Map m = restClient.doGet(url).getResponse(TreeMap.<jk>class</jk>); + * </p> + * + * @param <T> The class type of the object being created. + * See {@link #getResponse(Type, Type...)} for details. + * @param type The object type to create. + * @return The parsed object. + * @throws ParseException If the input contains a syntax error or is malformed, or is not valid for the specified type. * @throws IOException If a connection error occurred. - * @throws ParseException If the input contains a syntax error or is malformed for the <code>Content-Type</code> header. */ public <T> T getResponse(Class<T> type) throws IOException, ParseException { BeanContext bc = getParser().getBeanContext(); @@ -1130,28 +1183,48 @@ public final class RestCall { } /** - * Same as {@link #getResponse(Class)}, but useful for parsing into maps and collections of specific types. + * Parses HTTP body into the specified object type. + * The type can be a simple type (e.g. beans, strings, numbers) or parameterized type (collections/maps). + * + * <h5 class='section'>Examples:</h5> + * <p class='bcode'> + * <jc>// Parse into a linked-list of strings.</jc> + * List l = restClient.doGet(url).getResponse(LinkedList.<jk>class</jk>, String.<jk>class</jk>); + * + * <jc>// Parse into a linked-list of beans.</jc> + * List l = restClient.doGet(url).getResponse(LinkedList.<jk>class</jk>, MyBean.<jk>class</jk>); * - * @param type The class to resolve. - * Can be any of the following: + * <jc>// Parse into a linked-list of linked-lists of strings.</jc> + * List l = restClient.doGet(url).getResponse(LinkedList.<jk>class</jk>, LinkedList.<jk>class</jk>, String.<jk>class</jk>); + * + * <jc>// Parse into a map of string keys/values.</jc> + * Map m = restClient.doGet(url).getResponse(TreeMap.<jk>class</jk>, String.<jk>class</jk>, String.<jk>class</jk>); + * + * <jc>// Parse into a map containing string keys and values of lists containing beans.</jc> + * Map m = restClient.doGet(url).getResponse(TreeMap.<jk>class</jk>, String.<jk>class</jk>, List.<jk>class</jk>, MyBean.<jk>class</jk>); + * </p> + * <p> + * <code>Collection</code> classes are assumed to be followed by zero or one objects indicating the element type. + * <p> + * <code>Map</code> classes are assumed to be followed by zero or two meta objects indicating the key and value types. + * <p> + * The array can be arbitrarily long to indicate arbitrarily complex data structures. + * <p> + * <h5 class='section'>Notes:</h5> * <ul> - * <li>{@link ClassMeta} - * <li>{@link Class} - * <li>{@link ParameterizedType} - * <li>{@link GenericArrayType} + * <li>Use the {@link #getResponse(Class)} method instead if you don't need a parameterized map/collection. * </ul> + * + * @param <T> The class type of the object to create. + * @param type The object type to create. + * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} * @param args The type arguments of the class if it's a collection or map. - * Can be any of the following: - * <ul> - * <li>{@link ClassMeta} - * <li>{@link Class} - * <li>{@link ParameterizedType} - * <li>{@link GenericArrayType} - * </ul> - * @param <T> The class to convert the input to. - * @return The parsed output. + * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} + * <br>Ignored if the main type is not a map or collection. + * @return The parsed object. + * @throws ParseException If the input contains a syntax error or is malformed, or is not valid for the specified type. * @throws IOException If a connection error occurred. - * @throws ParseException If the input contains a syntax error or is malformed for the <code>Content-Type</code> header. + * @see BeanSession#getClassMeta(Class) for argument syntax for maps and collections. */ @SuppressWarnings("unchecked") public <T> T getResponse(Type type, Type...args) throws IOException, ParseException { http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/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 9ff546a..6594ced 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 @@ -59,7 +59,6 @@ public class RestClient extends CoreObject { private final UrlEncodingSerializer urlEncodingSerializer; // Used for form posts only. final Parser parser; private final String remoteableServletUri; - private final Map<Method,String> remoteableServiceUriMap; private final String rootUrl; private volatile boolean isClosed = false; private final StackTraceElement[] creationStack; @@ -113,7 +112,6 @@ public class RestClient extends CoreObject { this.headers = Collections.unmodifiableMap(h2); this.interceptors = interceptors.toArray(new RestCallInterceptor[interceptors.size()]); this.remoteableServletUri = remoteableServletUri; - this.remoteableServiceUriMap = new ConcurrentHashMap<Method,String>(remoteableServiceUriMap); this.rootUrl = rootUri; this.retryOn = retryOn; this.retries = retries; @@ -410,29 +408,65 @@ public class RestClient extends CoreObject { * @throws RuntimeException If the Remotable service URI has not been specified on this * client by calling {@link RestClientBuilder#remoteableServletUri(String)}. */ - @SuppressWarnings("unchecked") public <T> T getRemoteableProxy(final Class<T> interfaceClass) { if (remoteableServletUri == null) throw new RuntimeException("Remoteable service URI has not been specified."); - return (T)Proxy.newProxyInstance( - interfaceClass.getClassLoader(), - new Class[] { interfaceClass }, - new InvocationHandler() { - @Override /* InvocationHandler */ - public Object invoke(Object proxy, Method method, Object[] args) { - try { - String uri = remoteableServiceUriMap.get(method); - if (uri == null) { - // Constructing this string each time can be time consuming, so cache it. - uri = remoteableServletUri + '/' + interfaceClass.getName() + '/' + ClassUtils.getMethodSignature(method); - remoteableServiceUriMap.put(method, uri); + return getRemoteableProxy(interfaceClass, remoteableServletUri + '/' + interfaceClass.getName()); + } + + /** + * Create a new proxy interface for the specified REST PROXY interface. + * + * @param interfaceClass The interface to create a proxy for. + * @param proxyUrl The URL of the REST method annotated with <code><ja>@RestMethod</ja>(name=<js>"PROXY"</js>)</code>. + * @return The new proxy interface. + */ + public <T> T getRemoteableProxy(final Class<T> interfaceClass, final Object proxyUrl) { + return getRemoteableProxy(interfaceClass, proxyUrl, serializer, parser); + } + + /** + * Same as {@link #getRemoteableProxy(Class, Object)} but allows you to override the serializer and parser used. + * + * @param interfaceClass The interface to create a proxy for. + * @param proxyUrl The URL of the REST method annotated with <code><ja>@RestMethod</ja>(name=<js>"PROXY"</js>)</code>. + * @param serializer The serializer used to serialize POJOs to the body of the HTTP request. + * @param parser The parser used to parse POJOs from the body of the HTTP response. + * @return The new proxy interface. + */ + @SuppressWarnings({ "unchecked", "hiding" }) + public <T> T getRemoteableProxy(final Class<T> interfaceClass, final Object proxyUrl, final Serializer serializer, final Parser parser) { + try { + return (T)Proxy.newProxyInstance( + interfaceClass.getClassLoader(), + new Class[] { interfaceClass }, + new InvocationHandler() { + + final Map<Method,String> uriCache = new ConcurrentHashMap<Method,String>(); + final String uri = toURI(proxyUrl).toString(); + + @Override /* InvocationHandler */ + public Object invoke(Object proxy, Method method, Object[] args) { + + // Constructing this string each time can be time consuming, so cache it. + String u = uriCache.get(method); + if (u == null) { + try { + u = uri + '/' + URLEncoder.encode(ClassUtils.getMethodSignature(method), "utf-8"); + } catch (UnsupportedEncodingException e) {} + uriCache.put(method, u); + } + + try { + return doPost(u, args).serializer(serializer).parser(parser).getResponse(method.getGenericReturnType()); + } catch (Exception e) { + throw new RuntimeException(e); } - return doPost(uri, args).getResponse(method.getReturnType()); - } catch (Exception e) { - throw new RuntimeException(e); } - } - }); + }); + } catch (Exception e) { + throw new RuntimeException(e); + } } private Pattern absUrlPattern = Pattern.compile("^\\w+\\:\\/\\/.*"); http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClientBuilder.java ---------------------------------------------------------------------- diff --git a/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClientBuilder.java b/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClientBuilder.java index 0075437..39a2f88 100644 --- a/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClientBuilder.java +++ b/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClientBuilder.java @@ -1015,6 +1015,20 @@ public class RestClientBuilder extends CoreObjectBuilder { return property(PARSER_fileCharset, value); } + /** + * When called, <code>No-Trace: true</code> is added to requests. + * <p> + * This gives the opportunity for the servlet to not log errors on invalid requests. + * This is useful for testing purposes when you don't want your log file to show lots + * of errors that are simply the results of testing. + * + * @return This object (for method chaining). + */ + public RestClientBuilder noTrace() { + return header("No-Trace", true); + } + + @Override /* CoreObjectBuilder */ public RestClientBuilder beansRequireDefaultConstructor(boolean value) { super.beansRequireDefaultConstructor(value); @@ -1342,6 +1356,7 @@ public class RestClientBuilder extends CoreObjectBuilder { @Override /* CoreObjectBuilder */ public RestClientBuilder debug(boolean value) { super.debug(value); + header("Debug", value); return this; } http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-rest-test/pom.xml ---------------------------------------------------------------------- diff --git a/juneau-rest-test/pom.xml b/juneau-rest-test/pom.xml index 0b613dd..7e5cbff 100644 --- a/juneau-rest-test/pom.xml +++ b/juneau-rest-test/pom.xml @@ -46,6 +46,7 @@ <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> + <scope>compile</scope> </dependency> <dependency> <groupId>javax.ws.rs</groupId> http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/InterfaceProxy.java ---------------------------------------------------------------------- diff --git a/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/InterfaceProxy.java b/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/InterfaceProxy.java new file mode 100644 index 0000000..6b96290 --- /dev/null +++ b/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/InterfaceProxy.java @@ -0,0 +1,62 @@ +// *************************************************************************************************************************** +// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * +// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * +// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * +// * with the License. You may obtain a copy of the License at * +// * * +// * http://www.apache.org/licenses/LICENSE-2.0 * +// * * +// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * +// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * +// * specific language governing permissions and limitations under the License. * +// *************************************************************************************************************************** +package org.apache.juneau.rest.test; + +import java.util.*; + +/** + * Interface proxy exposed in InterfaceProxyResource and tested in InterfaceProxyTest. + */ +public interface InterfaceProxy { + + void returnVoid(); + int returnInt(); + Integer returnInteger(); + float returnFloat(); + Float returnFloatObject(); + String returnString(); + String returnNullString(); + int[] returnIntArray(); + String[] returnStringArray(); + List<Integer> returnIntegerList(); + List<String> returnStringList(); + Bean returnBean(); + Bean[] returnBeanArray(); + List<Bean> returnBeanList(); + + void setNothing(); + void setInt(int x); + void setInteger(Integer x); + void setFloat(float x); + void setFloatObject(Float x); + void setString(String x); + void setNullString(String x); + void setIntArray(int[] x); + void setStringArray(String[] x); + void setIntegerList(List<Integer> x); + void setStringList(List<String> x); + void setBean(Bean x); + void setBeanArray(Bean[] x); + void setBeanList(List<Bean> x); + + public static class Bean { + public int a; + public String b; + + Bean init() { + this.a = 1; + this.b = "foo"; + return this; + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/InterfaceProxyResource.java ---------------------------------------------------------------------- diff --git a/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/InterfaceProxyResource.java b/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/InterfaceProxyResource.java new file mode 100644 index 0000000..82de1c2 --- /dev/null +++ b/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/InterfaceProxyResource.java @@ -0,0 +1,128 @@ +// *************************************************************************************************************************** +// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * +// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * +// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * +// * with the License. You may obtain a copy of the License at * +// * * +// * http://www.apache.org/licenses/LICENSE-2.0 * +// * * +// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * +// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * +// * specific language governing permissions and limitations under the License. * +// *************************************************************************************************************************** +package org.apache.juneau.rest.test; + +import static org.junit.Assert.*; + +import java.util.*; + +import org.apache.juneau.json.*; +import org.apache.juneau.rest.annotation.*; +import org.apache.juneau.rest.jena.*; +import org.junit.*; + +/** + * Tests inteface proxies exposed through <code>@RestMethod(name="PROXY")</code> + */ +@RestResource( + path="/testInterfaceProxyResource") +public class InterfaceProxyResource extends RestServletJenaDefault { + private static final long serialVersionUID = 1L; + + //==================================================================================================== + // Test that Q-values are being resolved correctly. + //==================================================================================================== + @RestMethod(name="PROXY", path="/proxy/*") + public InterfaceProxy getProxy() { + return new InterfaceProxy() { + @Override + public void returnVoid() {} + @Override + public Integer returnInteger() { return 1;} + @Override + public int returnInt() { return 1; } + @Override + public float returnFloat() { return 1f; } + @Override + public Float returnFloatObject() { return 1f; } + @Override + public String returnString() { return "foobar"; } + @Override + public String returnNullString() { return null; } + @Override + public int[] returnIntArray() { return new int[]{1,2}; } + @Override + public String[] returnStringArray() { return new String[]{"foo","bar",null};} + @Override + public List<Integer> returnIntegerList() { return Arrays.asList(new Integer[]{1,2}); } + @Override + public List<String> returnStringList() { return Arrays.asList(new String[]{"foo","bar",null}); } + @Override + public Bean returnBean() { return new Bean().init(); } + @Override + public Bean[] returnBeanArray() { return new Bean[]{new Bean().init()}; } + @Override + public List<Bean> returnBeanList() { return Arrays.asList(new Bean().init()); } + + @Override + public void setNothing() { + } + @Override + public void setInt(int x) { + assertEquals(1, x); + } + @Override + public void setInteger(Integer x) { + assertEquals((Integer)1, x); + } + @Override + public void setFloat(float x) { + assertTrue(1f == x); + } + @Override + public void setFloatObject(Float x) { + assertTrue(1f == x); + } + @Override + public void setString(String x) { + assertEquals("foo", x); + } + @Override + public void setNullString(String x) { + assertNull(x); + } + @Override + public void setIntArray(int[] x) { + assertObjectEquals("[1,2]", x); + } + @Override + public void setStringArray(String[] x) { + assertObjectEquals("['foo','bar',null]", x); + } + @Override + public void setIntegerList(List<Integer> x) { + assertObjectEquals("[1,2,null]", x); + } + @Override + public void setStringList(List<String> x) { + assertObjectEquals("['foo','bar',null]", x); + } + @Override + public void setBean(Bean x) { + assertObjectEquals("{a:1,b:'foo'}", x); + } + @Override + public void setBeanArray(Bean[] x) { + assertObjectEquals("[{a:1,b:'foo'}]", x); + } + @Override + public void setBeanList(List<Bean> x) { + assertObjectEquals("[{a:1,b:'foo'}]", x); + } + }; + } + + private static void assertObjectEquals(String e, Object o) { + Assert.assertEquals(e, JsonSerializer.DEFAULT_LAX.toString(o)); + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/Root.java ---------------------------------------------------------------------- diff --git a/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/Root.java b/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/Root.java index f24c34c..ceeaf48 100644 --- a/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/Root.java +++ b/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/Root.java @@ -38,6 +38,7 @@ import org.apache.juneau.rest.labels.*; InheritanceResource.TestParsers.class, InheritanceResource.TestProperties.class, InheritanceResource.TestSerializers.class, + InterfaceProxyResource.class, LargePojosResource.class, MessagesResource.Messages2Resource.class, MessagesResource.class, http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/InterfaceProxyTest.java ---------------------------------------------------------------------- diff --git a/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/InterfaceProxyTest.java b/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/InterfaceProxyTest.java new file mode 100644 index 0000000..4ed272d --- /dev/null +++ b/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/InterfaceProxyTest.java @@ -0,0 +1,225 @@ +// *************************************************************************************************************************** +// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * +// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * +// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * +// * with the License. You may obtain a copy of the License at * +// * * +// * http://www.apache.org/licenses/LICENSE-2.0 * +// * * +// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * +// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * +// * specific language governing permissions and limitations under the License. * +// *************************************************************************************************************************** +package org.apache.juneau.rest.test; + +import static org.apache.juneau.rest.test.TestUtils.*; +import static org.junit.Assert.*; + +import java.util.*; + +import org.apache.juneau.html.*; +import org.apache.juneau.json.*; +import org.apache.juneau.msgpack.*; +import org.apache.juneau.parser.*; +import org.apache.juneau.rest.test.InterfaceProxy.*; +import org.apache.juneau.serializer.*; +import org.apache.juneau.uon.*; +import org.apache.juneau.urlencoding.*; +import org.apache.juneau.xml.*; +import org.junit.*; +import org.junit.runner.*; +import org.junit.runners.*; + +@RunWith(Parameterized.class) +public class InterfaceProxyTest extends RestTestcase { + + @Parameterized.Parameters + public static Collection<Object[]> getParameters() { + return Arrays.asList(new Object[][] { + { /* 0 */ "Json", JsonSerializer.DEFAULT, JsonParser.DEFAULT }, + { /* 1 */ "Xml", XmlSerializer.DEFAULT, XmlParser.DEFAULT }, + { /* 2 */ "Mixed", JsonSerializer.DEFAULT, XmlParser.DEFAULT }, + { /* 3 */ "Html", HtmlSerializer.DEFAULT, HtmlParser.DEFAULT }, + { /* 4 */ "MessagePack", MsgPackSerializer.DEFAULT, MsgPackParser.DEFAULT }, + { /* 5 */ "UrlEncoding", UrlEncodingSerializer.DEFAULT, UrlEncodingParser.DEFAULT }, + { /* 6 */ "Uon", UonSerializer.DEFAULT, UonParser.DEFAULT }, + }); + } + + private Serializer serializer; + private Parser parser; + + public InterfaceProxyTest(String label, Serializer serializer, Parser parser) { + this.serializer = serializer; + this.parser = parser; + } + + private InterfaceProxy getProxy() { + return getClient(serializer, parser).getRemoteableProxy(InterfaceProxy.class, "/testInterfaceProxyResource/proxy"); + } + + @Test + public void returnVoid() { + getProxy().returnVoid(); + } + + @Test + public void returnInteger() { + assertEquals((Integer)1, getProxy().returnInteger()); + } + + @Test + public void returnInt() { + assertEquals(1, getProxy().returnInt()); + } + + @Test + public void returnFloat() { + assertTrue(1f == getProxy().returnFloat()); + } + + @Test + public void returnFloatObject() { + assertTrue(1f == getProxy().returnFloatObject()); + } + + @Test + public void returnString() { + assertEquals("foobar", getProxy().returnString()); + } + + @Test + public void returnNullString() { + assertNull(getProxy().returnNullString()); + } + + @Test + public void returnIntArray() { + assertObjectEquals("[1,2]", getProxy().returnIntArray()); + } + + @Test + public void returnStringArray() { + assertObjectEquals("['foo','bar',null]", getProxy().returnStringArray()); + } + + @Test + public void returnIntegerList() { + assertObjectEquals("[1,2]", getProxy().returnIntegerList()); + assertTrue(getProxy().returnIntegerList().get(0) instanceof Integer); + } + + @Test + public void returnStringList() { + assertObjectEquals("['foo','bar',null]", getProxy().returnStringList()); + assertTrue(getProxy().returnStringList() instanceof List); + } + + @Test + public void returnBean() { + assertObjectEquals("{a:1,b:'foo'}", getProxy().returnBean()); + assertClass(InterfaceProxy.Bean.class, getProxy().returnBean()); + } + + @Test + public void returnBeanArray() { + assertObjectEquals("[{a:1,b:'foo'}]", getProxy().returnBeanArray()); + assertClass(InterfaceProxy.Bean.class, getProxy().returnBeanArray()[0]); + } + + @Test + public void returnBeanList() { + assertObjectEquals("[{a:1,b:'foo'}]", getProxy().returnBeanList()); + assertClass(InterfaceProxy.Bean.class, getProxy().returnBeanList().get(0)); + } + + @Test + public void setNothing() { + getProxy().setNothing(); + } + + @Test + public void setInt() { + getProxy().setInt(1); + } + + @Test + public void setWrongInt() { + try { + getProxy().setInt(2); + fail("Exception expected"); + } catch (Exception e) { + // Good. + } + } + + @Test + public void setInteger() { + getProxy().setInteger(1); + } + + @Test + public void setFloat() { + getProxy().setFloat(1f); + } + + @Test + public void setFloatObject() { + getProxy().setFloatObject(1f); + } + + @Test + public void setString() { + getProxy().setString("foo"); + } + + @Test + public void setNullString() { + getProxy().setNullString(null); + } + + @Test + public void setNullStringBad() { + try { + getProxy().setNullString("foo"); + fail("Exception expected"); + } catch (Exception e) { + // Good. + } + } + + @Test + public void setIntArray() { + getProxy().setIntArray(new int[]{1,2}); + } + + @Test + public void setStringArray() { + getProxy().setStringArray(new String[]{"foo","bar",null}); + } + + @Test + public void setIntegerList() { + getProxy().setIntegerList(Arrays.asList(new Integer[]{1,2,null})); + } + + @Test + public void setStringList() { + getProxy().setStringList(Arrays.asList("foo","bar",null)); + } + + @Test + public void setBean() { + getProxy().setBean(new Bean().init()); + } + + @Test + public void setBeanArray() { + getProxy().setBeanArray(new Bean[]{new Bean().init()}); + } + + @Test + public void setBeanList() { + getProxy().setBeanList(Arrays.asList(new Bean().init())); + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/RestTestcase.java ---------------------------------------------------------------------- diff --git a/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/RestTestcase.java b/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/RestTestcase.java index 70d4a5d..1a23177 100644 --- a/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/RestTestcase.java +++ b/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/RestTestcase.java @@ -12,6 +12,12 @@ // *************************************************************************************************************************** package org.apache.juneau.rest.test; +import java.io.*; +import java.util.*; + +import org.apache.juneau.parser.*; +import org.apache.juneau.rest.client.*; +import org.apache.juneau.serializer.*; import org.junit.*; /** @@ -22,15 +28,33 @@ import org.junit.*; public class RestTestcase { private static boolean microserviceStarted; + private static List<RestClient> clients = new ArrayList<RestClient>(); @BeforeClass public static void setUp() { microserviceStarted = TestMicroservice.startMicroservice(); } + /** + * Creates a REST client against the test microservice using the specified serializer and parser. + * Client is automatically closed on tear-down. + */ + protected RestClient getClient(Serializer serializer, Parser parser) { + RestClient rc = TestMicroservice.client(serializer, parser).build(); + clients.add(rc); + return rc; + } + @AfterClass public static void tearDown() { if (microserviceStarted) TestMicroservice.stopMicroservice(); + for (RestClient rc : clients) { + try { + rc.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } } } http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/TestMicroservice.java ---------------------------------------------------------------------- diff --git a/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/TestMicroservice.java b/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/TestMicroservice.java index 25a740d..a2f0777 100644 --- a/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/TestMicroservice.java +++ b/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/TestMicroservice.java @@ -96,7 +96,7 @@ public class TestMicroservice { try { return new RestClientBuilder() .rootUrl(microserviceURI) - // .httpClient(createHttpClient(), true) + .noTrace() ; } catch (Exception e) { throw new RuntimeException(e); http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/TestUtils.java ---------------------------------------------------------------------- diff --git a/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/TestUtils.java b/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/TestUtils.java index caf9041..102c17e 100644 --- a/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/TestUtils.java +++ b/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/TestUtils.java @@ -37,6 +37,13 @@ public class TestUtils { } /** + * Assert that the object is an instance of the specified class. + */ + public static void assertClass(Class<?> c, Object o) { + Assert.assertEquals(c, o == null ? null : o.getClass()); + } + + /** * Assert that the object equals the specified string after running it through ws.toString(). */ public static void assertObjectEquals(String s, Object o, WriterSerializer ws) { http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/_TestSuite.java ---------------------------------------------------------------------- diff --git a/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/_TestSuite.java b/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/_TestSuite.java index 2eb975c..0714aca 100644 --- a/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/_TestSuite.java +++ b/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/_TestSuite.java @@ -36,6 +36,7 @@ import org.junit.runners.Suite.*; GroupsTest.class, GzipTest.class, InheritanceTest.class, + InterfaceProxyTest.class, JacocoDummyTest.class, LargePojosTest.class, MessagesTest.class, http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-rest/src/main/java/org/apache/juneau/rest/CallMethod.java ---------------------------------------------------------------------- diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/CallMethod.java b/juneau-rest/src/main/java/org/apache/juneau/rest/CallMethod.java index 5cfa4e5..2fb2560 100644 --- a/juneau-rest/src/main/java/org/apache/juneau/rest/CallMethod.java +++ b/juneau-rest/src/main/java/org/apache/juneau/rest/CallMethod.java @@ -44,7 +44,7 @@ import org.apache.juneau.urlencoding.*; * Represents a single Java servlet/resource method annotated with {@link RestMethod @RestMethod}. */ @SuppressWarnings("hiding") -final class CallMethod implements Comparable<CallMethod> { +class CallMethod implements Comparable<CallMethod> { private final java.lang.reflect.Method method; private final String httpMethod; private final UrlPathPattern pathPattern; @@ -60,7 +60,7 @@ final class CallMethod implements Comparable<CallMethod> { private final UrlEncodingSerializer urlEncodingSerializer; private final ObjectMap properties; private final Map<String,String> defaultRequestHeaders; - private final String defaultEncoding; + private final String defaultCharset; private final boolean deprecated; private final String description, tags, summary, externalDocs; private final Integer priority; @@ -86,7 +86,7 @@ final class CallMethod implements Comparable<CallMethod> { this.urlEncodingSerializer = b.urlEncodingSerializer; this.properties = b.properties; this.defaultRequestHeaders = b.defaultRequestHeaders; - this.defaultEncoding = b.defaultEncoding; + this.defaultCharset = b.defaultCharset; this.deprecated = b.deprecated; this.description = b.description; this.tags = b.tags; @@ -98,7 +98,7 @@ final class CallMethod implements Comparable<CallMethod> { } private static class Builder { - private String httpMethod, defaultEncoding, description, tags, summary, externalDocs; + private String httpMethod, defaultCharset, description, tags, summary, externalDocs; private UrlPathPattern pathPattern; private CallMethod.MethodParam[] params; private RestGuard[] guards; @@ -263,7 +263,7 @@ final class CallMethod implements Comparable<CallMethod> { defaultRequestHeaders.put(h[0], h[1]); } - defaultEncoding = properties.getString(REST_defaultCharset, context.getDefaultCharset()); + defaultCharset = properties.getString(REST_defaultCharset, context.getDefaultCharset()); String paramFormat = properties.getString(REST_paramFormat, context.getParamFormat()); plainParams = paramFormat.equals("PLAIN"); @@ -808,8 +808,8 @@ final class CallMethod implements Comparable<CallMethod> { for (int i = 0; i < pathPattern.getVars().length; i++) req.setPathParameter(pathPattern.getVars()[i], patternVals[i]); - req.init(method, remainder, createRequestProperties(properties, req), defaultRequestHeaders, defaultEncoding, serializers, parsers, urlEncodingParser, encoders); - res.init(req.getProperties(), defaultEncoding, serializers, urlEncodingSerializer, encoders); + req.init(method, remainder, createRequestProperties(properties, req), defaultRequestHeaders, defaultCharset, serializers, parsers, urlEncodingParser, encoders); + res.init(req.getProperties(), defaultCharset, serializers, urlEncodingSerializer, encoders); // Class-level guards for (RestGuard guard : context.getGuards()) http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-rest/src/main/java/org/apache/juneau/rest/RestContext.java ---------------------------------------------------------------------- diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/RestContext.java b/juneau-rest/src/main/java/org/apache/juneau/rest/RestContext.java index 29113f4..89abdc4 100644 --- a/juneau-rest/src/main/java/org/apache/juneau/rest/RestContext.java +++ b/juneau-rest/src/main/java/org/apache/juneau/rest/RestContext.java @@ -399,14 +399,58 @@ public final class RestContext extends Context { throw new RestServletException("@RestMethod method {0}.{1} must be defined as public.", this.getClass().getName(), method.getName()); CallMethod sm = new CallMethod(resource, method, this); - _javaRestMethods.put(method.getName(), sm); - String httpMethod = sm.getHttpMethod(); - if (! routers.containsKey(httpMethod)) - routers.put(httpMethod, new CallRouter.Builder(httpMethod)); - - routers.get(httpMethod).add(sm); + // PROXY is a special case where a method returns an interface that we + // can perform REST calls against. + // We override the CallMethod.invoke() method to insert our logic. + if ("PROXY".equals(httpMethod)) { + + final ClassMeta<?> interfaceClass = beanContext.getClassMeta(method.getGenericReturnType()); + sm = new CallMethod(resource, method, this) { + + @Override + int invoke(String pathInfo, RestRequest req, RestResponse res) throws RestException { + + int rc = super.invoke(pathInfo, req, res); + if (rc != SC_OK) + return rc; + + final Object o = res.getOutput(); + + if ("GET".equals(req.getMethod())) { + res.setOutput(ClassUtils.getMethodInfo(interfaceClass.getProxyableMethods().values())); + return SC_OK; + + } else if ("POST".equals(req.getMethod())) { + if (pathInfo.indexOf('/') != -1) + pathInfo = pathInfo.substring(pathInfo.lastIndexOf('/')+1); + pathInfo = RestUtils.decode(pathInfo); + java.lang.reflect.Method m = interfaceClass.getProxyableMethods().get(pathInfo); + if (m != null) { + try { + // Parse the args and invoke the method. + Parser p = req.getParser(); + Object input = p.isReaderParser() ? req.getReader() : req.getInputStream(); + res.setOutput(m.invoke(o, p.parseArgs(input, m.getGenericParameterTypes()))); + return SC_OK; + } catch (Exception e) { + throw new RestException(SC_INTERNAL_SERVER_ERROR, e); + } + } + } + return SC_NOT_FOUND; + } + }; + + _javaRestMethods.put(method.getName(), sm); + addToRouter(routers, "GET", sm); + addToRouter(routers, "POST", sm); + + } else { + _javaRestMethods.put(method.getName(), sm); + addToRouter(routers, httpMethod, sm); + } } catch (RestServletException e) { throw new RestServletException("Problem occurred trying to serialize methods on class {0}, methods={1}", this.getClass().getName(), JsonSerializer.DEFAULT_LAX.serialize(methodsFound)).initCause(e); } @@ -484,6 +528,12 @@ public final class RestContext extends Context { } } + private static void addToRouter(Map<String, CallRouter.Builder> routers, String httpMethodName, CallMethod cm) throws RestServletException { + if (! routers.containsKey(httpMethodName)) + routers.put(httpMethodName, new CallRouter.Builder(httpMethodName)); + routers.get(httpMethodName).add(cm); + } + private static class Builder { boolean allowHeaderParams, allowBodyParam, renderResponseStackTraces, useStackTraceHashes; @@ -1416,5 +1466,4 @@ public final class RestContext extends Context { throw new RestServletException("Exception occurred while constructing class ''{0}''", c).initCause(e); } } - } http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-rest/src/main/java/org/apache/juneau/rest/RestLogger.java ---------------------------------------------------------------------- diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/RestLogger.java b/juneau-rest/src/main/java/org/apache/juneau/rest/RestLogger.java index 2b55b47..8a13d2f 100644 --- a/juneau-rest/src/main/java/org/apache/juneau/rest/RestLogger.java +++ b/juneau-rest/src/main/java/org/apache/juneau/rest/RestLogger.java @@ -148,7 +148,8 @@ public abstract class RestLogger { * <p> * Subclasses can override this method to provide their own logic for determining when exceptions are logged. * <p> - * The default implementation will return <jk>false</jk> if <js>"noTrace=true"</js> is passed in the query string. + * The default implementation will return <jk>false</jk> if <js>"noTrace=true"</js> is passed in the query string + * or <code>No-Trace: true</code> is specified in the header. * * @param req The HTTP request. * @param res The HTTP response. @@ -156,6 +157,8 @@ public abstract class RestLogger { * @return <jk>true</jk> if exception should be logged. */ protected boolean shouldLog(HttpServletRequest req, HttpServletResponse res, RestException e) { + if ("true".equals(req.getHeader("No-Trace"))) + return false; String q = req.getQueryString(); return (q == null ? true : q.indexOf("noTrace=true") == -1); } http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-rest/src/main/java/org/apache/juneau/rest/RestRequest.java ---------------------------------------------------------------------- diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/RestRequest.java b/juneau-rest/src/main/java/org/apache/juneau/rest/RestRequest.java index 98aba93..cd5d42c 100644 --- a/juneau-rest/src/main/java/org/apache/juneau/rest/RestRequest.java +++ b/juneau-rest/src/main/java/org/apache/juneau/rest/RestRequest.java @@ -67,7 +67,8 @@ public final class RestRequest extends HttpServletRequestWrapper { private final RestContext context; private final String method; - private String pathRemainder, body; + private String pathRemainder; + private byte[] body; private Method javaMethod; private ObjectMap properties; private SerializerGroup serializerGroup; @@ -118,14 +119,16 @@ public final class RestRequest extends HttpServletRequestWrapper { method = _method; if (context.isAllowBodyParam()) { - body = getQueryParameter("body"); - if (body != null) + String b = getQueryParameter("body"); + if (b != null) { setHeader("Content-Type", UonSerializer.DEFAULT.getResponseContentType()); + this.body = b.getBytes(IOUtils.UTF8); + } } defaultServletHeaders = context.getDefaultRequestHeaders(); - debug = "true".equals(getQueryParameter("debug", "false")); + debug = "true".equals(getQueryParameter("debug", "false")) || "true".equals(getHeader("Debug", "false")); if (debug) { context.getLogger().log(Level.INFO, toString()); @@ -1216,10 +1219,9 @@ public final class RestRequest extends HttpServletRequestWrapper { * @throws IOException If a problem occurred trying to read from the reader. */ public String getBodyAsString() throws IOException { - if (body != null) - return body; - body = IOUtils.read(getReader()).toString(); - return body; + if (body == null) + body = IOUtils.readBytes(getInputStream(), 1024); + return new String(body, IOUtils.UTF8); } /** @@ -1247,7 +1249,7 @@ public final class RestRequest extends HttpServletRequestWrapper { */ protected Reader getUnbufferedReader() throws IOException { if (body != null) - return new CharSequenceReader(body); + return new CharSequenceReader(new String(body, IOUtils.UTF8)); return new InputStreamReader(getInputStream(), getCharacterEncoding()); } @@ -1263,21 +1265,15 @@ public final class RestRequest extends HttpServletRequestWrapper { @Override /* ServletRequest */ public ServletInputStream getInputStream() throws IOException { + if (body != null) + return new ServletInputStream2(body); + Encoder enc = getEncoder(); ServletInputStream is = super.getInputStream(); if (enc != null) { final InputStream is2 = enc.getInputStream(is); - return new ServletInputStream() { - @Override /* InputStream */ - public final int read() throws IOException { - return is2.read(); - } - @Override /* InputStream */ - public final void close() throws IOException { - is2.close(); - } - }; + return new ServletInputStream2(is2); } return is; } @@ -1893,7 +1889,9 @@ public final class RestRequest extends HttpServletRequestWrapper { for (Map.Entry<String,String> e : defaultServletHeaders.entrySet()) { sb.append("\t").append(e.getKey()).append(": ").append(e.getValue()).append("\n"); } - if (method.equals("PUT") || method.equals("POST")) { + if (javaMethod == null) { + sb.append("***init() not called yet!***\n"); + } else if (method.equals("PUT") || method.equals("POST")) { sb.append("---Body---\n"); try { sb.append(getBodyAsString()).append("\n"); @@ -1985,4 +1983,30 @@ public final class RestRequest extends HttpServletRequestWrapper { void setJavaMethod(Method method) { this.javaMethod = method; } + + /** + * ServletInputStream wrapper around a normal input stream. + */ + private static class ServletInputStream2 extends ServletInputStream { + + private final InputStream is; + + private ServletInputStream2(InputStream is) { + this.is = is; + } + + private ServletInputStream2(byte[] b) { + this(new ByteArrayInputStream(b)); + } + + @Override /* InputStream */ + public final int read() throws IOException { + return is.read(); + } + + @Override /* InputStream */ + public final void close() throws IOException { + is.close(); + } + } } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-rest/src/main/java/org/apache/juneau/rest/annotation/RestMethod.java ---------------------------------------------------------------------- diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/annotation/RestMethod.java b/juneau-rest/src/main/java/org/apache/juneau/rest/annotation/RestMethod.java index 4856070..4270a38 100644 --- a/juneau-rest/src/main/java/org/apache/juneau/rest/annotation/RestMethod.java +++ b/juneau-rest/src/main/java/org/apache/juneau/rest/annotation/RestMethod.java @@ -17,6 +17,7 @@ import static java.lang.annotation.RetentionPolicy.*; import java.lang.annotation.*; +import org.apache.juneau.annotation.*; import org.apache.juneau.encoders.*; import org.apache.juneau.parser.*; import org.apache.juneau.rest.*; @@ -38,13 +39,26 @@ public @interface RestMethod { * <p> * Typically <js>"GET"</js>, <js>"PUT"</js>, <js>"POST"</js>, <js>"DELETE"</js>, or <js>"OPTIONS"</js>. * <p> - * Can also be a non-HTTP-standard name that is passed in through a <code>&method=methodName</code> URL parameter. - * <p> * Method names are case-insensitive (always folded to upper-case). * <p> - * If a method name is not specified, then the method name is determined based on the Java method name.<br> - * For example, if the method is <code>doPost(...)</code>, then the method name is automatically detected as <js>"POST"</js>. - + * Besides the standard HTTP method names, the following can also be specified: + * <ul> + * <li><js>"*"</js> - Denotes any method. + * <br>Use this if you want to capture any HTTP methods in a single Java method. + * <br>The {@link Method @Method} annotation and/or {@link RestRequest#getMethod()} method can be used + * to distinguish the actual HTTP method name. + * <li><js>""</js> - Auto-detect. + * <br>The method name is determined based on the Java method name. + * <br>For example, if the method is <code>doPost(...)</code>, then the method name is automatically detected as <js>"POST"</js>. + * <li><js>"PROXY"</js> - Remote-proxy interface. + * <br>This denotes a Java method that returns an object (usually an interface, often annotated with the {@link Remoteable @Remoteable} annotation) + * to be used as a remote proxy using <code>RestClient.getRemoteableProxy(Class<T> interfaceClass, String url)</code>. + * <br>This allows you to construct client-side interface proxies using REST as a transport medium. + * <br>Conceptually, this is simply a fancy <code>POST</code> against the url <js>"/{path}/{javaMethodName}"</js> where the arguments + * are marshalled from the client to the server as an HTTP body containing an array of objects, + * passed to the method as arguments, and then the resulting object is marshalled back to the client. + * <li>Anything else - Overloaded non-HTTP-standard names that are passed in through a <code>&method=methodName</code> URL parameter. + * </ul> */ String name() default ""; http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-rest/src/main/java/org/apache/juneau/rest/remoteable/RemoteableServlet.java ---------------------------------------------------------------------- diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/remoteable/RemoteableServlet.java b/juneau-rest/src/main/java/org/apache/juneau/rest/remoteable/RemoteableServlet.java index 15a96ad..c6bc0f4 100644 --- a/juneau-rest/src/main/java/org/apache/juneau/rest/remoteable/RemoteableServlet.java +++ b/juneau-rest/src/main/java/org/apache/juneau/rest/remoteable/RemoteableServlet.java @@ -115,7 +115,7 @@ public abstract class RemoteableServlet extends RestServletDefault { throw new RestException(SC_NOT_FOUND, "Method not found"); //$NON-NLS-1$ // Parse the args and invoke the method. - ClassMeta<?>[] argTypes = req.getBeanSession().getClassMetas(m.getParameterTypes()); + ClassMeta<?>[] argTypes = req.getBeanSession().getClassMetas(m.getGenericParameterTypes()); Object[] params = p.parseArgs(req.getReader(), argTypes); return m.invoke(service, params); }
