从Glassfish 4.1中的Object.afterUnmarshal捕获异常

时间:2016-02-19 20:44:12

标签: java jaxb glassfish glassfish-4.1

我目前正在JDK 1.8.0-40上运行Glassfish 4.1。我使用的是javaee-web-api-7.0和jersey-media-moxy-2.22。我正在从/向JAXB注释的java对象编组/解组JSON和XML。

我设置了ContextResolver<Unmarshaller>以提供Unmarshaller自定义ValidationEventHandler,它将从属性设置器中收集异常并抛出BadRequestException以及聚合验证错误。这部分正在运作。

但是,我在检查未设置属性的对象上也有beforeMarshalafterUnmarshal方法(必须设置属性的规则因属性值而异,导致我排除对模式的验证)。如果afterUnmarshal方法引发异常,则ValidationEventHandler不会看到异常,而是ExceptionMapper冒充。

有没有办法从单个对象的beforeMarshalafterUnmarshal方法中捕获异常,并将它们转到ValidationEventHandler

我认为可以实现MessageBodyReader来捕获异常,使用Unmarshaller.getEventHandler,手动调用ValidationEventHandler.handleEvent,然后抛出BadRequestException { {1}}返回false [编辑:如果从handleEvent抛出异常,则无法继续解组,因此唯一可能的办法是终止处理]。但这会遗漏事件位置信息,我并不特别想要实现我自己的Unmarshaller.unmarshal。我希望有一种更简单的内置方式来做到这一点,我无法发现。

提前感谢您的帮助。

1 个答案:

答案 0 :(得分:2)

经过一番挖掘和头痛之后,我最终开发了一个解决方案。

步骤1(可选)

编辑:您无需修补Jersey以实现此行为。我无法在任何地方找到它,但org.glassfish.jersey.internal.inject.Custom注释将提供者标记为自定义(而@Provider单独是不够的)。如果您的提供者是CommonProperties.MOXY_JSON_FEATURE_DISABLE,则还必须将application/json设置为true来禁用MOXy。因此,您只需要这样做:

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

<击> 这是我最不喜欢的解决方案的一部分,但也让我免于一堆代码重复。 Jersey的选择MessageBodyReader/Writers的排序算法无法确定应用程序提供程序的优先级(我可以找到)。我想扩展AbstractRootElementJaxbProvider以重用其功能,但这意味着我无法使其比Jersey提供的XmlRootElementJaxbProvider更具体。因为默认情况下,Jersey仅对媒体类型距离,对象类型距离以及提供者是否注册为自定义提供者(通过@Provider注释检测到的提供者未注册为自定义提供者)进行排序,因此Jersey实施将始终选择而不是我的MessageBodyReader/Writer

<击>

我从Github检查了Jersey 2.10.4来源并修补MessageBodyFactory以使用@Priority注释作为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));
         }
     }

<击> 在构建Jersey之后,我用我的修补版本替换了Glassfish模块目录中的jersey-common jar。这让我用MessageBodyReader/Writers注释我的@Priority(500)并让泽西选择它们。

我觉得这是让我优先考虑MessageBodyReader/Writers而不影响依赖于泽西岛的Glassfish其他任何东西的最简洁方法。

第2步

this post的启发我决定使用Unmarshaller.Listener比我在每个JAXB类上实现afterUnmarshal的原始路径更清晰。我创建了一个接口(CanBeValidated)和扩展Unmarshaller.Listener,如下所示。

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);
        }
    }
}

第3步

最后,我扩展了org.glassfish.jersey.message.internal.AbstractRootElementJaxbProvider以覆盖readFrom方法。

@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;
}