13

In Jersey 1.x, you can use ContainerRequest.getFormParameters() to do request filtering on the form data, but I don't see an obvious equivalent in Jersey 2.x. I've implemented the ContainerRequestFilter interface which gives me access to a ContainerRequestContext, but from there how can get the form data?

Jersey 1.x example:

public class MyFilter implements ContainerRequestFilter {
  public ContainerRequest filter(ContainerRequest request) {
    Form f = request.getFormParameters();

    // examine form data and filter as needed
  }
}

Jersey 2.x example:

public class MyFilter implements ContainerRequestFilter {
  public void filter(ContainerRequestContext context) {
    // how do I get to the Form data now?
  }
}
Mike
  • 4,722
  • 1
  • 27
  • 40

4 Answers4

21

After a ton of searching and trial and error, I have found a suitable way to do this in Jersey 2. You have to consume the request entity body manually, but you have to be careful to do it in a way that doesn't prevent subsequent filters and resources from also consuming it. Below is a simple example that reads the entity into a Form object:

@Provider
public class FormDataFilter implements ContainerRequestFilter
{
    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException
    {
        if (requestContext instanceof ContainerRequest)
        {
            ContainerRequest request = (ContainerRequest) requestContext;

            if ( requestContext.hasEntity()
              && MediaTypes.typeEqual(MediaType.APPLICATION_FORM_URLENCODED_TYPE,request.getMediaType()))
            {
                request.bufferEntity();
                Form f = request.readEntity(Form.class);
            }
        }
    } 
}

The key is calling bufferEntity(). Without this, the entity is marked as closed and causes IllegalStateExceptions on any subsequent read attempt.

Mike
  • 4,722
  • 1
  • 27
  • 40
  • 1
    Note that `ContainerRequest` is a Jersey class, and not part of the JAX-RS standard. Unfortunately, this is not possible with JAX-RS alone according to https://java.net/jira/browse/JERSEY-2664. – Zero3 Dec 15 '15 at 18:26
3

form POST parameters are sent in the http request body, so with ContainerRequestContext can you do something like

String q = IOUtils.toString(context.getEntityStream(), Charsets.UTF_8);
String[] params = q.split("&");  
Map<String, String> map = new HashMap<>();  
 for (String param : params)  
 {  
     String name = param.split("=")[0];  
     String value = param.split("=")[1];  
     map.put(name, value);  
 }  
bhowden
  • 669
  • 7
  • 15
  • 1
    Thanks, it does seem like you have to manipulate the entity stream directly. I just wish the Jersey team would have left this in there to help handle the converting of the stream, decoding, and building a multi-valued map. Seems like a pretty common use case that it warrants being in the API. – Mike Sep 05 '14 at 20:30
  • sorry but, its not work in my case , I also need form param from `ContainerRequestContext ` – HybrisHelp Sep 24 '14 at 12:10
  • it is using ContainerRequestContext: https://jax-rs-spec.java.net/nonav/2.0-SNAPSHOT/apidocs/javax/ws/rs/container/ContainerRequestContext.html#getEntityStream() – bhowden Sep 24 '14 at 14:26
2

Here's a way to read the form entity without relying on implementation specific classes, i.e. it will work with both Jersey (v2) or CXF (v3).

@Provider
public class AFilter implements ContainerRequestFilter {

    @Context
    private Providers providers;

    @Override
    public void filter(ContainerRequestContext request) throws IOException {
        if (!request.hasEntity() || !MediaTypes.typeEqual(APPLICATION_FORM_URLENCODED_TYPE, request.getMediaType())) {
            // if not a form ...
            return;
        }

        ByteArrayInputStream resettableIS = toResettableStream(request.getEntityStream());

        Form form = providers.getMessageBodyReader(Form.class, Form.class, new Annotation[0], APPLICATION_FORM_URLENCODED_TYPE)
                             .readFrom(Form.class, Form.class, new Annotation[0], APPLICATION_FORM_URLENCODED_TYPE, null, resettableIS);

        // do something with Form

        resettableIS.reset();
        request.setEntityStream(resettableIS);
    }

    @Nonnull
    private ByteArrayInputStream toResettableStream(InputStream entityStream) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len;
        while ((len = entityStream.read(buffer)) > -1) {
            baos.write(buffer, 0, len);
        }
        baos.flush();
        return new ByteArrayInputStream(baos.toByteArray());
    }
}

This works well and has the benefit of using only JAX-RS API thus is is portable.

Note however that CXF 2.x uses JAX-RS API 2.0-m10 which do not have the Form class yet. In this case one can simply replace Form.class by the MultivaluedMap.class at the price of some unchecked / raw type warnings.

bric3
  • 40,072
  • 9
  • 91
  • 111
0

Looking at the jersey 1 implementation, it seems that ContainerRequest#getEntity(Class) supports reading the entity stream directly into a Form class.

getEntity has been renamed to readEntity in jersey 2, so the following might work (untested):

Form params = request.readEntity(Form.class);

Suggested impl (stolen and slightly modified) from the jersey 1 impl:

/**
 * Stolen from the jersey 1 impl
 */
public static Form getFormParameters(ContainerRequest request) {
    if (MediaTypes.typeEqual(MediaType.APPLICATION_FORM_URLENCODED_TYPE, request.getMediaType())) {
        InputStream in = request.getEntityStream();
        if (in.getClass() != ByteArrayInputStream.class) {
            // Buffer input
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            try {
                ReaderWriter.writeTo(in, baos);
            } catch (IOException e) {
                throw new IllegalArgumentException(e);
            }

            in = new ByteArrayInputStream(baos.toByteArray());
            request.setEntityStream(in);
        }

        ByteArrayInputStream bais = (ByteArrayInputStream) in;
        Form f = request.readEntity(Form.class);
        bais.reset();
        return f;
    } else {
        return new Form();
    }
}
  • This won't work as is since ReaderWriter is not part of the Jersey 2 API. Not sure if there is a replacement, the migration guide doesn't mention one. – Mike Sep 17 '14 at 14:57