Skip to content
This repository was archived by the owner on May 28, 2018. It is now read-only.

JERSEY-1653: Add support for @BeanParam in jersey-proxy-client #235

Open
wants to merge 1 commit into
base: 2.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,14 @@

package org.glassfish.jersey.client.proxy;

import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Proxy;
Expand All @@ -56,6 +61,7 @@
import java.util.List;
import java.util.Map;

import javax.ws.rs.BeanParam;
import javax.ws.rs.Consumes;
import javax.ws.rs.CookieParam;
import javax.ws.rs.DefaultValue;
Expand Down Expand Up @@ -100,7 +106,7 @@ public final class WebResourceFactory implements InvocationHandler {
private static final MultivaluedMap<String, Object> EMPTY_HEADERS = new MultivaluedHashMap<>();
private static final Form EMPTY_FORM = new Form();
private static final List<Class> PARAM_ANNOTATION_CLASSES = Arrays.<Class>asList(PathParam.class, QueryParam.class,
HeaderParam.class, CookieParam.class, MatrixParam.class, FormParam.class);
HeaderParam.class, CookieParam.class, MatrixParam.class, FormParam.class, BeanParam.class);

/**
* Creates a new client-side representation of a resource described by
Expand Down Expand Up @@ -204,79 +210,14 @@ public Object invoke(final Object proxy, final Method method, final Object[] arg
Object entity = null;
Type entityType = null;
for (int i = 0; i < paramAnns.length; i++) {
final Map<Class, Annotation> anns = new HashMap<>();
for (final Annotation ann : paramAnns[i]) {
anns.put(ann.annotationType(), ann);
}
Annotation ann;
final Map<Class, Annotation> anns = getAnnotationsMap(paramAnns[i]);
Object value = args[i];
if (!hasAnyParamAnnotation(anns)) {
entityType = method.getGenericParameterTypes()[i];
entity = value;
} else {
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());
}
}
}
newTarget = setupParameter(method.getParameterTypes()[i], anns, headers, cookies,
form, newTarget, value);
}
}

Expand Down Expand Up @@ -378,4 +319,133 @@ private static String getHttpMethodName(final AnnotatedElement ae) {
final HttpMethod a = ae.getAnnotation(HttpMethod.class);
return a == null ? null : a.value();
}

private WebTarget setupParameter(final Class<?> paramType,
final Map<Class, Annotation> anns,
final MultivaluedHashMap<String, Object> headers,
final LinkedList<Cookie> cookies,
final Form form,
WebTarget newTarget,
Object value)
throws IllegalAccessException, InvocationTargetException, IntrospectionException {
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());
}
} else if ((ann = anns.get((BeanParam.class))) != null) {
newTarget = extractParamsFromBeanParamClass(paramType, headers, cookies, form, newTarget, value);
}
}
return newTarget;
}

private WebTarget extractParamsFromBeanParamClass(final Class<?> beanParamType,
final MultivaluedHashMap<String, Object> headers,
final LinkedList<Cookie> cookies,
final Form form,
WebTarget newTarget,
final Object bean)
throws IllegalAccessException, InvocationTargetException, IntrospectionException {
Field fields[] = AccessController.doPrivileged(ReflectionHelper.getAllFieldsPA(beanParamType));
for (Field field : fields) {
final Map<Class, Annotation> anns =
getAnnotationsMap(field.getAnnotations());

if (hasAnyParamAnnotation(anns)) {
if (!field.isAccessible()) {
field.setAccessible(true);
}
newTarget = setupParameter(field.getType(), anns, headers, cookies, form, newTarget,
field.get(bean));
}
}

PropertyDescriptor propertyDescriptors[] = Introspector.getBeanInfo(beanParamType,
Introspector.USE_ALL_BEANINFO).getPropertyDescriptors();
for (PropertyDescriptor propertyDesc : propertyDescriptors) {
Method beanSetterMethod = propertyDesc.getWriteMethod();
if (beanSetterMethod != null) {
final Map<Class, Annotation> anns =
getAnnotationsMap(beanSetterMethod.getAnnotations());

if (hasAnyParamAnnotation(anns)) {
Method beanGetterMethod = propertyDesc.getReadMethod();
if (!beanGetterMethod.isAccessible()) {
beanGetterMethod.setAccessible(true);
}
newTarget = setupParameter(beanGetterMethod.getReturnType(), anns, headers,
cookies, form, newTarget, beanGetterMethod.invoke(bean));
}
}
}

return newTarget;
}

private Map<Class, Annotation> getAnnotationsMap(Annotation[] annotations) {
final Map<Class, Annotation> anns = new HashMap<>();
for (final Annotation ann : annotations) {
anns.put(ann.annotationType(), ann);
}
return anns;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package org.glassfish.jersey.client.proxy;

import javax.ws.rs.CookieParam;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.QueryParam;

/**
* @author Lucas Sa
*/
public class MyBeanParam {
@QueryParam("name") private String queryName;

@CookieParam("name") private String cookieName;

@HeaderParam("name") private String headerName;

private String setterQueryName;

private String setterCookieName;

private String setterHeaderName;

public String getQueryName() {
return queryName;
}

public void setQueryName(String queryName) {
this.queryName = queryName;
}

public String getCookieName() {
return cookieName;
}

public void setCookieName(String cookieName) {
this.cookieName = cookieName;
}

public String getHeaderName() {
return headerName;
}

public void setHeaderName(String headerName) {
this.headerName = headerName;
}

public String getSetterQueryName() {
return setterQueryName;
}

@QueryParam("setterName")
public void setSetterQueryName(String setterQueryName) {
this.setterQueryName = setterQueryName;
}

public String getSetterCookieName() {
return setterCookieName;
}

@CookieParam("setterName")
public void setSetterCookieName(String setterCookieName) {
this.setterCookieName = setterCookieName;
}

public String getSetterHeaderName() {
return setterHeaderName;
}

@HeaderParam("setterName")
public void setSetterHeaderName(String setterHeaderName) {
this.setterHeaderName = setterHeaderName;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
*/
package org.glassfish.jersey.client.proxy;

import javax.ws.rs.BeanParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
Expand Down Expand Up @@ -91,6 +92,12 @@ public String getByNameMatrix(String name) {
return name;
}

@Override
public String getByBean(@BeanParam MyBeanParam bean) {
return bean.getQueryName() + bean.getCookieName() + bean.getHeaderName()
+ bean.getSetterQueryName() + bean.getSetterCookieName() + bean.getSetterHeaderName();
}

@Override
public String postByNameFormParam(String name) {
return name;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import java.util.Set;
import java.util.SortedSet;

import javax.ws.rs.BeanParam;
import javax.ws.rs.Consumes;
import javax.ws.rs.CookieParam;
import javax.ws.rs.FormParam;
Expand Down Expand Up @@ -101,6 +102,11 @@ public interface MyResourceIfc {
@Produces(MediaType.TEXT_PLAIN)
String getByNameMatrix(@MatrixParam("matrix-name") String name);

@Path("bean")
@GET
@Produces(MediaType.TEXT_PLAIN)
String getByBean(@BeanParam MyBeanParam bean);

@Path("form")
@POST
@Produces(MediaType.TEXT_PLAIN)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,19 @@ public void testMatrixParam() {
assertEquals("jiri", resource.getByNameMatrix("jiri"));
}

@Test
public void testBeanParam() {
MyBeanParam bean = new MyBeanParam();
bean.setQueryName("Query");
bean.setCookieName("Cookie");
bean.setHeaderName("Header");
bean.setSetterQueryName("SetterQuery");
bean.setSetterCookieName("SetterCookie");
bean.setSetterHeaderName("SetterHeader");
assertEquals("QueryCookieHeaderSetterQuerySetterCookieSetterHeader",
resource.getByBean(bean));
}

@Test
public void testSubResource() {
assertEquals("Got it!", resource.getSubResource().getMyBean().name);
Expand Down