forked from OpenFeign/feign
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Jakarta annotations support (OpenFeign#1649)
* Copy-paste JAX-RS to new module to allow jakarta support * Update README file * Fix code formatting * Move jakart module to java11 profile * Add jakarta to overview map * Reuse testing code between Jakarta and jaxrs modules * Reuse testing code between Jakarta and jaxrs modules * Release jakarta as part of java 11 flow * Release jakarta as part of java 11 flow Co-authored-by: Marvin Froeder <velo@users.noreply.github.com> Co-authored-by: Marvin Froeder <velo.br@gmail.com>
- Loading branch information
1 parent
5c96c8f
commit bc54d21
Showing
9 changed files
with
1,258 additions
and
428 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
# Feign Jakarta | ||
This module overrides annotation processing to instead use standard ones supplied by the Jakarta specification. This is currently targeted at the 3.1 spec. | ||
|
||
## Limitations | ||
While it may appear possible to reuse the same interface across client and server, bear in mind that Jakarta resource | ||
annotations were not designed to be processed by clients. Finally, Jakarta is a large spec and attempts to implement | ||
it completely would be a project larger than feign itself. In other words, this implementation is *best efforts* and | ||
concedes far from 100% compatibility with server interface behavior. | ||
|
||
## Currently Supported Annotation Processing | ||
Feign only supports processing java interfaces (not abstract or concrete classes). | ||
|
||
Here are a list of behaviors currently supported. | ||
### Type Annotations | ||
#### `@Path` | ||
Appends the value to `Target.url()`. Can have tokens corresponding to `@PathParam` annotations. | ||
### Method Annotations | ||
#### `@HttpMethod` meta-annotation (present on `@GET`, `@POST`, etc.) | ||
Sets the request method. | ||
#### `@Path` | ||
Appends the value to `Target.url()`. Can have tokens corresponding to `@PathParam` annotations. | ||
#### `@Produces` | ||
Adds all values into the `Accept` header. | ||
#### `@Consumes` | ||
Adds the first value as the `Content-Type` header. | ||
### Parameter Annotations | ||
#### `@PathParam` | ||
Links the value of the corresponding parameter to a template variable declared in the path. | ||
#### `@QueryParam` | ||
Links the value of the corresponding parameter to a query parameter. When invoked, null will skip the query param. | ||
#### `@HeaderParam` | ||
Links the value of the corresponding parameter to a header. | ||
#### `@FormParam` | ||
Links the value of the corresponding parameter to a key passed to `Encoder.Text<Map<String, Object>>.encode()`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<!-- | ||
Copyright 2012-2022 The Feign Authors | ||
Licensed 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. | ||
--> | ||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
<modelVersion>4.0.0</modelVersion> | ||
|
||
<parent> | ||
<groupId>io.github.openfeign</groupId> | ||
<artifactId>parent</artifactId> | ||
<version>12.0-SNAPSHOT</version> | ||
</parent> | ||
|
||
<artifactId>feign-jakarta</artifactId> | ||
<name>Feign Jakarta</name> | ||
<description>Feign Jakarta</description> | ||
|
||
<properties> | ||
<main.basedir>${project.basedir}/..</main.basedir> | ||
</properties> | ||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>${project.groupId}</groupId> | ||
<artifactId>feign-core</artifactId> | ||
</dependency> | ||
|
||
<dependency> | ||
<groupId>jakarta.ws.rs</groupId> | ||
<artifactId>jakarta.ws.rs-api</artifactId> | ||
<version>3.1.0</version> | ||
</dependency> | ||
|
||
<!-- for example --> | ||
<dependency> | ||
<groupId>${project.groupId}</groupId> | ||
<artifactId>feign-gson</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
|
||
<dependency> | ||
<groupId>${project.groupId}</groupId> | ||
<artifactId>feign-core</artifactId> | ||
<type>test-jar</type> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>${project.groupId}</groupId> | ||
<artifactId>feign-jaxrs</artifactId> | ||
<type>test-jar</type> | ||
<scope>test</scope> | ||
</dependency> | ||
</dependencies> | ||
</project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
/* | ||
* Copyright 2012-2022 The Feign Authors | ||
* | ||
* Licensed 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 feign.jaxrs; | ||
|
||
import static feign.Util.checkState; | ||
import static feign.Util.emptyToNull; | ||
import static feign.Util.removeValues; | ||
import feign.DeclarativeContract; | ||
import feign.MethodMetadata; | ||
import feign.Request; | ||
import java.lang.annotation.Annotation; | ||
import java.lang.reflect.Method; | ||
import java.util.Collections; | ||
import jakarta.ws.rs.*; | ||
|
||
public class JakartaContract extends DeclarativeContract { | ||
|
||
static final String ACCEPT = "Accept"; | ||
static final String CONTENT_TYPE = "Content-Type"; | ||
|
||
// Protected so unittest can call us | ||
// XXX: Should parseAndValidateMetadata(Class, Method) be public instead? The old deprecated | ||
// parseAndValidateMetadata(Method) was public.. | ||
@Override | ||
protected MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method) { | ||
return super.parseAndValidateMetadata(targetType, method); | ||
} | ||
|
||
public JakartaContract() { | ||
super.registerClassAnnotation(Path.class, (path, data) -> { | ||
if (path != null && !path.value().isEmpty()) { | ||
String pathValue = path.value(); | ||
if (!pathValue.startsWith("/")) { | ||
pathValue = "/" + pathValue; | ||
} | ||
if (pathValue.endsWith("/")) { | ||
// Strip off any trailing slashes, since the template has already had slashes | ||
// appropriately | ||
// added | ||
pathValue = pathValue.substring(0, pathValue.length() - 1); | ||
} | ||
// jax-rs allows whitespace around the param name, as well as an optional regex. The | ||
// contract | ||
// should | ||
// strip these out appropriately. | ||
pathValue = pathValue.replaceAll("\\{\\s*(.+?)\\s*(:.+?)?\\}", "\\{$1\\}"); | ||
data.template().uri(pathValue); | ||
} | ||
}); | ||
super.registerClassAnnotation(Consumes.class, this::handleConsumesAnnotation); | ||
super.registerClassAnnotation(Produces.class, this::handleProducesAnnotation); | ||
|
||
registerMethodAnnotation(methodAnnotation -> { | ||
final Class<? extends Annotation> annotationType = methodAnnotation.annotationType(); | ||
final HttpMethod http = annotationType.getAnnotation(HttpMethod.class); | ||
return http != null; | ||
}, (methodAnnotation, data) -> { | ||
final Class<? extends Annotation> annotationType = methodAnnotation.annotationType(); | ||
final HttpMethod http = annotationType.getAnnotation(HttpMethod.class); | ||
checkState(data.template().method() == null, | ||
"Method %s contains multiple HTTP methods. Found: %s and %s", data.configKey(), | ||
data.template().method(), http.value()); | ||
data.template().method(Request.HttpMethod.valueOf(http.value())); | ||
}); | ||
|
||
super.registerMethodAnnotation(Path.class, (path, data) -> { | ||
final String pathValue = emptyToNull(path.value()); | ||
if (pathValue == null) { | ||
return; | ||
} | ||
String methodAnnotationValue = path.value(); | ||
if (!methodAnnotationValue.startsWith("/") && !data.template().url().endsWith("/")) { | ||
methodAnnotationValue = "/" + methodAnnotationValue; | ||
} | ||
// jax-rs allows whitespace around the param name, as well as an optional regex. The contract | ||
// should | ||
// strip these out appropriately. | ||
methodAnnotationValue = | ||
methodAnnotationValue.replaceAll("\\{\\s*(.+?)\\s*(:.+?)?\\}", "\\{$1\\}"); | ||
data.template().uri(methodAnnotationValue, true); | ||
}); | ||
super.registerMethodAnnotation(Consumes.class, this::handleConsumesAnnotation); | ||
super.registerMethodAnnotation(Produces.class, this::handleProducesAnnotation); | ||
|
||
// trying to minimize the diff | ||
registerParamAnnotations(); | ||
} | ||
|
||
private void handleProducesAnnotation(Produces produces, MethodMetadata data) { | ||
final String[] serverProduces = | ||
removeValues(produces.value(), mediaType -> emptyToNull(mediaType) == null, String.class); | ||
checkState(serverProduces.length > 0, "Produces.value() was empty on %s", data.configKey()); | ||
data.template().header(ACCEPT, Collections.emptyList()); // remove any previous produces | ||
data.template().header(ACCEPT, serverProduces); | ||
} | ||
|
||
private void handleConsumesAnnotation(Consumes consumes, MethodMetadata data) { | ||
final String[] serverConsumes = | ||
removeValues(consumes.value(), mediaType -> emptyToNull(mediaType) == null, String.class); | ||
checkState(serverConsumes.length > 0, "Consumes.value() was empty on %s", data.configKey()); | ||
data.template().header(CONTENT_TYPE, serverConsumes); | ||
} | ||
|
||
protected void registerParamAnnotations() { | ||
{ | ||
registerParameterAnnotation(PathParam.class, (param, data, paramIndex) -> { | ||
final String name = param.value(); | ||
checkState(emptyToNull(name) != null, "PathParam.value() was empty on parameter %s", | ||
paramIndex); | ||
nameParam(data, name, paramIndex); | ||
}); | ||
registerParameterAnnotation(QueryParam.class, (param, data, paramIndex) -> { | ||
final String name = param.value(); | ||
checkState(emptyToNull(name) != null, "QueryParam.value() was empty on parameter %s", | ||
paramIndex); | ||
final String query = addTemplatedParam(name); | ||
data.template().query(name, query); | ||
nameParam(data, name, paramIndex); | ||
}); | ||
registerParameterAnnotation(HeaderParam.class, (param, data, paramIndex) -> { | ||
final String name = param.value(); | ||
checkState(emptyToNull(name) != null, "HeaderParam.value() was empty on parameter %s", | ||
paramIndex); | ||
final String header = addTemplatedParam(name); | ||
data.template().header(name, header); | ||
nameParam(data, name, paramIndex); | ||
}); | ||
registerParameterAnnotation(FormParam.class, (param, data, paramIndex) -> { | ||
final String name = param.value(); | ||
checkState(emptyToNull(name) != null, "FormParam.value() was empty on parameter %s", | ||
paramIndex); | ||
data.formParams().add(name); | ||
nameParam(data, name, paramIndex); | ||
}); | ||
} | ||
} | ||
|
||
// Not using override as the super-type's method is deprecated and will be removed. | ||
private String addTemplatedParam(String name) { | ||
return String.format("{%s}", name); | ||
} | ||
} |
Oops, something went wrong.