Bug #16391

gxrest jersey response problem

Added by Lucio Lelii about 1 month ago. Updated about 1 month ago.

Status:ClosedStart date:Mar 27, 2019
Priority:UrgentDue date:
Assignee:Lucio Lelii% Done:

100%

Category:common
Sprint:StorageHub Service
Milestones:
Duration:

Description

Jersey prints an error on server side logs when the return type of the called methdo is string and it throws a gxrest exception.
the log is the following:

javax.ws.rs.InternalServerErrorException: HTTP 500 Internal Server Error
    at org.glassfish.jersey.server.internal.MappableExceptionWrapperInterceptor.aroundWriteTo(MappableExceptionWrapperInterceptor.java:89)
    at org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:162)
    at org.glassfish.jersey.message.internal.MessageBodyFactory.writeTo(MessageBodyFactory.java:1154)
    at org.glassfish.jersey.server.ServerRuntime$Responder.writeResponse(ServerRuntime.java:621)
    at org.glassfish.jersey.server.ServerRuntime$Responder.processResponse(ServerRuntime.java:377)
    at org.glassfish.jersey.server.ServerRuntime$Responder.process(ServerRuntime.java:420)
    at org.glassfish.jersey.server.ServerRuntime$1.run(ServerRuntime.java:277)
    at org.glassfish.jersey.internal.Errors$1.call(Errors.java:271)
    at org.glassfish.jersey.internal.Errors$1.call(Errors.java:267)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:315)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:297)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:267)
    at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:297)
    at org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:254)
    at org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:1030)
    at org.glassfish.jersey.servlet.WebComponent.service(WebComponent.java:373)
    at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:381)
    at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:344)
    at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:221)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:303)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
    at org.gcube.smartgears.managers.RequestManager.doFilter(RequestManager.java:99)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:220)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:501)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:170)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:98)
    at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:950)
    at org.apache.catalina.valves.RemoteIpValve.invoke(RemoteIpValve.java:683)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408)
    at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1041)
    at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:607)
    at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:313)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:748)
Caused by: org.glassfish.jersey.message.internal.MessageBodyProviderNotFoundException: MessageBodyWriter not found for media type=text/html, type=class org.gcube.common.gxrest.response.entity.SerializableErrorEntity, genericType=class org.gcube.common.gxrest.response.entity.SerializableErrorEntity.
    at org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor.aroundWriteTo(WriterInterceptorExecutor.java:247)
    at org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:162)
    at org.glassfish.jersey.server.internal.JsonWithPaddingInterceptor.aroundWriteTo(JsonWithPaddingInterceptor.java:106)
    at org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:162)
    at org.glassfish.jersey.server.internal.MappableExceptionWrapperInterceptor.aroundWriteTo(MappableExceptionWrapperInterceptor.java:85)

History

#1 Updated by Lucio Lelii about 1 month ago

It doesn't happen when return type is a JSON mapped object

#2 Updated by Manuele Simi about 1 month ago

  • Status changed from New to In Progress

#3 Updated by Manuele Simi about 1 month ago

  • % Done changed from 0 to 50
  • Assignee changed from Manuele Simi to Lucio Lelii
  • Status changed from In Progress to Feedback

I've added to the error response a method that allows to set the media type for the returned entity:

GXOutboundErrorResponse.throwExceptionWithTrace(Exception exception, int keepLines, Response.Status status, MediaType type)

I don't have an easy way to test it. Can you please force update to the latest gxJRS 1.1.2-SNAPSHOT and give it a try? In your case I guess the value for the type parameter should be MediaType.TEXT_PLAIN.

#4 Updated by Lucio Lelii about 1 month ago

this solution doesn't work. A possible solution is to implement the message body writer (and the reader) for SerializableErrorEntity, I tried adding the following class and registered it as provider in my jersey service and the error server side works as expected.

package org.gcube.common.gxrest.response.entity;

import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Provider;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;

@Provider
@Produces("text/plain")
public class SerializableErrorEntityMessageBodyWriter implements MessageBodyWriter<SerializableErrorEntity> {

    @Override
    public boolean isWriteable(Class<?> type, Type genericType,
                               Annotation[] annotations, MediaType mediaType) {
        return type == SerializableErrorEntity.class;
    }

    @Override
    public long getSize(SerializableErrorEntity errorEntity, Class<?> type, Type genericType,
                        Annotation[] annotations, MediaType mediaType) {
        // deprecated by JAX-RS 2.0 and ignored by Jersey runtime
        return 0;
    }

    @Override
    public void writeTo(SerializableErrorEntity errorEntity, Class<?> type, Type genericType, Annotation[] annotations,
                        MediaType mediaType, MultivaluedMap<String, Object> httpHeaders,
                        OutputStream out) throws IOException, WebApplicationException {

        Writer writer = new PrintWriter(out);
        writer.write(errorEntity.getEncodedTrace());
        writer.flush();
        writer.close();
    }
}

#5 Updated by Lucio Lelii about 1 month ago

  • Status changed from Feedback to In Progress

#6 Updated by Manuele Simi about 1 month ago

That's indeed a possible way to work around the issue. I was hoping to find a more generic solution that does not require to register new provider.
I'll further investigate before going for the reader/writer of text/plain.

BTW, can you point me on the svn path where you submit the gxrest request that originates the error?

#8 Updated by Pasquale Pagano about 1 month ago

  • Priority changed from High to Urgent

This issue is indirectly blocking the release of another component that is urgently required by a community of users. If possible, please fix it asap while a proper solution could always be released.

#9 Updated by Manuele Simi about 1 month ago

I investigated the problem and the storage hub components.

I believe this issue originates from a partial adoption of gxRest in these components. A bit of explanation is needed before going to the solutions.

gxJRS promotes a clean usage of the RESTful approach. In this “ideal” world, each REST resource offers methods to add, update, get and delete the resource. Taking the example of the ItemSharing resource of the storage hub (which I understand is the method called in the reported stacktrace), it should have 4 methods to create, edit, fetch and delete a shared folder.

What to return by each method is also modeled in REST.
For instance, the create method should return the HTTP status code 201 for a successful PUT of a new resource, with the most specific URI for the new resource returned in the Location header. This is easily achievable with gxJRS if a javax.ws.rs.core.Response instance is returned by each resource method. And that’s also the assumption made when gxJRS is used to throw an Exception as response. Under the hood, it creates and returns a javax.ws.rs.WebApplicationException, which is ultimately wrapped into Response object by Jersey.

And here it lies the root of the exception reported in this issue: you are trying to return a Response object but the share method of ItemSharing declares to return a String object.

Now, I see two possible solutions to this problem:

1) Change the share method’s signature as follows:

public Response share(@FormDataParam("users") Set<String> users, @FormDataParam("defaultAccessType") AccessType accessType){

  // body here 

 return  GXOutboundSuccessResponse.newCREATEResponse(“URI of the shared folder”)
          .withContent(“Folder successfully shared.”)
          .ofType(MediaType.TEXT_PLAIN).build()
}

This would help create a response with a correct HTTP status, media type and URI location for the resource as required by REST and make it possible to throw exceptions with GXOutboundErrorResponse.

See https://wiki.gcube-system.org/gcube/GxRest/GxJRS/Responses#Error_Responses

2) Since I understand that there is a lot of legacy code that cannot be easily converted, I’ve added to gxJRS a JAX-RS reader and writer to send the exception over a method that returns plain text.

Here are the new marshaler/unmarshaler for the error entity:

org.gcube.common.gxrest.response.entity.SerializableErrorEntityTextReader
org.gcube.common.gxrest.response.entity.SerializableErrorEntityTextWriter

They are both annotated as Provider and to produce/consume MediaType.TEXT_PLAIN, so to my best knowledge, Jersey should automatically discover and pick them up. They are available in the latest 1.1.2 snapshot deployed in the Maven repo (and svn trunk). However, I was able to test only the serialization/deserialization of the entity, so far. I am working to create a dedicate service for testing several aspects of gxRest, but it's not yet ready.

#10 Updated by Lucio Lelii about 1 month ago

GxRest should make the life easier not harder, If I have a tool that wraps automatically the return to a Response (jersey) for sure I want to use it.

#11 Updated by Manuele Simi about 1 month ago

The goal of gxRest is not to automatically wrap jersey responses. Instead, it helps creating requests and responses compliant with the REST approach. In doing so, it plainly builds atop of JAX-RS and it is totally independent from the JAX-RS runtime used to write the webapp and its clients.

#12 Updated by Lucio Lelii about 1 month ago

  • % Done changed from 50 to 100
  • Status changed from In Progress to Closed

ok, I have implemented the solution n. 2 registering the providers statically server and client side. It works correctly now.

Also available in: Atom PDF