Catch exception from Object.afterUnmarshal in Glassfish 4.1

纵然是瞬间 提交于 2019-12-12 02:56:56

问题


I am currently running Glassfish 4.1 on JDK 1.8.0-40. I am using javaee-web-api-7.0 and jersey-media-moxy-2.22. I am marshalling/unmarshalling JSON and XML from/to JAXB-annotated java objects.

I have a ContextResolver<Unmarshaller> set up to provide an Unmarshaller with a custom ValidationEventHandler which will collect exceptions from the property setters and throw a BadRequestException with the aggregate validation errors. This part is working.

However, I also have beforeMarshal and afterUnmarshal methods on the objects that check for unset properties (the rules for which properties must be set vary on the values of the properties, leading me to rule out validation against a schema). If an exception is thrown from the afterUnmarshal method, it is not seen by the ValidationEventHandler and instead bubbles up to the ExceptionMapper.

Is there any way to catch the exceptions from the beforeMarshal and afterUnmarshal methods on the individual objects and get them to the ValidationEventHandler?

I think it would be possible to implement a MessageBodyReader to catch the exceptions, use Unmarshaller.getEventHandler, manually call ValidationEventHandler.handleEvent, and throw a BadRequestException if handleEvent returns false [edit: if an exception is thrown from Unmarshaller.unmarshal, it wouldn't be possible to continue unmarshalling, so the only possible recourse is to terminate processing]. But this would be missing the event location information, and I don't particularly fancy implementing my own MessageBodyReader. I am hoping there is a easier built-in way to do this that I have not been able to discover.

Thanks in advance for any help.


回答1:


After a bunch of digging and headaches, I ended up developing a solution.

Step 1 (optional)

EDIT: You don't have to patch Jersey to achieve this behavior. I cannot find it documented anywhere, but the org.glassfish.jersey.internal.inject.Custom annotation marks a provider as custom (whereas @Provider alone is insufficient). You also have to disable MOXy by setting CommonProperties.MOXY_JSON_FEATURE_DISABLE to true if your provider is for application/json. Thus you only have to do:

@Custom
@Provider
public class MyCustomMessageBodyReader...
[...]

This is my least favorite part of my solution, but also saved me from a bunch of code duplication. Jersey's sorting algorithm for selecting MessageBodyReader/Writers has no way to prioritize application providers (that I could find). I wanted to extend AbstractRootElementJaxbProvider to re-use its functionality, but that meant I couldn't make it more specific than the Jersey-provided XmlRootElementJaxbProvider. Since by default Jersey only sorts on media type distance, object type distance, and whether a provider is registered as a custom provider (providers detected via the @Provider annotation aren't registered as custom providers), the Jersey implementation would always be selected instead of my MessageBodyReader/Writer.

I checked out the Jersey 2.10.4 source from Github and patched MessageBodyFactory to utilize @Priority annotations as part of the selection algorithm for MessageBodyReader/Writers.

diff --git a/core-common/src/main/java/org/glassfish/jersey/message/internal/MessageBodyFactory.java b/core-common/src/main/java/org/glassfish/jersey/message/internal/MessageBodyFactory.java
index 3845b0c..110f18c 100644
--- a/core-common/src/main/java/org/glassfish/jersey/message/internal/MessageBodyFactory.java
+++ b/core-common/src/main/java/org/glassfish/jersey/message/internal/MessageBodyFactory.java
@@ -72,6 +72,7 @@ import javax.ws.rs.ext.MessageBodyWriter;
 import javax.ws.rs.ext.ReaderInterceptor;
 import javax.ws.rs.ext.WriterInterceptor;

+import javax.annotation.Priority;
 import javax.inject.Inject;
 import javax.inject.Singleton;
 import javax.xml.transform.Source;
@@ -107,6 +108,8 @@ import jersey.repackaged.com.google.common.primitives.Primitives;
  */
 public class MessageBodyFactory implements MessageBodyWorkers {

+    private static final int DEFAULT_WORKER_PRIORITY = 1000;
+
     private static final Logger LOGGER = Logger.getLogger(MessageBodyFactory.class.getName());

     /**
@@ -218,13 +221,15 @@ public class MessageBodyFactory implements MessageBodyWorkers {
         public final T provider;
         public final List<MediaType> types;
         public final Boolean custom;
+        public final int priority;
         public final Class<?> providerClassParam;

         protected WorkerModel(
-                final T provider, final List<MediaType> types, final Boolean custom, Class<T> providerType) {
+                final T provider, final List<MediaType> types, final Boolean custom, final int priority, Class<T> providerType) {
             this.provider = provider;
             this.types = types;
             this.custom = custom;
+            this.priority = priority;
             this.providerClassParam = getProviderClassParam(provider, providerType);
         }

@@ -239,8 +244,8 @@ public class MessageBodyFactory implements MessageBodyWorkers {

     private static class MbrModel extends WorkerModel<MessageBodyReader> {

-        public MbrModel(MessageBodyReader provider, List<MediaType> types, Boolean custom) {
-            super(provider, types, custom, MessageBodyReader.class);
+        public MbrModel(MessageBodyReader provider, List<MediaType> types, Boolean custom, int priority) {
+            super(provider, types, custom, priority, MessageBodyReader.class);
         }

         public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
@@ -263,8 +268,8 @@ public class MessageBodyFactory implements MessageBodyWorkers {

     private static class MbwModel extends WorkerModel<MessageBodyWriter> {

-        public MbwModel(MessageBodyWriter provider, List<MediaType> types, Boolean custom) {
-            super(provider, types, custom, MessageBodyWriter.class);
+        public MbwModel(MessageBodyWriter provider, List<MediaType> types, Boolean custom, int priority) {
+            super(provider, types, custom, priority, MessageBodyWriter.class);
         }

         public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
@@ -437,6 +442,10 @@ public class MessageBodyFactory implements MessageBodyWorkers {
             if (modelA.custom ^ modelB.custom) {
                 return (modelA.custom) ? -1 : 1;
             }
+
+            if(modelA.priority != modelB.priority) {
+                return modelA.priority - modelB.priority;
+            }
             return 0;
         }

@@ -578,17 +587,27 @@ public class MessageBodyFactory implements MessageBodyWorkers {
         }
     }

+    private static int getPriority(Priority annotation) {
+        if (annotation == null) {
+            return DEFAULT_WORKER_PRIORITY;
+        }
+
+        return annotation.value();
+    }
+
     private static void addReaders(List<MbrModel> models, Set<MessageBodyReader> readers, boolean custom) {
         for (MessageBodyReader provider : readers) {
+            int priority = getPriority(provider.getClass().getAnnotation(Priority.class));
             List<MediaType> values = MediaTypes.createFrom(provider.getClass().getAnnotation(Consumes.class));
-            models.add(new MbrModel(provider, values, custom));
+            models.add(new MbrModel(provider, values, custom, priority));
         }
     }

     private static void addWriters(List<MbwModel> models, Set<MessageBodyWriter> writers, boolean custom) {
         for (MessageBodyWriter provider : writers) {
+            int priority = getPriority(provider.getClass().getAnnotation(Priority.class));
             List<MediaType> values = MediaTypes.createFrom(provider.getClass().getAnnotation(Produces.class));
-            models.add(new MbwModel(provider, values, custom));
+            models.add(new MbwModel(provider, values, custom, priority));
         }
     }

After building Jersey, I replaced the jersey-common jar in the Glassfish modules directory with my patched version. This let me annotate my MessageBodyReader/Writers with @Priority(500) and have them be selected by Jersey.

I felt that this was the cleanest way to let me prioritize my MessageBodyReader/Writers without affecting anything else in Glassfish that relies on Jersey.

Step 2

Inspired by this post I decided that using an Unmarshaller.Listener would be cleaner than my original path of implementing afterUnmarshal on each of my JAXB classes. I made an interface (CanBeValidated) and extended Unmarshaller.Listener as follows.

public final class ValidatingUnmarshallerListener
        extends Unmarshaller.Listener
{
    private final ValidationEventHandler validationEventHandler;

    public ValidatingUnmarshallerListener(
            ValidationEventHandler validationEventHandler)
    {
        this.validationEventHandler = validationEventHandler;
    }

    @Override
    public void afterUnmarshal(Object target, Object parent)
    {
        if (target == null
                || !(target instanceof CanBeValidated))
        {
            return;
        }

        CanBeValidated v = (CanBeValidated) target;
        Collection<Throwable> validationErrors = v.validate();

        for (Throwable t : validationErrors)
        {
            ValidationEvent event = new ValidationEventImpl(
                    ValidationEvent.ERROR,
                    t.getLocalizedMessage(),
                    null,
                    t);

            this.validationEventHandler.handleEvent(event);
        }
    }
}

Step 3

Finally, I extended org.glassfish.jersey.message.internal.AbstractRootElementJaxbProvider to override the readFrom method.

@Override
protected Object readFrom(
        Class<Object> type,
        MediaType mediaType,
        Unmarshaller u,
        InputStream entityStream)
        throws JAXBException
{
    final SAXSource source = getSAXSource(spf.provide(), entityStream);
    ValidationEventCollector eventCollector = new ValidationEventCollector();
    ValidatingUnmarshallerListener listener = new ValidatingUnmarshallerListener(eventCollector);
    u.setEventHandler(eventCollector);
    u.setListener(listener);

    final Object result;
    if (type.isAnnotationPresent(XmlRootElement.class))
    {
        result = u.unmarshal(source);
    }
    else
    {
        result = u.unmarshal(source, type).getValue();
    }

    if (eventCollector.hasEvents())
    {
        HttpError error = new HttpError(Response.Status.BAD_REQUEST);

        for (ValidationEvent event : eventCollector.getEvents())
        {
            error.addMessage(ValidationUtil.toString(event));
        }

        throw new WebApplicationException(error.toResponse());
    }

    return result;
}


来源:https://stackoverflow.com/questions/35514658/catch-exception-from-object-afterunmarshal-in-glassfish-4-1

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!