Search code examples
javajerseyjersey-2.0

org.glassfish.jersey.server.ContainerException: java.io.IOException: Stream closed


I have register two ContainerRequestFilter filters and one ContainerResponseFilter in my application class that extends a JAX-RS Application:

filters as :

register(LoggingResponseFilter.class);
register(AuthorizationRequestFilter.class);
register(LikeRequestFilter.class);

and when I tries to call a targeted method resource I have an Exception.

Resource method as :

@Override
@POST
@Path("/{userId}/like")
@TokenResource
@GMTResource   // to validate GMT of client and server
@Like       // for like filter
public Response like(@HeaderParam("token") @NotNull String token,
            @HeaderParam("user-agent") String userAgent,
            @NotNull @Valid UserFriendsBaseModel userFriendsBaseModel)
            throws BadRequestException, UnauthorizedException,
            ForbiddenException, InternalServerError {
    userService.like(userFriendsBaseModel,userAgent);
    return Response.ok().build();
}

Exception :

    javax.servlet.ServletException: org.glassfish.jersey.server.ContainerException: java.io.IOException: Stream closed
    org.glassfish.jersey.servlet.WebComponent.service(WebComponent.java:419)
    org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:381)
    org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:344)
    org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:221)
    org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
root cause

org.glassfish.jersey.server.ContainerException: java.io.IOException: Stream closed
    org.glassfish.jersey.servlet.internal.ResponseWriter.rethrow(ResponseWriter.java:256)
    org.glassfish.jersey.servlet.internal.ResponseWriter.failure(ResponseWriter.java:238)
    org.glassfish.jersey.server.ServerRuntime$Responder.process(ServerRuntime.java:480)
    org.glassfish.jersey.server.ServerRuntime$2.run(ServerRuntime.java:311)
    org.glassfish.jersey.internal.Errors$1.call(Errors.java:271)
    org.glassfish.jersey.internal.Errors$1.call(Errors.java:267)
    org.glassfish.jersey.internal.Errors.process(Errors.java:315)
    org.glassfish.jersey.internal.Errors.process(Errors.java:297)
    org.glassfish.jersey.internal.Errors.process(Errors.java:267)
    org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:317)
    org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:286)
    org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:1072)
    org.glassfish.jersey.servlet.WebComponent.service(WebComponent.java:399)
    org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:381)
    org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:344)
    org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:221)
    org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
root cause

java.io.IOException: Stream closed
    org.apache.catalina.connector.InputBuffer.read(InputBuffer.java:312)
    org.apache.catalina.connector.CoyoteInputStream.read(CoyoteInputStream.java:200)
    org.glassfish.jersey.message.internal.EntityInputStream.read(EntityInputStream.java:101)
    org.glassfish.jersey.message.internal.ReaderInterceptorExecutor$UnCloseableInputStream.read(ReaderInterceptorExecutor.java:301)
    com.fasterxml.jackson.core.json.ByteSourceJsonBootstrapper.ensureLoaded(ByteSourceJsonBootstrapper.java:503)
    com.fasterxml.jackson.core.json.ByteSourceJsonBootstrapper.detectEncoding(ByteSourceJsonBootstrapper.java:129)
    com.fasterxml.jackson.core.json.ByteSourceJsonBootstrapper.constructParser(ByteSourceJsonBootstrapper.java:224)
    com.fasterxml.jackson.core.JsonFactory._createParser(JsonFactory.java:1242)
    com.fasterxml.jackson.core.JsonFactory.createParser(JsonFactory.java:753)
    com.fasterxml.jackson.jaxrs.base.ProviderBase._createParser(ProviderBase.java:791)
    com.fasterxml.jackson.jaxrs.base.ProviderBase.readFrom(ProviderBase.java:760)
    org.glassfish.jersey.message.internal.ReaderInterceptorExecutor$TerminalReaderInterceptor.invokeReadFrom(ReaderInterceptorExecutor.java:259)
    org.glassfish.jersey.message.internal.ReaderInterceptorExecutor$TerminalReaderInterceptor.aroundReadFrom(ReaderInterceptorExecutor.java:235)
    org.glassfish.jersey.message.internal.ReaderInterceptorExecutor.proceed(ReaderInterceptorExecutor.java:155)
    org.glassfish.jersey.server.internal.MappableExceptionWrapperInterceptor.aroundReadFrom(MappableExceptionWrapperInterceptor.java:74)
    org.glassfish.jersey.message.internal.ReaderInterceptorExecutor.proceed(ReaderInterceptorExecutor.java:155)
    org.glassfish.jersey.message.internal.MessageBodyFactory.readFrom(MessageBodyFactory.java:1075)
    org.glassfish.jersey.message.internal.InboundMessageContext.readEntity(InboundMessageContext.java:853)
    org.glassfish.jersey.server.ContainerRequest.readEntity(ContainerRequest.java:270)
    org.glassfish.jersey.server.internal.inject.EntityParamValueFactoryProvider$EntityValueFactory.provide(EntityParamValueFactoryProvider.java:96)
    org.glassfish.jersey.server.spi.internal.ParameterValueHelper.getParameterValues(ParameterValueHelper.java:81)
    org.glassfish.jersey.server.model.internal.JavaResourceMethodDispatcherProvider$AbstractMethodParamInvoker.getParamValues(JavaResourceMethodDispatcherProvider.java:125)
    org.glassfish.jersey.server.model.internal.JavaResourceMethodDispatcherProvider$ResponseOutInvoker.doDispatch(JavaResourceMethodDispatcherProvider.java:158)
    org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.dispatch(AbstractJavaResourceMethodDispatcher.java:97)
    org.glassfish.jersey.server.model.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:389)
    org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:347)
    org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:102)
    org.glassfish.jersey.server.ServerRuntime$2.run(ServerRuntime.java:303)
    org.glassfish.jersey.internal.Errors$1.call(Errors.java:271)
    org.glassfish.jersey.internal.Errors$1.call(Errors.java:267)
    org.glassfish.jersey.internal.Errors.process(Errors.java:315)
    org.glassfish.jersey.internal.Errors.process(Errors.java:297)
    org.glassfish.jersey.internal.Errors.process(Errors.java:267)
    org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:317)
    org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:286)
    org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:1072)
    org.glassfish.jersey.servlet.WebComponent.service(WebComponent.java:399)
    org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:381)
    org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:344)
    org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:221)
    org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)

I have also set the priority for request filters as:

@Priority(200)
public class LikeRequestFilter implements ContainerRequestFilter {

    private final Logger _logger = LoggerFactory.getLogger(LikeRequestFilter.class);

    @Inject
    ResourceInfo resourceInfo;

    @Inject
    UriInfo uriInfo;

    @Autowired
    UserManager userManager;

    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {
        if (resourceInfo.getResourceMethod().isAnnotationPresent(Like.class)) {
            try {
                ObjectMapper jsonMapper = new ObjectMapper();
                final UserFriendsBaseModel usm = jsonMapper
                        .readValue(requestContext.getEntityStream(), UserFriendsBaseModel.class);
                boolean isLiked = userManager.isLiked(usm);
                /**
                 *  If already liked (on api server db ) the check for other conditions
                 *  otherwise consider as first time like request and send to resource for processing 
                 */
                if(isLiked) {
                    /**
                     * Check if already liked on openfire
                     */
                    boolean isRoster = userManager.isRoster(usm);
                    /**
                     * if already roster then check for reverse like
                     * else add entry in add roster failure request
                     */
                    if(isRoster) {
                        /**
                         * Check if reverse like on api server if liked then check for openfire
                         */
                        final UserFriendsBaseModel reverseModel = UserFriendsBaseModel.reverse(usm);
                        boolean isReverseLiked = userManager.isLiked(reverseModel);
                        /**
                         * Reverse liked(Friends) on api server db . Check on openfire
                         */
                        if(isReverseLiked) {
                            boolean isReverseRoster = userManager.isRosterFriend(reverseModel);
                            /**
                             * If friend on openfire then send push to both user for new Match
                             */
                            if(isReverseRoster) {
                                // TODO send push
                            } else {
                                userManager.addRosterFailureRequest(reverseModel);
                            }
                        }

                    } else {
                        userManager.addRosterFailureRequest(usm);
                    }

                    requestContext.abortWith(Response.ok().build());
                }


            } catch (Exception e) {
                _logger.error("Something went wrong in Like request Filter", e);
            }

            _logger.info("end of if in LIKE Filter : ");
        }
        _logger.info("End of LIKE Filter : ");
    }

}

and for AuthorizationRequestFilter filter as :

@Priority(100)
public class AuthorizationRequestFilter implements ContainerRequestFilter {

    private final Logger _logger = LoggerFactory
            .getLogger(AuthorizationRequestFilter.class);

    @Inject
    ResourceInfo resourceInfo;

    @Inject
    UriInfo uriInfo;

    @Autowired
    AuthorizationService authorizationService;

and a Response filter as :

public class LoggingResponseFilter implements ContainerResponseFilter {

    final static Logger _logger = Logger.getLogger(LoggingResponseFilter.class
            .getName());

    public void filter(ContainerRequestContext requestContext,ContainerResponseContext responseContext) throws IOException {
        String method = requestContext.getMethod();

        _logger.info("Requesting : " + method + " for path " + requestContext.getUriInfo().getPath());

then problem occurs in LikeRequestFilter when it calls in filter chain after AuthorizationRequestFilter.

I am unable to find out what's going wrong ? how to chain request filter with response filter ?


Solution

  • The problem is with the LikeRequestFilter. You are reading the request entity InputStream with the ObjectMapper. Generally, InputStreams can only be read once, then they are empty. For this reason, after the ObjectMapper reads the stream, it closes it. So when Jersey tries to read it, it can't, as it's closed. Even if it wasn't closed, it would still be empty.

    For this use case, Jersey allows us to buffer the stream, so it can be read multiple times. What you need to do though is cast the ContainerRequestContext to ContainerRequest, which has a bufferEntity() method. It also has methods to help with the reading of the stream, like readEntity, that is used similar to the client Response#readEntity().

    So you could do something like

    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {
        try {
            ContainerRequest cr = (ContainerRequest) requestContext;
            cr.bufferEntity();
            final UserFriendsBaseModel bm = cr.readEntity(UserFriendsBaseModel.class);