问题
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 [edit: if an exception is thrown from handleEvent
returns falseUnmarshaller.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