2016-02-19 2 views
0

Я в настоящее время работает Glassfish 4.1 on JDK 1.8.0-40. Я использую javaee-web-api-7.0 и jersey-media-moxy-2.22. Я собираю/развязываю JSON и XML из/в JAXB-аннотированные java-объекты.Исключить из Object.afterUnmarshal в Glassfish 4.1

У меня есть ContextResolver<Unmarshaller> создать, чтобы обеспечить Unmarshaller с обычаем ValidationEventHandler, который будет собирать исключения из инкубационных свойств и кинуть BadRequestException с совокупными ошибками валидации. Эта часть работает.

Однако, я также имею beforeMarshal и afterUnmarshal методы на объектах, которые проверяют на неустановленные свойства (правила, свойства которого должны быть установлены варьироваться от значений свойств, что приводит меня, чтобы исключить проверку в отношении схемы). Если исключение выбрано из метода afterUnmarshal, оно не видно ValidationEventHandler и вместо этого пузырится до ExceptionMapper.

Есть ли способ, чтобы поймать исключения из beforeMarshal и afterUnmarshal методов на отдельных объектах и ​​получить их к ValidationEventHandler?

Я думаю, что можно было бы реализовать MessageBodyReader ловить исключения, используйте Unmarshaller.getEventHandler, вручную вызвать ValidationEventHandler.handleEvent, и бросить BadRequestException если handleEvent возвращает ложь [править: если исключение из Unmarshaller.unmarshal, это Wouldn нельзя продолжать развязывание, поэтому единственное возможное средство - прекратить обработку]. Но это будет отсутствовать информация о местоположении события, и я не особо причудливо реализую свой собственный MessageBodyReader. Я надеюсь, что есть более простой встроенный способ сделать это, что я не смог обнаружить.

Заранее благодарим за любую помощь.

ответ

2

После кучи копания и головных болей, я закончил разработку решения.

Шаг 1 (по желанию)

EDIT: Вам не придется латать Джерси добиться такого поведения. Я не могу найти его документально нигде, но аннотация org.glassfish.jersey.internal.inject.Custom отмечает, что поставщик является обычным (тогда как @Provider недостаточно). Вы также должны отключить MOXy, установив CommonProperties.MOXY_JSON_FEATURE_DISABLE в true, если ваш провайдер находится в состоянии application/json. Таким образом, вы только должны сделать:

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

Это моя нелюбимой часть моего решения, но и спасли меня от связки дублирования кода. У алгоритма сортировки Джерси для выбора MessageBodyReader/Writers нет способа расставить приоритеты поставщикам приложений (которые я мог найти). Я хотел продлить AbstractRootElementJaxbProvider, чтобы повторно использовать его функциональность, но это означало, что я не мог сделать его более конкретным, чем предоставленный Джерси XmlRootElementJaxbProvider. Поскольку по умолчанию Джерси сортирует только расстояние между медиаданными, расстояние по типу объекта и регистрируется ли поставщик как пользовательский поставщик (поставщики, обнаруженные через аннотацию @Provider, не зарегистрированы как заказные поставщики), реализация Джерси всегда выбиралась вместо мой 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 модули каталог с моей исправленной версии. Это позволит мне аннотировать мой MessageBodyReader/Writers с @Priority(500) и выбрать их на Джерси.

Я чувствовал, что это был самый чистый способ позволить мне расставить приоритеты для моего MessageBodyReader/Writers, не затрагивая ничего другого в Glassfish, который полагается на Джерси.

Шаг 2

Вдохновленный this post я решил, что использование Unmarshaller.Listener будет чище, чем мой оригинальный путь реализации afterUnmarshal на каждом из моих классов JAXB. Я сделал интерфейс (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' в свой конструктор, который мне нужно получить, чтобы передать конструктору супертипа, но я получаю это исключение:' org.jboss.weld.exceptions.CreationException: WELD- 001530: Не удается создать экземпляр класса [...]. ' –

+0

Я столкнулся с той же проблемой. Я работал вокруг этого, лениво загружая класс. Я написал обертку 'public abstract class LazyJaxbProvider реализует MessageBodyReader , MessageBodyWriter ' и ввел поставщиков в оболочку '@Context private Providers ps'. Затем я написал метод 'getMessageBodyReaderWriter', который возвращает новый экземпляр моего подкласса' AbstractRootElementJaxbProvider', предоставляя 'this.ps' в качестве аргумента конструктора. –

+0

В зависимости от того, что вы делаете, вы также можете посмотреть на [API проверки боба] (https://jersey.java.net/documentation/latest/bean-validation.html). Я не пробовал использовать его вообще, поэтому я даже не уверен, что версия Джерси, упакованная с Glassfish 4.1, поддерживает проверку валиков. Я чувствовал, что имеет смысл иметь объекты, которые выполняют свою собственную проверку, вместо того, чтобы писать кучу шаблонных пользовательских аннотаций, которые в любом случае будут тесно связаны с целевым классом. –

Смежные вопросы