2016-02-19 51 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驗證。我覺得讓對象執行自己的驗證更有意義,而不必編寫一堆樣板定製註釋,無論如何都要與目標類緊密耦合。 –