diff --git a/ext/proxy-client/src/main/java/org/glassfish/jersey/client/proxy/RequestParameters.java b/ext/proxy-client/src/main/java/org/glassfish/jersey/client/proxy/RequestParameters.java new file mode 100644 index 0000000000..007fbefde8 --- /dev/null +++ b/ext/proxy-client/src/main/java/org/glassfish/jersey/client/proxy/RequestParameters.java @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2012, 2021 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.client.proxy; + +import jakarta.ws.rs.BeanParam; +import jakarta.ws.rs.CookieParam; +import jakarta.ws.rs.FormParam; +import jakarta.ws.rs.HeaderParam; +import jakarta.ws.rs.MatrixParam; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.Cookie; +import jakarta.ws.rs.core.Form; +import jakarta.ws.rs.core.MultivaluedHashMap; +import jakarta.ws.rs.core.MultivaluedMap; + +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * Collector to retrieve parameters for setting up the HTTP request sent in the invoke method of WebResourceFactory + * The addParameter method takes a single annotated method parameter or annotated field or property of a BeanParam + * and adds the information to the web target, headers, cookie list or form. + */ +class RequestParameters { + + private WebTarget newTarget; + private final MultivaluedHashMap headers; + private final LinkedList cookies; + private final Form form; + + private static final List> PARAM_ANNOTATION_CLASSES = Arrays.asList(PathParam.class, QueryParam.class, + HeaderParam.class, CookieParam.class, MatrixParam.class, FormParam.class, BeanParam.class); + + RequestParameters(final WebTarget newTarget, final MultivaluedMap headers, + final List cookies, final Form form) { + + this.headers = new MultivaluedHashMap<>(headers); + this.cookies = new LinkedList<>(cookies); + this.form = new Form(); + this.form.asMap().putAll(form.asMap()); + + this.newTarget = newTarget; + } + + void addParameter(final Object value, final Map, Annotation> anns) + throws IntrospectionException, InvocationTargetException, IllegalAccessException { + + Annotation ann; + if ((ann = anns.get(PathParam.class)) != null) { + newTarget = newTarget.resolveTemplate(((PathParam) ann).value(), value); + } else if ((ann = anns.get((QueryParam.class))) != null) { + if (value instanceof Collection) { + newTarget = newTarget.queryParam(((QueryParam) ann).value(), convert((Collection) value)); + } else { + newTarget = newTarget.queryParam(((QueryParam) ann).value(), value); + } + } else if ((ann = anns.get((HeaderParam.class))) != null) { + if (value instanceof Collection) { + headers.addAll(((HeaderParam) ann).value(), convert((Collection) value)); + } else { + headers.addAll(((HeaderParam) ann).value(), value); + } + + } else if ((ann = anns.get((CookieParam.class))) != null) { + final String name = ((CookieParam) ann).value(); + Cookie c; + if (value instanceof Collection) { + for (final Object v : ((Collection) value)) { + if (!(v instanceof Cookie)) { + c = new Cookie(name, v.toString()); + } else { + c = (Cookie) v; + if (!name.equals(((Cookie) v).getName())) { + // is this the right thing to do? or should I fail? or ignore the difference? + c = new Cookie(name, c.getValue(), c.getPath(), c.getDomain(), c.getVersion()); + } + } + cookies.add(c); + } + } else { + if (!(value instanceof Cookie)) { + cookies.add(new Cookie(name, value.toString())); + } else { + c = (Cookie) value; + if (!name.equals(((Cookie) value).getName())) { + // is this the right thing to do? or should I fail? or ignore the difference? + cookies.add(new Cookie(name, c.getValue(), c.getPath(), c.getDomain(), c.getVersion())); + } + } + } + } else if ((ann = anns.get((MatrixParam.class))) != null) { + if (value instanceof Collection) { + newTarget = newTarget.matrixParam(((MatrixParam) ann).value(), convert((Collection) value)); + } else { + newTarget = newTarget.matrixParam(((MatrixParam) ann).value(), value); + } + } else if ((ann = anns.get((FormParam.class))) != null) { + if (value instanceof Collection) { + for (final Object v : ((Collection) value)) { + form.param(((FormParam) ann).value(), v.toString()); + } + } else { + form.param(((FormParam) ann).value(), value.toString()); + } + } else if ((anns.get((BeanParam.class))) != null) { + if (value instanceof Collection) { + for (final Object v : ((Collection) value)) { + addBeanParameter(v); + } + } else { + addBeanParameter(value); + } + } + } + + private void addBeanParameter(final Object beanParam) + throws IllegalAccessException, IntrospectionException, InvocationTargetException { + Class beanClass = beanParam.getClass(); + List fields = new ArrayList<>(); + getAllFields(fields, beanClass); + + for (final Field field : fields) { + Object value = null; + final Map, Annotation> anns = new HashMap<>(); + + // get field annotations + for (final Annotation ann : field.getAnnotations()) { + anns.put(ann.annotationType(), ann); + } + + if (hasAnyParamAnnotation(anns)) { + value = field.get(beanParam); + } else { + // get getter annotations if there are no field annotations + for (final PropertyDescriptor pd : Introspector.getBeanInfo(beanClass).getPropertyDescriptors()) { + if (pd.getName().equals(field.getName()) && pd.getReadMethod() != null) { + for (final Annotation ann : pd.getReadMethod().getAnnotations()) { + anns.put(ann.annotationType(), ann); + } + if (hasAnyParamAnnotation(anns)) { + value = pd.getReadMethod().invoke(beanParam); + } + } + } + } + + if (value != null) { + addParameter(value, anns); + } + } + } + + private List getAllFields(List fields, Class type) { + fields.addAll(Arrays.asList(type.getDeclaredFields())); + + if (type.getSuperclass() != null) { + getAllFields(fields, type.getSuperclass()); + } + + return fields; + } + + private Object[] convert(final Collection value) { + return value.toArray(); + } + + public static boolean hasAnyParamAnnotation(final Map, Annotation> anns) { + for (final Class paramAnnotationClass : PARAM_ANNOTATION_CLASSES) { + if (anns.containsKey(paramAnnotationClass)) { + return true; + } + } + return false; + } + + WebTarget getNewTarget() { + return newTarget; + } + + MultivaluedHashMap getHeaders() { + return headers; + } + + LinkedList getCookies() { + return cookies; + } + + Form getForm() { + return form; + } + +} diff --git a/ext/proxy-client/src/main/java/org/glassfish/jersey/client/proxy/WebResourceFactory.java b/ext/proxy-client/src/main/java/org/glassfish/jersey/client/proxy/WebResourceFactory.java index 585457f6b0..b1cecb7039 100644 --- a/ext/proxy-client/src/main/java/org/glassfish/jersey/client/proxy/WebResourceFactory.java +++ b/ext/proxy-client/src/main/java/org/glassfish/jersey/client/proxy/WebResourceFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2021 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -18,20 +18,19 @@ import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; -import java.lang.reflect.ParameterizedType; import java.lang.reflect.Proxy; +import java.lang.reflect.InvocationHandler; import java.lang.reflect.Type; +import java.lang.reflect.ParameterizedType; import java.security.AccessController; import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.HashMap; -import java.util.LinkedList; import java.util.List; import java.util.Map; +import jakarta.ws.rs.BeanParam; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.CookieParam; import jakarta.ws.rs.DefaultValue; @@ -75,8 +74,8 @@ public final class WebResourceFactory implements InvocationHandler { private static final MultivaluedMap EMPTY_HEADERS = new MultivaluedHashMap<>(); private static final Form EMPTY_FORM = new Form(); - private static final List PARAM_ANNOTATION_CLASSES = Arrays.asList(PathParam.class, QueryParam.class, - HeaderParam.class, CookieParam.class, MatrixParam.class, FormParam.class); + private static final List> PARAM_ANNOTATION_CLASSES = Arrays.asList(PathParam.class, QueryParam.class, + HeaderParam.class, CookieParam.class, MatrixParam.class, FormParam.class, BeanParam.class); /** * Creates a new client-side representation of a resource described by @@ -92,7 +91,7 @@ public final class WebResourceFactory implements InvocationHandler { * be used for making requests to the server. */ public static C newResource(final Class resourceInterface, final WebTarget target) { - return newResource(resourceInterface, target, false, EMPTY_HEADERS, Collections.emptyList(), EMPTY_FORM); + return newResource(resourceInterface, target, false, EMPTY_HEADERS, Collections.emptyList(), EMPTY_FORM); } /** @@ -182,93 +181,35 @@ public Object invoke(final Object proxy, final Method method, final Object[] arg // process method params (build maps of (Path|Form|Cookie|Matrix|Header..)Params // and extract entity type - final MultivaluedHashMap headers = new MultivaluedHashMap(this.headers); - final LinkedList cookies = new LinkedList<>(this.cookies); - final Form form = new Form(); - form.asMap().putAll(this.form.asMap()); + RequestParameters requestParameters = new RequestParameters(newTarget, headers, cookies, form); final Annotation[][] paramAnns = method.getParameterAnnotations(); Object entity = null; Type entityType = null; for (int i = 0; i < paramAnns.length; i++) { - final Map anns = new HashMap<>(); + final Map, Annotation> anns = new HashMap<>(); for (final Annotation ann : paramAnns[i]) { anns.put(ann.annotationType(), ann); } - Annotation ann; Object value = args[i]; - if (!hasAnyParamAnnotation(anns)) { + if (!RequestParameters.hasAnyParamAnnotation(anns)) { entityType = method.getGenericParameterTypes()[i]; entity = value; } else { + Annotation ann; if (value == null && (ann = anns.get(DefaultValue.class)) != null) { value = ((DefaultValue) ann).value(); } - if (value != null) { - if ((ann = anns.get(PathParam.class)) != null) { - newTarget = newTarget.resolveTemplate(((PathParam) ann).value(), value); - } else if ((ann = anns.get((QueryParam.class))) != null) { - if (value instanceof Collection) { - newTarget = newTarget.queryParam(((QueryParam) ann).value(), convert((Collection) value)); - } else { - newTarget = newTarget.queryParam(((QueryParam) ann).value(), value); - } - } else if ((ann = anns.get((HeaderParam.class))) != null) { - if (value instanceof Collection) { - headers.addAll(((HeaderParam) ann).value(), convert((Collection) value)); - } else { - headers.addAll(((HeaderParam) ann).value(), value); - } - - } else if ((ann = anns.get((CookieParam.class))) != null) { - final String name = ((CookieParam) ann).value(); - Cookie c; - if (value instanceof Collection) { - for (final Object v : ((Collection) value)) { - if (!(v instanceof Cookie)) { - c = new Cookie(name, v.toString()); - } else { - c = (Cookie) v; - if (!name.equals(((Cookie) v).getName())) { - // is this the right thing to do? or should I fail? or ignore the difference? - c = new Cookie(name, c.getValue(), c.getPath(), c.getDomain(), c.getVersion()); - } - } - cookies.add(c); - } - } else { - if (!(value instanceof Cookie)) { - cookies.add(new Cookie(name, value.toString())); - } else { - c = (Cookie) value; - if (!name.equals(((Cookie) value).getName())) { - // is this the right thing to do? or should I fail? or ignore the difference? - cookies.add(new Cookie(name, c.getValue(), c.getPath(), c.getDomain(), c.getVersion())); - } - } - } - } else if ((ann = anns.get((MatrixParam.class))) != null) { - if (value instanceof Collection) { - newTarget = newTarget.matrixParam(((MatrixParam) ann).value(), convert((Collection) value)); - } else { - newTarget = newTarget.matrixParam(((MatrixParam) ann).value(), value); - } - } else if ((ann = anns.get((FormParam.class))) != null) { - if (value instanceof Collection) { - for (final Object v : ((Collection) value)) { - form.param(((FormParam) ann).value(), v.toString()); - } - } else { - form.param(((FormParam) ann).value(), value.toString()); - } - } + requestParameters.addParameter(value, anns); } } } + newTarget = requestParameters.getNewTarget(); if (httpMethod == null) { // the method is a subresource locator - return WebResourceFactory.newResource(responseType, newTarget, true, headers, cookies, form); + return WebResourceFactory.newResource(responseType, newTarget, true, + requestParameters.getHeaders(), requestParameters.getCookies(), requestParameters.getForm()); } // accepted media types @@ -281,7 +222,7 @@ public Object invoke(final Object proxy, final Method method, final Object[] arg // determine content type String contentType = null; if (entity != null) { - final List contentTypeEntries = headers.get(HttpHeaders.CONTENT_TYPE); + final List contentTypeEntries = requestParameters.getHeaders().get(HttpHeaders.CONTENT_TYPE); if ((contentTypeEntries != null) && (!contentTypeEntries.isEmpty())) { contentType = contentTypeEntries.get(0).toString(); } else { @@ -296,32 +237,32 @@ public Object invoke(final Object proxy, final Method method, final Object[] arg } Invocation.Builder builder = newTarget.request() - .headers(headers) // this resets all headers so do this first + .headers(requestParameters.getHeaders()) // this resets all headers so do this first .accept(accepts); // if @Produces is defined, propagate values into Accept header; empty array is NO-OP - for (final Cookie c : cookies) { + for (final Cookie c : requestParameters.getCookies()) { builder = builder.cookie(c); } final Object result; - if (entity == null && !form.asMap().isEmpty()) { - entity = form; + if (entity == null && !requestParameters.getForm().asMap().isEmpty()) { + entity = requestParameters.getForm(); contentType = MediaType.APPLICATION_FORM_URLENCODED; } else { if (contentType == null) { contentType = MediaType.APPLICATION_OCTET_STREAM; } - if (!form.asMap().isEmpty()) { + if (!requestParameters.getForm().asMap().isEmpty()) { if (entity instanceof Form) { - ((Form) entity).asMap().putAll(form.asMap()); + ((Form) entity).asMap().putAll(requestParameters.getForm().asMap()); } else { // TODO: should at least log some warning here } } } - final GenericType responseGenericType = new GenericType(method.getGenericReturnType()); + final GenericType responseGenericType = new GenericType(method.getGenericReturnType()); if (entity != null) { if (entityType instanceof ParameterizedType) { entity = new GenericEntity(entity, entityType); @@ -334,18 +275,6 @@ public Object invoke(final Object proxy, final Method method, final Object[] arg return result; } - private boolean hasAnyParamAnnotation(final Map anns) { - for (final Class paramAnnotationClass : PARAM_ANNOTATION_CLASSES) { - if (anns.containsKey(paramAnnotationClass)) { - return true; - } - } - return false; - } - - private Object[] convert(final Collection value) { - return value.toArray(); - } private static WebTarget addPathFromAnnotation(final AnnotatedElement ae, WebTarget target) { final Path p = ae.getAnnotation(Path.class); diff --git a/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/MyBeanParam.java b/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/MyBeanParam.java new file mode 100644 index 0000000000..70bb227ae5 --- /dev/null +++ b/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/MyBeanParam.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.client.proxy; + +import jakarta.ws.rs.FormParam; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.core.Cookie; + +import java.util.List; + +/** + * @author Richard Obersheimer + */ +public class MyBeanParam extends MyGetBeanParam { + + @FormParam("formParam1") + String formParam1; + + @FormParam("formParam2") + String formParam2; + + String queryParam2; + + public MyBeanParam(String headerParam, String pathParam, String queryParam, String formParam1, String formParam2, + List matrixParam, Cookie cookieParam, MySubBeanParam subBeanParam) { + this.headerParam = headerParam; + this.pathParam = pathParam; + this.queryParam = queryParam; + this.formParam1 = formParam1; + this.formParam2 = formParam2; + this.matrixParam = matrixParam; + this.cookieParam = cookieParam; + this.subBeanParam = subBeanParam; + } + + public MyBeanParam() {} + + public String getFormParam1() { + return formParam1; + } + + public void setFormParam1(String formParam1) { + this.formParam1 = formParam1; + } + + public String getFormParam2() { + return formParam2; + } + + public void setFormParam2(String formParam2) { + this.formParam2 = formParam2; + } + + @QueryParam("queryParam2") + public String getQueryParam2() { + return queryParam2; + } + + @QueryParam("queryParam2") + public void setQueryParam2(String queryParam2) { + this.queryParam2 = queryParam2; + } + +} diff --git a/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/MyGetBeanParam.java b/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/MyGetBeanParam.java new file mode 100644 index 0000000000..18d4d6052c --- /dev/null +++ b/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/MyGetBeanParam.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.client.proxy; + +import jakarta.ws.rs.BeanParam; +import jakarta.ws.rs.CookieParam; +import jakarta.ws.rs.HeaderParam; +import jakarta.ws.rs.MatrixParam; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.core.Cookie; + +import java.util.List; + +/** + * @author Richard Obersheimer + */ +public class MyGetBeanParam { + + @HeaderParam("headerParam") + String headerParam; + + @PathParam("pathParam") + String pathParam; + + @QueryParam("queryParam") + String queryParam; + + @MatrixParam("matrixParam") + List matrixParam; + + @CookieParam("cookieParam") + Cookie cookieParam; + + @BeanParam + MySubBeanParam subBeanParam; + + public MyGetBeanParam() {} + + public String getHeaderParam() { + return headerParam; + } + + public void setHeaderParam(String headerParam) { + this.headerParam = headerParam; + } + + public String getPathParam() { + return pathParam; + } + + public void setPathParam(String pathParam) { + this.pathParam = pathParam; + } + + public String getQueryParam() { + return queryParam; + } + + public void setQueryParam(String queryParam) { + this.queryParam = queryParam; + } + + public List getMatrixParam() { + return matrixParam; + } + + public void setMatrixParam(List matrixParam) { + this.matrixParam = matrixParam; + } + + public Cookie getCookieParam() { + return cookieParam; + } + + public void setCookieParam(Cookie cookieParam) { + this.cookieParam = cookieParam; + } + + public MySubBeanParam getSubBeanParam() { + return subBeanParam; + } + + public void setSubBeanParam(MySubBeanParam subBeanParam) { + this.subBeanParam = subBeanParam; + } +} diff --git a/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/MyResourceWithBeanParam.java b/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/MyResourceWithBeanParam.java new file mode 100644 index 0000000000..a1d65e7dd9 --- /dev/null +++ b/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/MyResourceWithBeanParam.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.client.proxy; + +import jakarta.ws.rs.BeanParam; + +/** + * @author Richard Obersheimer + */ +public class MyResourceWithBeanParam implements MyResourceWithBeanParamIfc { + + @Override + public String echoQuery(MyGetBeanParam bean) { + return bean.getQueryParam(); + } + + @Override + public String echoHeader(@BeanParam MyGetBeanParam bean) { + return bean.getHeaderParam(); + } + + @Override + public String echoPath(@BeanParam MyGetBeanParam bean) { + return bean.getPathParam(); + } + + @Override + public String echoCookie(@BeanParam MyGetBeanParam bean) { + return bean.getCookieParam().getValue(); + } + + @Override + public String echoMatrix(@BeanParam MyGetBeanParam bean) { + return bean.getMatrixParam().toString(); + } + + @Override + public String echoSubBean(@BeanParam MyGetBeanParam bean) { + return bean.getSubBeanParam().getSubQueryParam().toString(); + } + + @Override + public String echo(MyBeanParam bean) { + return ("HEADER=" + bean.getHeaderParam() + ",PATH=" + bean.getPathParam() + ",FORM=" + + bean.getFormParam1() + "," + bean.getFormParam2() + ",QUERY=" + bean.getQueryParam() + + ",MATRIX=" + bean.getMatrixParam().size() + ",COOKIE=" + bean.getCookieParam().getValue() + + ",SUB=" + bean.getSubBeanParam().getSubQueryParam().size() + + ",Q2=" + bean.getQueryParam2()); + } + + @Override + public MyResourceWithBeanParamIfc getSubResource() { + return new MyResourceWithBeanParam(); + } +} diff --git a/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/MyResourceWithBeanParamIfc.java b/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/MyResourceWithBeanParamIfc.java new file mode 100644 index 0000000000..f57c85d1b9 --- /dev/null +++ b/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/MyResourceWithBeanParamIfc.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.client.proxy; + +import jakarta.ws.rs.BeanParam; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; + +/** + * @author Richard Obersheimer + */ +@Path("mybeanresource") +public interface MyResourceWithBeanParamIfc { + + @GET + @Path("getQuery") + @Produces("text/plain") + public String echoQuery(@BeanParam MyGetBeanParam bean); + + @GET + @Path("getHeader") + @Produces("text/plain") + public String echoHeader(@BeanParam MyGetBeanParam bean); + + @GET + @Path("getPath/{pathParam}") + @Produces("text/plain") + public String echoPath(@BeanParam MyGetBeanParam bean); + + @GET + @Path("getCookie") + @Produces("text/plain") + public String echoCookie(@BeanParam MyGetBeanParam bean); + + @GET + @Path("getMatrix") + @Produces("text/plain") + public String echoMatrix(@BeanParam MyGetBeanParam bean); + + @GET + @Path("getSubBean") + @Produces("text/plain") + public String echoSubBean(@BeanParam MyGetBeanParam bean); + + @POST + @Consumes("application/x-www-form-urlencoded") + @Path("all/{pathParam}") + @Produces("text/plain") + public String echo(@BeanParam MyBeanParam bean); + + @Path("subresource") + MyResourceWithBeanParamIfc getSubResource(); + +} diff --git a/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/MySubBeanParam.java b/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/MySubBeanParam.java new file mode 100644 index 0000000000..4cd792d364 --- /dev/null +++ b/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/MySubBeanParam.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.client.proxy; + +import jakarta.ws.rs.QueryParam; + +import java.util.List; + +/** + * @author Richard Obersheimer + */ +public class MySubBeanParam { + + public List getSubQueryParam() { + return subQueryParam; + } + + public void setSubQueryParam(List subQueryParam) { + this.subQueryParam = subQueryParam; + } + + public MySubBeanParam(List subQueryParam) { + this.subQueryParam = subQueryParam; + } + + public MySubBeanParam() {} + + @QueryParam("subQueryParam") + List subQueryParam; +} diff --git a/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/RequestParametersTest.java b/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/RequestParametersTest.java new file mode 100644 index 0000000000..aee925ec9c --- /dev/null +++ b/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/RequestParametersTest.java @@ -0,0 +1,293 @@ +/* + * Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.client.proxy; + +import jakarta.ws.rs.BeanParam; +import jakarta.ws.rs.CookieParam; +import jakarta.ws.rs.FormParam; +import jakarta.ws.rs.HeaderParam; +import jakarta.ws.rs.MatrixParam; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.Cookie; +import jakarta.ws.rs.core.Form; +import jakarta.ws.rs.core.MultivaluedHashMap; +import jakarta.ws.rs.core.MultivaluedMap; +import org.junit.Test; + +import java.beans.IntrospectionException; +import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationTargetException; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.assertEquals; + +/** + * @author Richard Obersheimer + */ +public class RequestParametersTest { + + private static final MultivaluedMap EMPTY_HEADERS = new MultivaluedHashMap<>(); + private static final Form EMPTY_FORM = new Form(); + private static final String baseURL = "http://example.com"; + + @QueryParam("queryParam") + String queryParam; + + @QueryParam("queryParams") + List queryParams; + + @PathParam("pathParam") + String pathParam; + + @HeaderParam("headerParam") + String headerParam; + + @MatrixParam("matrixParam") + List matrixParam; + + @CookieParam("cookieParam") + Cookie cookieParam; + + @BeanParam + MySubBeanParam subBeanParam; + + @FormParam("formParam") + String formParam; + + @FormParam("formParams") + List formParams; + + + private WebTarget getExampleTarget() { + Client client = ClientBuilder.newClient(); + return client.target(baseURL); + } + + private WebTarget getExampleTargetWithPathParam() { + Client client = ClientBuilder.newClient(); + return client.target(baseURL + "/{pathParam}"); + } + + private RequestParameters getEmptyRequestParameters(WebTarget webTarget) { + return new RequestParameters(webTarget, + EMPTY_HEADERS, Collections.emptyList(), EMPTY_FORM); + } + + @Test + public void testAddQueryParameter() throws NoSuchFieldException, IntrospectionException, + InvocationTargetException, IllegalAccessException { + + WebTarget webTarget = getExampleTarget(); + RequestParameters requestParameters = getEmptyRequestParameters(webTarget); + + Annotation ann = this.getClass().getDeclaredField("queryParam").getAnnotations()[0]; + Map, Annotation> anns = new HashMap<>(); + anns.put(QueryParam.class, ann); + + requestParameters.addParameter("testQuery", anns); + String uri = requestParameters.getNewTarget().getUriBuilder().build().toString(); + + assertEquals(baseURL + "/?queryParam=testQuery", uri); + } + + @Test + public void testAddListOfQueryParameters() throws IntrospectionException, InvocationTargetException, + IllegalAccessException, NoSuchFieldException { + + WebTarget webTarget = getExampleTarget(); + RequestParameters requestParameters = getEmptyRequestParameters(webTarget); + + Annotation ann = this.getClass().getDeclaredField("queryParams").getAnnotations()[0]; + Map, Annotation> anns = new HashMap<>(); + anns.put(QueryParam.class, ann); + List subQueryParam = Arrays.asList("subQuery1", "subQuery2"); + + requestParameters.addParameter(subQueryParam, anns); + + String uri = requestParameters.getNewTarget().getUriBuilder().build().toString(); + + assertEquals(baseURL + "/?queryParams=subQuery1&queryParams=subQuery2", uri); + } + + @Test + public void testAddPathParameter() throws NoSuchFieldException, IntrospectionException, + InvocationTargetException, IllegalAccessException { + + WebTarget webTarget = getExampleTargetWithPathParam(); + RequestParameters requestParameters = getEmptyRequestParameters(webTarget); + + Annotation ann = this.getClass().getDeclaredField("pathParam").getAnnotations()[0]; + Map, Annotation> anns = new HashMap<>(); + anns.put(PathParam.class, ann); + + requestParameters.addParameter("testPath", anns); + String uri = requestParameters.getNewTarget().getUriBuilder().build().toString(); + + assertEquals(baseURL + "/testPath", uri); + } + + @Test + public void testAddHeaderParameter() throws NoSuchFieldException, IntrospectionException, + InvocationTargetException, IllegalAccessException { + + WebTarget webTarget = getExampleTarget(); + RequestParameters requestParameters = getEmptyRequestParameters(webTarget); + + Annotation ann = this.getClass().getDeclaredField("headerParam").getAnnotations()[0]; + Map, Annotation> anns = new HashMap<>(); + anns.put(HeaderParam.class, ann); + + requestParameters.addParameter("testHeader", anns); + MultivaluedHashMap headers = requestParameters.getHeaders(); + LinkedList headerList = new LinkedList<>(); + headerList.add("testHeader"); + + assertEquals(headerList, headers.get("headerParam")); + } + + @Test + public void testAddMatrixParameter() throws NoSuchFieldException, IntrospectionException, + InvocationTargetException, IllegalAccessException { + + WebTarget webTarget = getExampleTarget(); + RequestParameters requestParameters = getEmptyRequestParameters(webTarget); + + Annotation ann = this.getClass().getDeclaredField("matrixParam").getAnnotations()[0]; + Map, Annotation> anns = new HashMap<>(); + anns.put(MatrixParam.class, ann); + + requestParameters.addParameter("testMatrix", anns); + String uri = requestParameters.getNewTarget().getUriBuilder().build().toString(); + + assertEquals(baseURL + "/;matrixParam=testMatrix", uri); + } + + @Test + public void testAddCookieParameter() throws NoSuchFieldException, IntrospectionException, + InvocationTargetException, IllegalAccessException { + + WebTarget webTarget = getExampleTarget(); + RequestParameters requestParameters = getEmptyRequestParameters(webTarget); + + Annotation ann = this.getClass().getDeclaredField("cookieParam").getAnnotations()[0]; + Map, Annotation> anns = new HashMap<>(); + anns.put(CookieParam.class, ann); + + Cookie cookie = new Cookie("cookieParamName", "testCookie"); + requestParameters.addParameter(cookie, anns); + List cookies = requestParameters.getCookies(); + + assertEquals(new Cookie("cookieParam", "testCookie"), cookies.get(0)); + } + + @Test + public void testAddFormParameter() throws NoSuchFieldException, IntrospectionException, + InvocationTargetException, IllegalAccessException { + + WebTarget webTarget = getExampleTarget(); + RequestParameters requestParameters = getEmptyRequestParameters(webTarget); + + Annotation ann = this.getClass().getDeclaredField("formParam").getAnnotations()[0]; + Map, Annotation> anns = new HashMap<>(); + anns.put(FormParam.class, ann); + + requestParameters.addParameter("testForm", anns); + Form form = requestParameters.getForm(); + LinkedList formList = new LinkedList<>(); + formList.add("testForm"); + + assertEquals(formList, form.asMap().get("formParam")); + } + + @Test + public void testListOfFormParameters() throws NoSuchFieldException, IntrospectionException, + InvocationTargetException, IllegalAccessException { + + WebTarget webTarget = getExampleTarget(); + RequestParameters requestParameters = getEmptyRequestParameters(webTarget); + + Annotation ann = this.getClass().getDeclaredField("formParams").getAnnotations()[0]; + Map, Annotation> anns = new HashMap<>(); + anns.put(FormParam.class, ann); + + List testFormList = Arrays.asList("formParam1", "formParam2"); + requestParameters.addParameter(testFormList, anns); + Form form = requestParameters.getForm(); + + assertEquals(testFormList, form.asMap().get("formParams")); + + } + + // any nonempty annotation will do + private Map, Annotation> getNonEmptyBeanParamAnnotation() throws NoSuchFieldException { + Annotation ann = this.getClass().getDeclaredField("queryParam").getAnnotations()[0]; + Map, Annotation> anns = new HashMap<>(); + anns.put(BeanParam.class, ann); + return anns; + } + + @Test + public void testAddBeanParameter() throws NoSuchFieldException, IntrospectionException, + InvocationTargetException, IllegalAccessException { + + WebTarget webTarget = getExampleTarget(); + RequestParameters requestParameters = getEmptyRequestParameters(webTarget); + + MyBeanParam beanParam = new MyBeanParam(); + beanParam.setQueryParam2("testQuery"); + + Map, Annotation> anns = getNonEmptyBeanParamAnnotation(); + + requestParameters.addParameter(beanParam, anns); + String uri = requestParameters.getNewTarget().getUriBuilder().build().toString(); + + assertEquals(baseURL + "/?queryParam2=testQuery", uri); + } + + @Test + public void testAddListOfBeanParameters() throws NoSuchFieldException, IntrospectionException, + InvocationTargetException, IllegalAccessException { + + WebTarget webTarget = getExampleTarget(); + RequestParameters requestParameters = getEmptyRequestParameters(webTarget); + + MyBeanParam beanParam1 = new MyBeanParam(); + beanParam1.setQueryParam("testQuery"); + MyBeanParam beanParam2 = new MyBeanParam(); + beanParam2.setCookieParam(new Cookie("cookie", "cookieValue")); + + Map, Annotation> anns = getNonEmptyBeanParamAnnotation(); + + List beanParams = Arrays.asList(beanParam1, beanParam2); + requestParameters.addParameter(beanParams, anns); + + String uri = requestParameters.getNewTarget().getUriBuilder().build().toString(); + List cookies = requestParameters.getCookies(); + + assertEquals(baseURL + "/?queryParam=testQuery", uri); + assertEquals(new Cookie("cookieParam", "cookieValue"), cookies.get(0)); + } +} \ No newline at end of file diff --git a/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/WebResourceFactoryBeanParamTest.java b/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/WebResourceFactoryBeanParamTest.java new file mode 100644 index 0000000000..27b1fd872d --- /dev/null +++ b/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/WebResourceFactoryBeanParamTest.java @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.client.proxy; + +import jakarta.ws.rs.core.Cookie; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.glassfish.jersey.test.TestProperties; +import org.junit.Test; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.assertEquals; + +/** + * @author Richard Obersheimer + */ +public class WebResourceFactoryBeanParamTest extends JerseyTest { + + private MyResourceWithBeanParamIfc resourceWithBeanParam; + + @Override + protected ResourceConfig configure() { + enable(TestProperties.LOG_TRAFFIC); + return new ResourceConfig(MyResourceWithBeanParam.class); + } + + @Override + public void setUp() throws Exception { + super.setUp(); + resourceWithBeanParam = WebResourceFactory.newResource(MyResourceWithBeanParamIfc.class, target()); + } + + @Test + public void testBeanParamQuery() { + MyGetBeanParam myGetBeanParam = new MyGetBeanParam(); + myGetBeanParam.setQueryParam("query"); + + String response = resourceWithBeanParam.echoQuery(myGetBeanParam); + + assertEquals("query", response); + } + + @Test + public void testBeanParamHeader() { + MyGetBeanParam myGetBeanParam = new MyGetBeanParam(); + myGetBeanParam.setHeaderParam("header"); + + String response = resourceWithBeanParam.echoHeader(myGetBeanParam); + + assertEquals("header", response); + } + + @Test + public void testBeanParamPath() { + MyGetBeanParam myGetBeanParam = new MyGetBeanParam(); + myGetBeanParam.setPathParam("path"); + + String response = resourceWithBeanParam.echoPath(myGetBeanParam); + + assertEquals("path", response); + } + + @Test + public void testBeanParamCookie() { + MyGetBeanParam myGetBeanParam = new MyGetBeanParam(); + Cookie cookie = new Cookie("cName", "cValue"); + myGetBeanParam.setCookieParam(cookie); + + String response = resourceWithBeanParam.echoCookie(myGetBeanParam); + + assertEquals("cValue", response); + } + + @Test + public void testBeanParamMatrix() { + MyGetBeanParam myGetBeanParam = new MyGetBeanParam(); + List matrixParam = Arrays.asList("1", "2", "3"); + myGetBeanParam.setMatrixParam(matrixParam); + + String response = resourceWithBeanParam.echoMatrix(myGetBeanParam); + + assertEquals(matrixParam.toString(), response); + } + + @Test + public void testBeanParamSubBean() { + MyGetBeanParam myGetBeanParam = new MyGetBeanParam(); + List subQueryParam = Arrays.asList("1", "2", "3"); + MySubBeanParam subBeanParam = new MySubBeanParam(subQueryParam); + myGetBeanParam.setSubBeanParam(subBeanParam); + + String response = resourceWithBeanParam.echoSubBean(myGetBeanParam); + + assertEquals(subQueryParam.toString(), response); + } + + @Test + public void testBeanParam() { + List matrixParam = Arrays.asList("1", "2", "3"); + Cookie cookieParam = new Cookie("cookie1", "value1"); + List subQueryParam = Arrays.asList("subQuery1", "subQuery2"); + MySubBeanParam subBeanParam = new MySubBeanParam(subQueryParam); + MyBeanParam myBeanParam = new MyBeanParam("header", "path", "query", + "form1", "form2", matrixParam, cookieParam, subBeanParam); + myBeanParam.setQueryParam2("q2"); + + String response = resourceWithBeanParam.echo(myBeanParam); + + assertEquals("HEADER=header,PATH=path,FORM=form1,form2,QUERY=query,MATRIX=3,COOKIE=value1,SUB=2" + + ",Q2=q2", response); + } + + @Test + public void testSubResource() { + MyGetBeanParam myGetBeanParam = new MyGetBeanParam(); + myGetBeanParam.setQueryParam("query"); + + String response = resourceWithBeanParam.getSubResource().echoQuery(myGetBeanParam); + + assertEquals("query", response); + } +} diff --git a/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/WebResourceFactoryTest.java b/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/WebResourceFactoryTest.java index 6ca975c562..68e81c7cc2 100644 --- a/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/WebResourceFactoryTest.java +++ b/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/WebResourceFactoryTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2021 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at