|
13 | 13 | */ |
14 | 14 | package feign.jaxrs; |
15 | 15 |
|
16 | | -import static feign.Util.checkState; |
17 | | -import static feign.Util.emptyToNull; |
18 | | -import static feign.Util.removeValues; |
| 16 | +import feign.jaxrs3.JAXRS3Contract; |
19 | 17 |
|
20 | | -import feign.DeclarativeContract; |
21 | | -import feign.MethodMetadata; |
22 | | -import feign.Request; |
23 | | -import jakarta.ws.rs.*; |
24 | | -import jakarta.ws.rs.container.Suspended; |
25 | | -import jakarta.ws.rs.core.Context; |
26 | | -import java.lang.annotation.Annotation; |
27 | | -import java.lang.reflect.Field; |
28 | | -import java.lang.reflect.Method; |
29 | | -import java.util.Collections; |
30 | | - |
31 | | -public class JakartaContract extends DeclarativeContract { |
32 | | - |
33 | | - static final String ACCEPT = "Accept"; |
34 | | - static final String CONTENT_TYPE = "Content-Type"; |
35 | | - |
36 | | - // Protected so unittest can call us |
37 | | - // XXX: Should parseAndValidateMetadata(Class, Method) be public instead? The old deprecated |
38 | | - // parseAndValidateMetadata(Method) was public.. |
39 | | - @Override |
40 | | - protected MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method) { |
41 | | - return super.parseAndValidateMetadata(targetType, method); |
42 | | - } |
43 | | - |
44 | | - public JakartaContract() { |
45 | | - super.registerClassAnnotation( |
46 | | - Path.class, |
47 | | - (path, data) -> { |
48 | | - if (path != null && !path.value().isEmpty()) { |
49 | | - String pathValue = path.value(); |
50 | | - if (!pathValue.startsWith("/")) { |
51 | | - pathValue = "/" + pathValue; |
52 | | - } |
53 | | - if (pathValue.endsWith("/")) { |
54 | | - // Strip off any trailing slashes, since the template has already had slashes |
55 | | - // appropriately |
56 | | - // added |
57 | | - pathValue = pathValue.substring(0, pathValue.length() - 1); |
58 | | - } |
59 | | - // jax-rs allows whitespace around the param name, as well as an optional regex. The |
60 | | - // contract |
61 | | - // should |
62 | | - // strip these out appropriately. |
63 | | - pathValue = pathValue.replaceAll("\\{\\s*(.+?)\\s*(:.+?)?\\}", "\\{$1\\}"); |
64 | | - data.template().uri(pathValue); |
65 | | - } |
66 | | - }); |
67 | | - super.registerClassAnnotation(Consumes.class, this::handleConsumesAnnotation); |
68 | | - super.registerClassAnnotation(Produces.class, this::handleProducesAnnotation); |
69 | | - |
70 | | - registerMethodAnnotation( |
71 | | - methodAnnotation -> { |
72 | | - final Class<? extends Annotation> annotationType = methodAnnotation.annotationType(); |
73 | | - final HttpMethod http = annotationType.getAnnotation(HttpMethod.class); |
74 | | - return http != null; |
75 | | - }, |
76 | | - (methodAnnotation, data) -> { |
77 | | - final Class<? extends Annotation> annotationType = methodAnnotation.annotationType(); |
78 | | - final HttpMethod http = annotationType.getAnnotation(HttpMethod.class); |
79 | | - checkState( |
80 | | - data.template().method() == null, |
81 | | - "Method %s contains multiple HTTP methods. Found: %s and %s", |
82 | | - data.configKey(), |
83 | | - data.template().method(), |
84 | | - http.value()); |
85 | | - data.template().method(Request.HttpMethod.valueOf(http.value())); |
86 | | - }); |
87 | | - |
88 | | - super.registerMethodAnnotation( |
89 | | - Path.class, |
90 | | - (path, data) -> { |
91 | | - final String pathValue = emptyToNull(path.value()); |
92 | | - if (pathValue == null) { |
93 | | - return; |
94 | | - } |
95 | | - String methodAnnotationValue = path.value(); |
96 | | - if (!methodAnnotationValue.startsWith("/") && !data.template().url().endsWith("/")) { |
97 | | - methodAnnotationValue = "/" + methodAnnotationValue; |
98 | | - } |
99 | | - // jax-rs allows whitespace around the param name, as well as an optional regex. The |
100 | | - // contract |
101 | | - // should |
102 | | - // strip these out appropriately. |
103 | | - methodAnnotationValue = |
104 | | - methodAnnotationValue.replaceAll("\\{\\s*(.+?)\\s*(:.+?)?\\}", "\\{$1\\}"); |
105 | | - data.template().uri(methodAnnotationValue, true); |
106 | | - }); |
107 | | - super.registerMethodAnnotation(Consumes.class, this::handleConsumesAnnotation); |
108 | | - super.registerMethodAnnotation(Produces.class, this::handleProducesAnnotation); |
109 | | - |
110 | | - // parameter with unsupported jax-rs annotations should not be passed as body params. |
111 | | - // this will prevent interfaces from becoming unusable entirely due to single (unsupported) |
112 | | - // endpoints. |
113 | | - // https://github.com/OpenFeign/feign/issues/669 |
114 | | - super.registerParameterAnnotation(Suspended.class, (ann, data, i) -> data.ignoreParamater(i)); |
115 | | - super.registerParameterAnnotation(Context.class, (ann, data, i) -> data.ignoreParamater(i)); |
116 | | - // trying to minimize the diff |
117 | | - registerParamAnnotations(); |
118 | | - } |
119 | | - |
120 | | - private void handleProducesAnnotation(Produces produces, MethodMetadata data) { |
121 | | - final String[] serverProduces = |
122 | | - removeValues(produces.value(), mediaType -> emptyToNull(mediaType) == null, String.class); |
123 | | - checkState(serverProduces.length > 0, "Produces.value() was empty on %s", data.configKey()); |
124 | | - data.template().header(ACCEPT, Collections.emptyList()); // remove any previous produces |
125 | | - data.template().header(ACCEPT, serverProduces); |
126 | | - } |
127 | | - |
128 | | - private void handleConsumesAnnotation(Consumes consumes, MethodMetadata data) { |
129 | | - final String[] serverConsumes = |
130 | | - removeValues(consumes.value(), mediaType -> emptyToNull(mediaType) == null, String.class); |
131 | | - checkState(serverConsumes.length > 0, "Consumes.value() was empty on %s", data.configKey()); |
132 | | - data.template().header(CONTENT_TYPE, serverConsumes); |
133 | | - } |
134 | | - |
135 | | - protected void registerParamAnnotations() { |
136 | | - |
137 | | - registerParameterAnnotation( |
138 | | - PathParam.class, |
139 | | - (param, data, paramIndex) -> { |
140 | | - final String name = param.value(); |
141 | | - checkState( |
142 | | - emptyToNull(name) != null, "PathParam.value() was empty on parameter %s", paramIndex); |
143 | | - nameParam(data, name, paramIndex); |
144 | | - }); |
145 | | - registerParameterAnnotation( |
146 | | - QueryParam.class, |
147 | | - (param, data, paramIndex) -> { |
148 | | - final String name = param.value(); |
149 | | - checkState( |
150 | | - emptyToNull(name) != null, |
151 | | - "QueryParam.value() was empty on parameter %s", |
152 | | - paramIndex); |
153 | | - final String query = addTemplatedParam(name); |
154 | | - data.template().query(name, query); |
155 | | - nameParam(data, name, paramIndex); |
156 | | - }); |
157 | | - registerParameterAnnotation( |
158 | | - HeaderParam.class, |
159 | | - (param, data, paramIndex) -> { |
160 | | - final String name = param.value(); |
161 | | - checkState( |
162 | | - emptyToNull(name) != null, |
163 | | - "HeaderParam.value() was empty on parameter %s", |
164 | | - paramIndex); |
165 | | - final String header = addTemplatedParam(name); |
166 | | - data.template().header(name, header); |
167 | | - nameParam(data, name, paramIndex); |
168 | | - }); |
169 | | - registerParameterAnnotation( |
170 | | - FormParam.class, |
171 | | - (param, data, paramIndex) -> { |
172 | | - final String name = param.value(); |
173 | | - checkState( |
174 | | - emptyToNull(name) != null, "FormParam.value() was empty on parameter %s", paramIndex); |
175 | | - data.formParams().add(name); |
176 | | - nameParam(data, name, paramIndex); |
177 | | - }); |
178 | | - |
179 | | - // Reflect over the Bean Param looking for supported parameter annotations |
180 | | - registerParameterAnnotation( |
181 | | - BeanParam.class, |
182 | | - (param, data, paramIndex) -> { |
183 | | - final Field[] aggregatedParams = |
184 | | - data.method().getParameters()[paramIndex].getType().getDeclaredFields(); |
185 | | - |
186 | | - for (Field aggregatedParam : aggregatedParams) { |
187 | | - |
188 | | - if (aggregatedParam.isAnnotationPresent(PathParam.class)) { |
189 | | - final String name = aggregatedParam.getAnnotation(PathParam.class).value(); |
190 | | - checkState( |
191 | | - emptyToNull(name) != null, |
192 | | - "BeanParam parameter %s contains PathParam with empty .value() on field %s", |
193 | | - paramIndex, |
194 | | - aggregatedParam.getName()); |
195 | | - nameParam(data, name, paramIndex); |
196 | | - } |
197 | | - |
198 | | - if (aggregatedParam.isAnnotationPresent(QueryParam.class)) { |
199 | | - final String name = aggregatedParam.getAnnotation(QueryParam.class).value(); |
200 | | - checkState( |
201 | | - emptyToNull(name) != null, |
202 | | - "BeanParam parameter %s contains QueryParam with empty .value() on field %s", |
203 | | - paramIndex, |
204 | | - aggregatedParam.getName()); |
205 | | - final String query = addTemplatedParam(name); |
206 | | - data.template().query(name, query); |
207 | | - nameParam(data, name, paramIndex); |
208 | | - } |
209 | | - |
210 | | - if (aggregatedParam.isAnnotationPresent(HeaderParam.class)) { |
211 | | - final String name = aggregatedParam.getAnnotation(HeaderParam.class).value(); |
212 | | - checkState( |
213 | | - emptyToNull(name) != null, |
214 | | - "BeanParam parameter %s contains HeaderParam with empty .value() on field %s", |
215 | | - paramIndex, |
216 | | - aggregatedParam.getName()); |
217 | | - final String header = addTemplatedParam(name); |
218 | | - data.template().header(name, header); |
219 | | - nameParam(data, name, paramIndex); |
220 | | - } |
221 | | - |
222 | | - if (aggregatedParam.isAnnotationPresent(FormParam.class)) { |
223 | | - final String name = aggregatedParam.getAnnotation(FormParam.class).value(); |
224 | | - checkState( |
225 | | - emptyToNull(name) != null, |
226 | | - "BeanParam parameter %s contains FormParam with empty .value() on field %s", |
227 | | - paramIndex, |
228 | | - aggregatedParam.getName()); |
229 | | - data.formParams().add(name); |
230 | | - nameParam(data, name, paramIndex); |
231 | | - } |
232 | | - } |
233 | | - }); |
234 | | - } |
235 | | - |
236 | | - // Not using override as the super-type's method is deprecated and will be removed. |
237 | | - private String addTemplatedParam(String name) { |
238 | | - return String.format("{%s}", name); |
239 | | - } |
240 | | -} |
| 18 | +/** |
| 19 | + * @deprecated use {@link JAXRS3Contract} instead |
| 20 | + */ |
| 21 | +public class JakartaContract extends JAXRS3Contract {} |
0 commit comments