Skip to content

Commit b9653d1

Browse files
robersheimersenivam
authored andcommitted
Issue #3493 - Add BeanParam support to WebResourceFactory (#4919)
* Issue #3493 - Add BeanParam support to WebResourceFactory Allows the proxy client holding a resource interface to supply a bean of the corresponding type for the method fields annotated with @BeanParam, instead of having to supply the associated parameters individually. This also works if BeanParams themselves contain fields annotated with @BeanParam.
1 parent ad2d70f commit b9653d1

File tree

10 files changed

+1037
-95
lines changed

10 files changed

+1037
-95
lines changed
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
/*
2+
* Copyright (c) 2012, 2021 Oracle and/or its affiliates. All rights reserved.
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License v. 2.0, which is available at
6+
* http://www.eclipse.org/legal/epl-2.0.
7+
*
8+
* This Source Code may also be made available under the following Secondary
9+
* Licenses when the conditions for such availability set forth in the
10+
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
11+
* version 2 with the GNU Classpath Exception, which is available at
12+
* https://www.gnu.org/software/classpath/license.html.
13+
*
14+
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15+
*/
16+
17+
package org.glassfish.jersey.client.proxy;
18+
19+
import jakarta.ws.rs.BeanParam;
20+
import jakarta.ws.rs.CookieParam;
21+
import jakarta.ws.rs.FormParam;
22+
import jakarta.ws.rs.HeaderParam;
23+
import jakarta.ws.rs.MatrixParam;
24+
import jakarta.ws.rs.PathParam;
25+
import jakarta.ws.rs.QueryParam;
26+
import jakarta.ws.rs.client.WebTarget;
27+
import jakarta.ws.rs.core.Cookie;
28+
import jakarta.ws.rs.core.Form;
29+
import jakarta.ws.rs.core.MultivaluedHashMap;
30+
import jakarta.ws.rs.core.MultivaluedMap;
31+
32+
import java.beans.IntrospectionException;
33+
import java.beans.Introspector;
34+
import java.beans.PropertyDescriptor;
35+
import java.lang.annotation.Annotation;
36+
import java.lang.reflect.Field;
37+
import java.lang.reflect.InvocationTargetException;
38+
import java.util.ArrayList;
39+
import java.util.Arrays;
40+
import java.util.Collection;
41+
import java.util.HashMap;
42+
import java.util.LinkedList;
43+
import java.util.List;
44+
import java.util.Map;
45+
46+
/**
47+
* Collector to retrieve parameters for setting up the HTTP request sent in the invoke method of WebResourceFactory
48+
* The addParameter method takes a single annotated method parameter or annotated field or property of a BeanParam
49+
* and adds the information to the web target, headers, cookie list or form.
50+
*/
51+
class RequestParameters {
52+
53+
private WebTarget newTarget;
54+
private final MultivaluedHashMap<String, Object> headers;
55+
private final LinkedList<Cookie> cookies;
56+
private final Form form;
57+
58+
private static final List<Class<?>> PARAM_ANNOTATION_CLASSES = Arrays.asList(PathParam.class, QueryParam.class,
59+
HeaderParam.class, CookieParam.class, MatrixParam.class, FormParam.class, BeanParam.class);
60+
61+
RequestParameters(final WebTarget newTarget, final MultivaluedMap<String, Object> headers,
62+
final List<Cookie> cookies, final Form form) {
63+
64+
this.headers = new MultivaluedHashMap<>(headers);
65+
this.cookies = new LinkedList<>(cookies);
66+
this.form = new Form();
67+
this.form.asMap().putAll(form.asMap());
68+
69+
this.newTarget = newTarget;
70+
}
71+
72+
void addParameter(final Object value, final Map<Class<?>, Annotation> anns)
73+
throws IntrospectionException, InvocationTargetException, IllegalAccessException {
74+
75+
Annotation ann;
76+
if ((ann = anns.get(PathParam.class)) != null) {
77+
newTarget = newTarget.resolveTemplate(((PathParam) ann).value(), value);
78+
} else if ((ann = anns.get((QueryParam.class))) != null) {
79+
if (value instanceof Collection) {
80+
newTarget = newTarget.queryParam(((QueryParam) ann).value(), convert((Collection<?>) value));
81+
} else {
82+
newTarget = newTarget.queryParam(((QueryParam) ann).value(), value);
83+
}
84+
} else if ((ann = anns.get((HeaderParam.class))) != null) {
85+
if (value instanceof Collection) {
86+
headers.addAll(((HeaderParam) ann).value(), convert((Collection<?>) value));
87+
} else {
88+
headers.addAll(((HeaderParam) ann).value(), value);
89+
}
90+
91+
} else if ((ann = anns.get((CookieParam.class))) != null) {
92+
final String name = ((CookieParam) ann).value();
93+
Cookie c;
94+
if (value instanceof Collection) {
95+
for (final Object v : ((Collection<?>) value)) {
96+
if (!(v instanceof Cookie)) {
97+
c = new Cookie(name, v.toString());
98+
} else {
99+
c = (Cookie) v;
100+
if (!name.equals(((Cookie) v).getName())) {
101+
// is this the right thing to do? or should I fail? or ignore the difference?
102+
c = new Cookie(name, c.getValue(), c.getPath(), c.getDomain(), c.getVersion());
103+
}
104+
}
105+
cookies.add(c);
106+
}
107+
} else {
108+
if (!(value instanceof Cookie)) {
109+
cookies.add(new Cookie(name, value.toString()));
110+
} else {
111+
c = (Cookie) value;
112+
if (!name.equals(((Cookie) value).getName())) {
113+
// is this the right thing to do? or should I fail? or ignore the difference?
114+
cookies.add(new Cookie(name, c.getValue(), c.getPath(), c.getDomain(), c.getVersion()));
115+
}
116+
}
117+
}
118+
} else if ((ann = anns.get((MatrixParam.class))) != null) {
119+
if (value instanceof Collection) {
120+
newTarget = newTarget.matrixParam(((MatrixParam) ann).value(), convert((Collection<?>) value));
121+
} else {
122+
newTarget = newTarget.matrixParam(((MatrixParam) ann).value(), value);
123+
}
124+
} else if ((ann = anns.get((FormParam.class))) != null) {
125+
if (value instanceof Collection) {
126+
for (final Object v : ((Collection<?>) value)) {
127+
form.param(((FormParam) ann).value(), v.toString());
128+
}
129+
} else {
130+
form.param(((FormParam) ann).value(), value.toString());
131+
}
132+
} else if ((anns.get((BeanParam.class))) != null) {
133+
if (value instanceof Collection) {
134+
for (final Object v : ((Collection<?>) value)) {
135+
addBeanParameter(v);
136+
}
137+
} else {
138+
addBeanParameter(value);
139+
}
140+
}
141+
}
142+
143+
private void addBeanParameter(final Object beanParam)
144+
throws IllegalAccessException, IntrospectionException, InvocationTargetException {
145+
Class<?> beanClass = beanParam.getClass();
146+
List<Field> fields = new ArrayList<>();
147+
getAllFields(fields, beanClass);
148+
149+
for (final Field field : fields) {
150+
Object value = null;
151+
final Map<Class<?>, Annotation> anns = new HashMap<>();
152+
153+
// get field annotations
154+
for (final Annotation ann : field.getAnnotations()) {
155+
anns.put(ann.annotationType(), ann);
156+
}
157+
158+
if (hasAnyParamAnnotation(anns)) {
159+
value = field.get(beanParam);
160+
} else {
161+
// get getter annotations if there are no field annotations
162+
for (final PropertyDescriptor pd : Introspector.getBeanInfo(beanClass).getPropertyDescriptors()) {
163+
if (pd.getName().equals(field.getName()) && pd.getReadMethod() != null) {
164+
for (final Annotation ann : pd.getReadMethod().getAnnotations()) {
165+
anns.put(ann.annotationType(), ann);
166+
}
167+
if (hasAnyParamAnnotation(anns)) {
168+
value = pd.getReadMethod().invoke(beanParam);
169+
}
170+
}
171+
}
172+
}
173+
174+
if (value != null) {
175+
addParameter(value, anns);
176+
}
177+
}
178+
}
179+
180+
private List<Field> getAllFields(List<Field> fields, Class<?> type) {
181+
fields.addAll(Arrays.asList(type.getDeclaredFields()));
182+
183+
if (type.getSuperclass() != null) {
184+
getAllFields(fields, type.getSuperclass());
185+
}
186+
187+
return fields;
188+
}
189+
190+
private Object[] convert(final Collection<?> value) {
191+
return value.toArray();
192+
}
193+
194+
public static boolean hasAnyParamAnnotation(final Map<Class<?>, Annotation> anns) {
195+
for (final Class<?> paramAnnotationClass : PARAM_ANNOTATION_CLASSES) {
196+
if (anns.containsKey(paramAnnotationClass)) {
197+
return true;
198+
}
199+
}
200+
return false;
201+
}
202+
203+
WebTarget getNewTarget() {
204+
return newTarget;
205+
}
206+
207+
MultivaluedHashMap<String, Object> getHeaders() {
208+
return headers;
209+
}
210+
211+
LinkedList<Cookie> getCookies() {
212+
return cookies;
213+
}
214+
215+
Form getForm() {
216+
return form;
217+
}
218+
219+
}

0 commit comments

Comments
 (0)