2016-02-19 86 views
0

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

我有一个ContextResolver<Unmarshaller>成立,提供与定制ValidationEventHandler将收集从属性setter异常并抛出BadRequestException与骨料验证错误的Unmarshaller。这部分工作。

但是,我也有beforeMarshalafterUnmarshal方法检查未设置的属性(必须设置属性的规则取决于属性的值,导致我排除对模式的验证)。如果从afterUnmarshal方法中抛出异常,则ValidationEventHandler不会看到异常,而是可能会出现ExceptionMapper

有什么办法捉对单个对象从beforeMarshalafterUnmarshal方法例外,并让它们在ValidationEventHandler

我认为这是可能实现MessageBodyReader赶上例外,使用Unmarshaller.getEventHandler,手动调用ValidationEventHandler.handleEvent,并抛出一个BadRequestException 如果 handleEvent返回false [编辑:如果异常是从Unmarshaller.unmarshal抛出,它止跌不可能继续解组,所以唯一可能的方法是终止处理]。但是这将会丢失活动的位置信息,我并不特别喜欢实施我自己的MessageBodyReader。我希望有一个更简单的内置方式来做到这一点,我一直没有发现。

在此先感谢您的帮助。

回答

2

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

步骤1(可选)

编辑:您不必修补泽西实现这一行为。我无法在任何地方找到它,但org.glassfish.jersey.internal.inject.Custom注释将提供商标记为自定义(但仅有@Provider不足)。如果您的供应商为application/json,您还必须将CommonProperties.MOXY_JSON_FEATURE_DISABLE设置为true来禁用MOXy。因此,你只需要做:

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

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

我检查了泽西岛2.10。4源来自Github并且修补 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)); 
     } 
    } 

建设球衣后,我取代了Glassfish的球衣常见罐子modules目录与我的补丁版本。这让我用 @Priority(500)注释我的 MessageBodyReader/Writers,并让他们被泽西选中。

我觉得这是最简洁的方法,让我优先考虑我的 MessageBodyReader/Writers而不会影响依赖于Jersey的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; 
} 
+0

您能否显示一下org.glassfish.jersey.message.internal.AbstractRootElementJaxbProvider的子类的完整代码?我无法工作;我尝试在我的构造函数中注入带'@ Context'的'Providers',我必须把它传递给超类型构造函数,但我一直得到这个异常:'org.jboss.weld.exceptions.CreationException:WELD- 001530:无法生成类[...]的实例。' –

+0

我遇到了同样的问题。我通过懒惰地加载类来解决它。我写了一个封装器'公共抽象类LazyJaxbProvider implements MessageBodyReader ,MessageBodyWriter '并将提供者注入到封装器@Context private Providers ps'中。然后我写了一个方法'getMessageBodyReaderWriter',它返回'AbstractRootElementJaxbProvider'的子类的新实例,提供'this.ps'作为构造函数参数。 –

+0

根据你在做什么,你可能也想看看[bean validation API](https://jersey.java.net/documentation/latest/bean-validation.html)。我还没有尝试过使用它,所以我甚至不确定与Glassfish 4.1一起打包的Jersey版本是否支持bean验证。我觉得让对象执行自己的验证更有意义,而不必编写一堆样板定制注释,无论如何都要与目标类紧密耦合。 –