I am trying to use PATCH method in my client using CXF implementation of JAX-RS.
At first I defined the PATCH annotation as
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@HttpMethod("PATCH")
public @interface PATCH {
}
Referencing what was written here :
How to have a @PATCH annotation for JAX-RS?
Then I found out @PATCH was added into CXF 3.1.2, so I changed version in my maven's pom.xml and indeed there is public @interface PATCH
inside of package org.apache.cxf.jaxrs.ext;
and the code actually looks exactly as what I posted above.
However, when I try to use this annotation on my service definition as
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public interface AbcService {
@PATCH
@Path("/abc/efg")
public SomeDTO patchSomething(RequestObject request);
}
I end up with the java.net.ProtocolException: Invalid HTTP method: PATCH
as was said in the queston link I posted above. They discuss some solution for this with Jersey, however what I can I do in CXF, so that I can use :
AbcService abcService = JAXRSClientFactory.create(myURI, AbcService.class, myProviders, true);
abcService.patchSomething(new RequestObject('something'));
So I have couple of questions:
- How can I make this work ? No I need to write custom CXF interceptor ?
- Why did they add the PATCH annotation into CXF if it doesn't work ?
- Some guys in the other topic said that the mentioned PATCH annotation definition works for them. How come ? Does it only make trouble on the client side, and if so why is it ?
- Why I can't find this annotation in CXF documentation ? I looked into org.apache.cxf.jaxrs.ext package at http://cxf.apache.org/javadoc/latest/ and I don't see any PATCH. Yet in the latest cxf 3.1.2 I really can find it in this package.
It turns out it's cause because in JAVA7, HttpURLConnection doesn't support PATCH, the supported methods in that class are defined statically as
private static final String[] methods = {
"GET", "POST", "HEAD", "OPTIONS", "PUT", "DELETE", "TRACE"
};
However it is possible to send PATCH request in CXF, but the Conduit object must be of type AsyncHTTPConduit
.
To make CXF use AsyncHTTPConduit, you can programatically achieve it like this
AbcService service = JAXRSClientFactory.create(myURI, AbcService.class, myProviders, true);
WebClient.getConfig(service).getRequestContext().put("use.async.http.conduit", true);
service.patchEnvironmentParameters(patchRequest);
Or
WebClient client = WebClient.create("http://localhost:53261/v1-0/api/environment/parameters");
WebClient.getConfig(client).getRequestContext().put("use.async.http.conduit", true);
client.invoke("PATCH", "{}");
But beware !! In order to make this work, you have put this dependency into your project
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http-hc</artifactId>
<version>${cxf.version}</version>
</dependency>
Also make sure that you use the same version of cxf-rt-transports-http-hc
and cxf
.
But as you can see what I described doesn't solve the original issue, this way I just made 1 specific PATCH request. However in my project there are many PATCH services defined using interfaces like I showed originally
public interface AbcService {
@PATCH
@Path("/abc/efg")
public SomeDTO patchSomething(RequestObject request);
}
So in order to use the AsyncHTTPConduit only on PATCH methods, I had to write custom CXF interceptor, about which you can learn more here http://cxf.apache.org/docs/interceptors.html
The interceptor I wrote runs in PRE_LOGIC phase and it checks what kind of method is used and in case it PATCH, it defined the conduit property. Then in latter phases of service invocation, CXF uses this property to choose which Conduit implementation should be used, and so after
if ( message.get(Message.HTTP_REQUEST_METHOD).equals("PATCH") {
message.put("use.async.http.conduit", true);
}
the AsyncHTTPConduit
instance will be used with which the PATCH will work.
Could you try to replace the use of @PATCH
with @POST
in your code to see if it works ? Your AbcService
interface misses an @Path
annotation at the type level (unless it is a subresource ?), so it might be worth trying with a standard HTTP verb first to make sure everything else is properly configured.