Injection provider with injected MultivaluedMap (application/x-www-form-urlencoded) - java

The Jersey docs give an example of how to inject HttpSession on resources. How should I inject (or otherwise gain access to) form parameters sent on requests with "Content-Type: application/x-www-form-urlencoded"? I see that these are passed as parameters on methods, and do not seem to be annotated, letting me believe there is some quirk here?
The (naive) factory I'm currently working with is implemented as follows, JerseyHttpServletRequestWrapper being one of my own classes:
import org.glassfish.hk2.api.Factory;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.core.MultivaluedMap;
public class JerseyHttpServletRequestWrapperFactory implements Factory<JerseyHttpServletRequestWrapper> {
private final HttpServletRequest request;
private final MultivaluedMap<String, String> formParams;
#Inject
public JerseyHttpServletRequestWrapperFactory(HttpServletRequest request, MultivaluedMap<String, String> formParams) {
this.request = request;
this.formParams = formParams;
}
#Override
public JerseyHttpServletRequestWrapper provide() {
return new JerseyHttpServletRequestWrapper(request, formParams);
}
#Override
public void dispose(JerseyHttpServletRequestWrapper jerseyHttpServletRequestWrapper) {
}
}
I'm thinking here that an entity provider should be injected into the instance so that I can check if there is actually an entity sent with the request. Trying to directly inject MultivaluedMap errors out with:
org.glassfish.hk2.api.UnsatisfiedDependencyException: There was no object available for injection at SystemInjecteeImpl(requiredType=MultivaluedMap<String,String>,parent=JerseyHttpServletRequestWrapperFactory,qualifiers={},position=1,optional=false,self=false,unqualified=null,2067821943)

When you do
#POST
public Response post(MultivaluedMap<String, String> params) {}
this method parameter injection is handled differently than with regular field/constructor injection. So you can't try to inject the MultivaluedMap into a field.
What you can do though is inject the ContainerRequest, and then read the body. You'll want to check that it's a POST request and the Content-Type is application/x-www-form-urlencoded. If you don't, there's a chance you will get an exception when you try to read the entity.
#Inject
ContainerRequest request;
if (request.getMethod().toUpperCase().equals("POST")
&& request.getMediaType().equals(MediaType.APPLICATION_FORM_URLENCODED_TYPE)) {
request.bufferEntity();
Form form = request.readEntity(Form.class);
MultivaluedMap<String, String> params = form.asMap();
}

Related

HttpServletRequest getParameterMap - Fetch the form parameters alone

getParameterMap() of HttpServletRequest returns both the query params and posted form data.
I am able to fetch the query parameters alone from UriInfo.getQueryParameters().
But I need the form parameters separately as a MultivaluedMap similar to query parameters, is there a way to fetch it?
EDITED:
I apologize for not making this clear. I am trying to fetch the form parameters in the filter/interceptor.
You can put the MultivaluedMap as a parameter in the resource method. This will be the body of the request. JAX-RS will put all the parameters in the map for you.
#POST
#Consumes("application/x-www-form-urlencoded")
public Response post(#Context UriInfo uriInfo, MultivaluedMap params) {}
UPDATE (to edited post)
So if you want to get the parameters in a filter, you can get the body from the ContainerRequestContext. With Jersey, instead of getting the InputStream with context.getEntityStream(), you can cast the ContainerRequestContext to Jersey's ContainerRequest implementation. This will give you access to the methods bufferEntity() and readEntity(). These methods will allow you do easily get the form parameters. You will need to buffer the entity so that it can be read later when it needs to be passed on to your resource method.
#Provider
public class MyFilter implements ContainerRequestFilter {
#Override
public void filter(ContainerRequestContext context) throws IOException {
if (MediaTypes.typeEqual(MediaType.APPLICATION_FORM_URLENCODED_TYPE, context.getMediaType())) { {
return;
}
ContainerRequest request = (ContainerRequest) context;
request.bufferEntity();
Form form = request.readEntity(Form.class);
MultivaluedMap params<String, String> = form.asMap();
MultivaluedMap<String, String> query = context.getUriInfo().getQueryParameters();
}
}
If you want to use the filter only with specific resource methods, then you can use Name Binding or Dynamic Binding.
If for some reason the readEntity() returns an empty map (I've seen rare occurrences of people having this problem), you can try to retrieve the Form through an internal property
Object formProperty = request.getProperty(InternalServerProperties.FORM_DECODED_PROPERTY);
if (formProperty != null) {
Form for = (Form) formProperty;
}

How to add HTTP headers in Microprofile REST client dynamically?

I am developing application, which uses microprofile rest client. And that rest client should send REST request with various http header. Some headers names changes dynamically. My microprofile rest client should be generic, but I did not find how to implement such behaviour.
According to the documentation you need to specify all header names in the implementation via annotations and that is not generic. Is there any way how to "hack" it and add HTTP headers programatically?
Thanks in advance
GenericRestClient genericRestClient = null;
Map<String, Object> appConfig = context.appConfigs();
String baseUrl = (String) appConfig.get("restClient.baseUrl");
path = (String) appConfig.get("restClient.path");
try {
genericRestClient = RestClientBuilder.newBuilder()
.baseUri(new URI(baseUrl)).build(GenericRestClient.class);
}catch(URISyntaxException e){
logger.error("",e);
throw e;
}
Response response = genericRestClient.sendMessage(path, value);
logger.info("Status: "+response.getStatus());
logger.info("Response body: "+response.getEntity().toString());
Generic rest client code:
#RegisterRestClient
public interface GenericRestClient {
#POST
#Path("{path}")
#Produces("application/json")
#Consumes("application/json")
public Response sendMessage(<here should go any map of custom headers>, #PathParam("path") String pathParam, String jsonBody);
}
According to the spec, you can use a ClientHeadersFactory. Something like this:
public class CustomClientHeadersFactory implements ClientHeadersFactory {
#Override public MultivaluedMap<String, String> update(
MultivaluedMap<String, String> incomingHeaders,
MultivaluedMap<String, String> clientOutgoingHeaders
) {
MultivaluedMap<String, String> returnVal = new MultivaluedHashMap<>();
returnVal.putAll(clientOutgoingHeaders);
returnVal.putSingle("MyHeader", "generated");
return returnVal;
}
}
#RegisterRestClient
#RegisterClientHeaders(CustomClientHeadersFactory.class)
public interface GenericRestClient {
...
}
You can't pass values directly to the ClientHeadersFactory; but you can directly access the headers of an incoming request, if your own service is called via JAX-RS. You can also #Inject anything you need. If this is still not sufficient and you really need to pass things from the service call, you can use a custom #RequestScope bean, e.g.:
#RequestScope
class CustomHeader {
private String name;
private String value;
// getters/setters
}
public class CustomClientHeadersFactory implements ClientHeadersFactory {
#Inject CustomHeader customHeader;
#Override public MultivaluedMap<String, String> update(
MultivaluedMap<String, String> incomingHeaders,
MultivaluedMap<String, String> clientOutgoingHeaders
) {
MultivaluedMap<String, String> returnVal = new MultivaluedHashMap<>();
returnVal.putAll(clientOutgoingHeaders);
returnVal.putSingle(customHeader.getName(), customHeader.getValue());
return returnVal;
}
}
class Client {
#Inject CustomHeader customHeader;
void call() {
customHeader.setName("MyHeader");
customHeader.setValue("generated");
...
Response response = genericRestClient.sendMessage(path, value);
}
}
Add ClientRequestHeader to your client as follows:
#POST
// read from application.properties
#ClientRequestHeader(name=“myHeader1”, value="${myProperty}")
// read from a method
#ClientRequestHeader(name=“myHeader2”, value="{addHeaderMethod}")
Response sendMessage();
default String addHeaderMethod() {
//put your logic here
return “my dynamic value”;
}

How to get content type of the response in JAX-RS ExceptionMapper

I have a resource:
#GET
#Path("/print-order")
#Produces("application/pdf")
public byte[] printOrder(#QueryParam("id") Long orderId) {
return ...;
}
...which can throw an error that is relevant to the user and must be displayed as a HTML page. So I implemented an ExceptionMapper but I don't know how to get the value of #Produces("application/pdf") annotation of the called resource.
#Provider
public class CustomExceptionMapper implements ExceptionMapper<CustomException> {
#Override
public Response toResponse(CustomException exception) {
if (contentType = "application/pdf")
... html respone
else
... entity response
}
}
I'm using JAX-RS 1.x (jsr311) with Jersy 1.12 implementation but would love to have implementation independent solution.
You can inject different context objects into the ExceptionMapper to get more info on the request it handles. It's convenient to determine what content type the client expects based on HTTP's Accept header (learn more here).
Below is the example on how you can make ExceptionMapper to respond with different formats based on Accept header specified (or not specified) by your APIs client.
#Provider
public class CustomExceptionMapper implements ExceptionMapper<CustomException> {
// Inject headers of the request being processed
#Context
private HttpHeaders headers;
// or even all the request details
#Context
private HttpServletRequest request;
#Override
public Response toResponse(CustomException exception) {
List<MediaType> acceptedTypes = headers.getAcceptableMediaTypes();
if (acceptedTypes.contains(MediaType.APPLICATION_JSON)) {
// respond with entity
} else {
// respond with HTML
}
}
}
You initial idea can be implemented, though. You can inject HttpServletRequest in your resource class and use setAttribute() method to store application/pdf string within the context of current request. It can be later obtained in ExceptionMapper using getAttribute() method. But I wouldn't recommend to do so unless absolutely necessary. It introduces not so obvious dependencies between components of your code.

How to send a query params map using RESTEasy proxy client

I'm looking for a way to pass a map that contains param names and values to a GET Web Target. I am expecting RESTEasy to convert my map to a list of URL query params; however, RESTEasy throws an exception saying Caused by: javax.ws.rs.ProcessingException: RESTEASY004565: A GET request cannot have a body.
. How can I tell RESTEasy to convert this map to a URL query parameters?
This is the proxy interface:
#Path("/")
#Consumes(MediaType.APPLICATION_JSON)
public interface ExampleClient {
#GET
#Path("/example/{name}")
#Produces(MediaType.APPLICATION_JSON)
Object getObject(#PathParam("name") String name, MultivaluedMap<String, String> multiValueMap);
}
This is the usage:
#Controller
public class ExampleController {
#Inject
ExampleClient exampleClient; // injected correctly by spring DI
// this runs inside a spring controller
public String action(String objectName) {
MultivaluedMap<String, String> params = new MultivaluedHashMap<>();
// in the real code I get the params and values from a DB
params.add("foo", "bar")
params.add("jar", "car")
//.. keep adding
exampleClient.getObject(objectName, params); // throws exception
}
}
After hours digging down in RESTEasy source code, I found out that there is no way to do that though interface annotation. In short, RESTEasy creates something called a 'processor' from org.jboss.resteasy.client.jaxrs.internal.proxy.processors.ProcessorFactory to map the annotation to the target URI.
However, it is really simple to solve this issue by creating a ClientRequestFilter that takes the Map from the request body (before executing the request of course), and place them inside the URI query param. Check the code below:
The filter:
#Provider
#Component // because I'm using spring boot
public class GetMessageBodyFilter implements ClientRequestFilter {
#Override
public void filter(ClientRequestContext requestContext) throws IOException {
if (requestContext.getEntity() instanceof Map && requestContext.getMethod().equals(HttpMethod.GET)) {
UriBuilder uriBuilder = UriBuilder.fromUri(requestContext.getUri());
Map allParam = (Map)requestContext.getEntity();
for (Object key : allParam.keySet()) {
uriBuilder.queryParam(key.toString(), allParam.get(key));
}
requestContext.setUri(uriBuilder.build());
requestContext.setEntity(null);
}
}
}
PS: I have used Map instead of MultivaluedMap for simplicity

Jersey - Determine "#Produces" using Context?

I'm new to Jersey, and want to determine the #Produces type in other contexts, so I can use it during error handling cases.
For example, I have the following method that produces json:
#Path("test-json")
#Produces(MediaType.APPLICATION_JSON)
#GET
public Object getTestJson(#Context HttpServletRequest req, #Context HttpServletResponse res) throws Exception
{
throw new RuntimeException("POST submitted without CSRF token! ");
}
Later on, in a global exception handler, I'd like to get the #Produces media type.
I've tried doing this with something like the following, but getMediaType() is returning null (note that this is simplified, but headers is not null in all of my tests, just getMediaType() is null).
public class someClass
{
#Context
HttpHeaders headers;
public Response convertExceptionToResponse(T exception)
{
MediaType mediaType = headers.getMediaType();
// At this point, I thought media type would be
// MediaType.APPLICATION_JSON
// for the above 'getTestJson' method, but it's null.
}
}
How can I do this?
JAX-RS
Inject ResourceInfo and invoke getResourceMethod() which will return Java Method. Then you can simple retrieve declared annotations. The problem here is that with this approach you need to do a lot of coding in case #Produces is not located directly on a method but somewhere in the hierarchy.
Jersey 2
Inject ExtendedUriInfo
#Context
private ExtendedUriInfo uriInfo;
and look for matched ResourceMethod (getMatchedResourceMethod()). Then simply get list of producible media types (getProducedTypes()).

Categories

Resources