Catch exception from Object.afterUnmarshal in Glassfish 4.1 - java

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

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

Related

Get Jersey to work with Optional parameters

I'm trying to get Jersey to work with Optional parameters. I have a very simple web service:
#Path("helloworld")
public static class HelloWorldResource {
public static final String CLICHED_MESSAGE = "Hello World!";
#GET
#Produces("text/plain")
public String getHello(#QueryParam("maybe") Optional<String> maybe) {
return CLICHED_MESSAGE;
}
}
And a simple harness:
public static void main(String[] arg) throws IOException {
ResourceConfig config = new ResourceConfig(HelloWorldResource.class);
String baseUri = "http://localhost:8080/api/";
HttpServer server = GrizzlyHttpServerFactory
.createHttpServer(URI.create(baseUri), config, false);
server.start();
}
However I get the following error:
Exception in thread "main" org.glassfish.jersey.server.model.ModelValidationException: Validation of the application resource model has failed during application initialization.
[[FATAL] No injection source found for a parameter of type public java.lang.String com.mercuria.odyssey.server.GrizllyOptional$HelloWorldResource.getHello(java.util.Optional) at index 0.; source='ResourceMethod{httpMethod=GET, consumedTypes=[], producedTypes=[text/plain], suspended=false, suspendTimeout=0, suspendTimeoutUnit=MILLISECONDS, invocable=Invocable{handler=ClassBasedMethodHandler{handlerClass=class com.mercuria.odyssey.server.GrizllyOptional$HelloWorldResource, handlerConstructors=[org.glassfish.jersey.server.model.HandlerConstructor#a3d9978]}, definitionMethod=public java.lang.String com.mercuria.odyssey.server.GrizllyOptional$HelloWorldResource.getHello(java.util.Optional), parameters=[Parameter [type=class java.util.Optional, source=maybe, defaultValue=null]], responseType=class java.lang.String}, nameBindings=[]}']
at org.glassfish.jersey.server.ApplicationHandler.initialize(ApplicationHandler.java:555)
at org.glassfish.jersey.server.ApplicationHandler.access$500(ApplicationHandler.java:184)
at org.glassfish.jersey.server.ApplicationHandler$3.call(ApplicationHandler.java:350)
at org.glassfish.jersey.server.ApplicationHandler$3.call(ApplicationHandler.java:347)
at org.glassfish.jersey.internal.Errors.process(Errors.java:315)
at org.glassfish.jersey.internal.Errors.process(Errors.java:297)
at org.glassfish.jersey.internal.Errors.processWithException(Errors.java:255)
at org.glassfish.jersey.server.ApplicationHandler.<init>(ApplicationHandler.java:347)
at org.glassfish.jersey.server.ApplicationHandler.<init>(ApplicationHandler.java:311)
at org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpContainer.<init>(GrizzlyHttpContainer.java:337)
at org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory.createHttpServer(GrizzlyHttpServerFactory.java:140)
at com.mercuria.odyssey.server.GrizllyOptional.main(GrizllyOptional.java:33)
I presume I need to do something about so that Jersey knows how to handle Optional parameters, but I've no idea what!
So parameter types that are allowed as a #xxxParam, you need to meet one of these requirements:
Be a primitive type
Have a constructor that accepts a single String argument
Have a static method named valueOf() or fromString() that accepts a single String argument (see, for example, Integer.valueOf(String))
Have a registered implementation of ParamConverterProvider JAX-RS extension SPI that returns a ParamConverter instance capable of a "from string" conversion for the type.
Be List<T>, Set<T> or SortedSet<T>, where T satisfies 2, 3 or 4 above. The resulting collection is read-only.
So in this case of Optional, going down the list; it's not a primitive; it doesn't have a String constructor; it doesn't have a static valueOf() or fromString()
So basically, the only option left is to implement a ParamConverter/ParamConverterProvider pair for it. Dropwizard (a framework built on top of Jersey) has a good implementation for it. I will post it here in case the link ever goes dead
import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.jersey.internal.inject.Providers;
import org.glassfish.jersey.internal.util.ReflectionHelper;
import org.glassfish.jersey.internal.util.collection.ClassTypePair;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.ws.rs.ext.ParamConverter;
import javax.ws.rs.ext.ParamConverterProvider;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Optional;
import java.util.Set;
#Singleton
public class OptionalParamConverterProvider implements ParamConverterProvider {
private final ServiceLocator locator;
#Inject
public OptionalParamConverterProvider(final ServiceLocator locator) {
this.locator = locator;
}
/**
* {#inheritDoc}
*/
#Override
public <T> ParamConverter<T> getConverter(final Class<T> rawType, final Type genericType, final Annotation[] annotations) {
if (Optional.class.equals(rawType)) {
final List<ClassTypePair> ctps = ReflectionHelper.getTypeArgumentAndClass(genericType);
final ClassTypePair ctp = (ctps.size() == 1) ? ctps.get(0) : null;
if (ctp == null || ctp.rawClass() == String.class) {
return new ParamConverter<T>() {
#Override
public T fromString(final String value) {
return rawType.cast(Optional.ofNullable(value));
}
#Override
public String toString(final T value) {
return value.toString();
}
};
}
final Set<ParamConverterProvider> converterProviders = Providers.getProviders(locator, ParamConverterProvider.class);
for (ParamConverterProvider provider : converterProviders) {
final ParamConverter<?> converter = provider.getConverter(ctp.rawClass(), ctp.type(), annotations);
if (converter != null) {
return new ParamConverter<T>() {
#Override
public T fromString(final String value) {
return rawType.cast(Optional.ofNullable(value).map(s -> converter.fromString(value)));
}
#Override
public String toString(final T value) {
return value.toString();
}
};
}
}
}
return null;
}
}
Note, if you are using a Jersey version 2.26+, instead of injecting ServiceLocator you will use InjectionManager instead. Also the argument that accepts a locator, you will need to change the the manager.
With this class, you just need to register it with your Jersey application.
This is a partial solution, but it seems like DropWizard has a feature specifically to support this:
https://github.com/dropwizard/dropwizard/blob/master/dropwizard-jersey/src/main/java/io/dropwizard/jersey/optional/OptionalParamBinder.java
So you can simply use their code:
import io.dropwizard.jersey.optional.*;
class DirtyBinder extends AbstractBinder {
#Override
protected void configure() {
bind(OptionalParamConverterProvider.class).to(ParamConverterProvider.class).in(Singleton.class);
bind(OptionalDoubleParamConverterProvider.class).to(ParamConverterProvider.class).in(Singleton.class);
bind(OptionalIntParamConverterProvider.class).to(ParamConverterProvider.class).in(Singleton.class);
bind(OptionalLongParamConverterProvider.class).to(ParamConverterProvider.class).in(Singleton.class);
}
}
then just add:
config.register(new DirtyBinder());

JAXB unmarshaller's handleEvent not being called

I'm trying to setup a simple Jersey JAX-RS application using MOXy for JAXB JSON support and I wanted to customise the unmarshaller. I did the following:
#Provider
public class CustomProvider extends ConfigurableMoxyJsonProvider{
#Override
protected void preReadFrom(..., Unmarshaller unmarshaller) throws JAXBException{
super.preReadFrom(...);
System.out.println("preReadFrom entered");
unmarshaller.setEventHandler(new ValidationEventHandler(){
#Override
public boolean handleEvent(ValidationEvent event){
System.out.println("Entered handleEvent");
return false;
}
});
}
}
I wrote an override for preReadFrom and set an event handler on the unmarshaller. When I pass an invalid JSON body, print statement in preReadFrom executes but not the one in event handler. So the provider is registered properly but the event handler is not being called.
What might cause this issue?
What I want to achieve is when a user passes extraneous attributes in the JSON body, I want to throw an error (By default, these attributes are ignored). Searching on various websites, adding an event handler is the only way to do that. It would be great if I can achieve this in a different way too.
I assume System.out.println("preReadFrom entered"); is getting called, and your CustomProvider is actually registered and used. Because in Weblogic e.g even if you register a different provider, still default ConfigurableMoxyJsonProvider is getting called unless you disable Moxy.
If first assumption is correct, then for sure you will be getting public boolean handleEvent(ValidationEvent event) called for validations like if your Pojo, attribute is of numeric and you are passing String in json.
For UnmappedElements, I have seen that Moxy, "ignores" the warning for Json. What that means is if your pojo is like below:
public class Emp {
public Emp() {
super();
}
private int id ;
public void setId(int id) {
this.id = id;
}
public int getId() {
return id;
}
}
And your Json is like: {"id" : 11, "name1" : 111} then your handleEvent won't be called. Reason being the Moxy uses org.eclipse.persistence.internal.oxm.record.UnmarshalRecordI‌mpl.startUnmappedEle‌​ment() that has a check to see if the media type is xml prior to issuing a warning to the event handler.
So how to solve it for throw. I may not know the best of answer but here is my solution :
In your preReadFrom method add your custom UnmappedHandlerClass like below
protected void preReadFrom(Class<Object> type, Type genericType, Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, String> httpHeaders,
Unmarshaller unmarshaller) throws JAXBException {
System.out.println("preReadFrom entered");
super.preReadFrom(type, genericType, annotations, MediaType.WILDCARD_TYPE, httpHeaders, unmarshaller);
System.out.println("preReadFrom returned from super");
//new code
if(unmarshaller instanceof org.eclipse.persistence.jaxb.JAXBUnmarshaller) {
org.eclipse.persistence.jaxb.JAXBUnmarshaller moxyUn = ( org.eclipse.persistence.jaxb.JAXBUnmarshaller)unmarshaller;
moxyUn.getXMLUnmarshaller().setUnmappedContentHandlerClass(CustomUnmapped.class);
}
//your existing code
unmarshaller.setEventHandler(new ValidationEventHandler() {
#Override
public boolean handleEvent(ValidationEvent event) {
System.out.println("Entered handleEvent");
return false;
}
});
}
And you can use Customunmapped class like this:
class CustomUnmapped extends org.eclipse.persistence.internal.oxm.unmapped.DefaultUnmappedContentHandler {
#Override
public void startElement(String p1, String p2, String p3, Attributes p4) throws SAXException {
throw new SAXNotRecognizedException(p1);
}
}
That shall work. But before trying 3, just make sure that your a) CustomProvider is actually being called by adding a breakpoint, or System.out.. statement. and b) Your handleEvent is called for generic errors like passing a string x in json, for number field.

Using Jackson to dynamically serialize an entity as either its ID or its full representation at runtime

We're developing a RESTful API using Java EE 7 (RESTEasy / Hibernate / Jackson).
We want the API to serialize all child entities using their IDs, by default. We're doing this mostly to maintain consistency with our deserialization strategy, where we insist on receiving an ID.
However, we also want our users to be able to choose to get an expanded view of any of our child entities, either through a custom endpoint or a query parameter (undecided). For example:
// http://localhost:8080/rest/operator/1
// =====================================
{
"operatorId": 1,
"organization": 34,
"endUser": 23
}
// http://localhost:8080/rest/operator/1?expand=organization
// =====================================
{
"operatorId": 1,
"organization": {
"organizationId": 34,
"organizationName": "name"
},
"endUser": 23
}
// http://localhost:8080/rest/operator/1?expand=enduser
// =====================================
{
"operatorId": 1,
"organization": 34,
"endUser": {
"endUserId": 23,
"endUserName": "other name"
}
}
// http://localhost:8080/rest/operator/1?expand=organization,enduser
// =====================================
{
"operatorId": 1,
"organization": {
"organizationId": 34,
"organizationName": "name"
},
"endUser": {
"endUserId": 23,
"endUserName": "other name"
}
}
Is there a way to dynamically change the behavior of Jackson to determine whether a specified AbstractEntity field is serialized in full form or as its ID? How might it be done?
Additional Info
We know of a few ways to serialize our child entities using their IDs, including:
public class Operator extends AbstractEntity {
...
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="organizationId")
#JsonIdentityReference(alwaysAsId=true)
public getOrganization() { ... }
...
}
and
public class Operator extends AbstractEntity {
...
#JsonSerialize(using=AbstractEntityIdSerializer.class)
public getOrganization() { ... }
...
}
where AbstractEntityIdSerializer serializes the entity using its ID.
The problem is that we don't know of a way for the user to override that default behavior and revert to standard Jackson object serialization. Ideally they'd also be able to choose which child properties to serialize in full form.
It would be awesome to dynamically toggle the alwaysAsId argument of #JsonIdentityReference for any property at runtime, if that's possible, or make the equivalent change to ObjectMapper/ObjectWriter.
Update: Working(?) Solution
We haven't had a chance to fully test this yet, but I've been working on a solution that leverages overriding Jackson's AnnotationIntrospector class. It seems to be working as intended.
public class CustomAnnotationIntrospector extends JacksonAnnotationIntrospector {
private final Set<String> expandFieldNames_;
public CustomAnnotationIntrospector(Set<String> expandFieldNames) {
expandFieldNames_ = expandFieldNames;
}
#Override
public ObjectIdInfo findObjectReferenceInfo(Annotated ann, ObjectIdInfo objectIdInfo) {
JsonIdentityReference ref = _findAnnotation(ann, JsonIdentityReference.class);
if (ref != null) {
for (String expandFieldName : expandFieldNames_) {
String expandFieldGetterName = "get" + expandFieldName;
String propertyName = ann.getName();
boolean fieldNameMatches = expandFieldName.equalsIgnoreCase(propertyName);
boolean fieldGetterNameMatches = expandFieldGetterName.equalsIgnoreCase(propertyName);
if (fieldNameMatches || fieldGetterNameMatches) {
return objectIdInfo.withAlwaysAsId(false);
}
}
objectIdInfo = objectIdInfo.withAlwaysAsId(ref.alwaysAsId());
}
return objectIdInfo;
}
}
At serialization time, we copy our ObjectMapper (so the AnnotationIntrospector runs again) and apply CustomAnnotationIntrospector as follows:
#Context
private HttpRequest httpRequest_;
#Override
writeTo(...) {
// Get our application's ObjectMapper.
ContextResolver<ObjectMapper> objectMapperResolver = provider_.getContextResolver(ObjectMapper.class,
MediaType.WILDCARD_TYPE);
ObjectMapper objectMapper = objectMapperResolver.getContext(Object.class);
// Get Set of fields to be expanded (pre-parsed).
Set<String> fieldNames = (Set<String>)httpRequest_.getAttribute("ExpandFields");
if (!fieldNames.isEmpty()) {
// Pass expand fields to AnnotationIntrospector.
AnnotationIntrospector expansionAnnotationIntrospector = new CustomAnnotationIntrospector(fieldNames);
// Replace ObjectMapper with copy of ObjectMapper and apply custom AnnotationIntrospector.
objectMapper = objectMapper.copy();
objectMapper.setAnnotationIntrospector(expansionAnnotationIntrospector);
}
ObjectWriter objectWriter = objectMapper.writer();
objectWriter.writeValue(...);
}
Any glaring flaws in this approach? It seems relatively straightforward and is fully dynamic.
The answer is Jackson's mixin feature:
You create a simple Java class that has the exact same method signature as the anotated method of the entity. You annotate that method with the modified value. the body of the method is insignificant (it would not be called):
public class OperatorExpanded {
...
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="organizationId")
#JsonIdentityReference(alwaysAsId=false)
public Organization getOrganization() { return null; }
...
}
you tie the mixin to the entity-to-be-serialized using Jackson's module system: this can be decided at run time
ObjectMapper mapper = new ObjectMapper();
if ("organization".equals(request.getParameter("exapnd")) {
SimpleModule simpleModule = new SimpleModule();
simpleModule.setMixInAnnotation(Operator.class, OperatorExpanded.class);
mapper.registerModule(simpleModule);
}
now, the mapper will take the annotations from the mixin, but invoke the method of the entity.
If you are looking for a generalized solution that needs to be extended to all of your resources you may try following approach. I tried below solution using Jersey and Jackson. It should also work with RestEasy.
Basically, you need to write a custom jackson provider which set a special serializer for an expand field. Also, you need to pass the expand fields to the serializer so that you can decide how to do the serialization for expand fields.
#Singleton
public class ExpandFieldJacksonProvider extends JacksonJaxbJsonProvider {
#Inject
private Provider<ContainerRequestContext> provider;
#Override
protected JsonEndpointConfig _configForWriting(final ObjectMapper mapper, final Annotation[] annotations, final Class<?> defaultView) {
final AnnotationIntrospector customIntrospector = mapper.getSerializationConfig().getAnnotationIntrospector();
// Set the custom (user) introspector to be the primary one.
final ObjectMapper filteringMapper = mapper.setAnnotationIntrospector(AnnotationIntrospector.pair(customIntrospector, new JacksonAnnotationIntrospector() {
#Override
public Object findSerializer(Annotated a) {
// All expand fields should be annotated with '#ExpandField'.
ExpandField expField = a.getAnnotation(ExpandField.class);
if (expField != null) {
// Use a custom serializer for expand field
return new ExpandSerializer(expField.fieldName(), expField.idProperty());
}
return super.findSerializer(a);
}
}));
return super._configForWriting(filteringMapper, annotations, defaultView);
}
#Override
public void writeTo(final Object value, final Class<?> type, final Type genericType, final Annotation[] annotations, final MediaType mediaType, final MultivaluedMap<String, Object> httpHeaders,
final OutputStream entityStream) throws IOException {
// Set the expand fields to java's ThreadLocal so that it can be accessed in 'ExpandSerializer' class.
ExpandFieldThreadLocal.set(provider.get().getUriInfo().getQueryParameters().get("expand"));
super.writeTo(value, type, genericType, annotations, mediaType, httpHeaders, entityStream);
// Once the serialization is done, clear ThreadLocal
ExpandFieldThreadLocal.remove();
}
ExpandField.java
#Retention(RUNTIME)
public #interface ExpandField {
// name of expand field
String fieldName();
// name of Id property in expand field. For eg: oraganisationId
String idProperty();
}
ExpandFieldThreadLocal.java
public class ExpandFieldThreadLocal {
private static final ThreadLocal<List<String>> _threadLocal = new ThreadLocal<>();
public static List<String> get() {
return _threadLocal.get();
}
public static void set(List<String> expandFields) {
_threadLocal.set(expandFields);
}
public static void remove() {
_threadLocal.remove();
}
}
ExpandFieldSerializer.java
public static class ExpandSerializer extends JsonSerializer<Object> {
private String fieldName;
private String idProperty;
public ExpandSerializer(String fieldName,String idProperty) {
this.fieldName = fieldName;
this.idProperty = idProperty;
}
#Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException {
// Get expand fields in current request which is set in custom jackson provider.
List<String> expandFields = ExpandFieldThreadLocal.get();
if (expandFields == null || !expandFields.contains(fieldName)) {
try {
// If 'expand' is not present in query param OR if the 'expand' field does not contain this field, write only id.
serializers.defaultSerializeValue(value.getClass().getMethod("get"+StringUtils.capitalize(idProperty)).invoke(value),gen);
} catch (Exception e) {
//Handle Exception here
}
} else {
serializers.defaultSerializeValue(value, gen);
}
}
}
Operator.java
public class Operator extends AbstractEntity {
...
#ExpandField(fieldName = "organization",idProperty="organizationId")
private organization;
...
}
The final step is to register the new ExpandFieldJacksonProvider. In Jersey, we register it through an instance of javax.ws.rs.core.Application as shown below. I hope there is something similar in RestEasy. By default, most of the JAX-RS libraries tend to load default JacksonJaxbJsonProvider through auto-discovery. You have to make sure auto-discovery is disabled for Jackson and new ExpandFieldJacksonProvider is registered.
public class JaxRsApplication extends Application{
#Override
public Set<Class<?>> getClasses() {
Set<Class<?>> clazzes=new HashSet<>();
clazzes.add(ExpandFieldJacksonProvider.class);
return clazzes;
}
}

How to check argument value formatting before controller method?

In my controller I have a method such as bellow:
public QueryResult<TrsAccount> listExclude(String codeAccount, String searchFilter, String order, int pageNumber,
int pageSize){}
But before executing this method I have to chech if:
Assert.TRUE(codeAccount.matches("^[0-9]{1,20}$"));
Because this is very frequent in my application and it is not only this case, I want a general approach to check the argument format. The way I'm using now is the use of AOP, in which:
#Aspect
public class HijackBeforeMethod {
#Pointcut("within(#org.springframework.stereotype.Controller *)")
public void controllerBean() {
}
#Pointcut("execution(* *(..))")
public void methodPointcut() {
}
#Before(value = "controllerBean() && methodPointcut()", argNames = "joinPoint")
public void before(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Object[] args = joinPoint.getArgs();
String[] paramNames = signature.getParameterNames();
for (int count = 0; count < paramNames.length; count++) {
String tempParam = paramNames[count];
Object tempValue = args[count];
if (tempParam.toLowerCase().equalsIgnoreCase("codeAccount") && Assert.isNotNull(tempValue)
&& Assert.isNotEmpty((String) tempValue)) {
Assert.TRUE(((String) tempValue).matches("^[0-9]{1,20}$"));
}
}
}
}
As you can see, this is very rudimentary and error prone code snippet. Is there any better solutions??
Using AOP in Controllers is not really recommended. A better approach would be to use JSR 303 / JSR 349 Bean Validation, but that would probably require wrapping the string in a value object, which is then annotated accordingly.
If you insist on solving this with AOP, you'll probably need a ControllerAdvice
Just like #Sean Patrick Floyd said, using Bean Validation is more advisable.
Firstly, define a class which extends from org.springframework.validation.Validator like:
#Component
public class CodeAccountValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return String.class.equals(clazz);
}
#Override
public void validate(Object target, Errors errors) {
if (Assert.isNotNull(target) && Assert.isNotEmpty((String) target)) {
Assert.TRUE(((String) target).matches("^[0-9]{1,20}$"));
}
}
}
Then add #Validated annotation to your controller like:
public QueryResult<TrsAccount> listExclude(
#Validated(CodeAccountValidator.class)String codeAccount,
String searchFilter,
String order, int pageNumber,
int pageSize) {
... ...
}
Trying to solve this with AOP is something you shouldn't do. Instead use an object to bind your properties and validate that object.
public class QueryCriteria {
private String codeAccount;
private String searchFilter;
private int pageNumber;
private int pageSize;
private String order;
// Getters / Setters.
}
Then modify your controller method
public QueryResult<TrsAccount> listExclude(#Valid QueryCriteria criteria, BIndingResult result) { ... }
Then either use a Spring Validator which validates what you need .
public QueryCriteriaValidator implements Validator {
private final Pattern ACCOUNT_EXPR = Pattern.compile("^[0-9]{1,20}$");
public boolean supports(Class<?> clazz) {
return QueryCriteria.isAssignable(clazz);
}
public void validate(Object target, Errors errors) {
final QueryCriteria criteria = (QueryCriteria) target;
if (!ACCOUNT_EXPR.matcher(criteria.getCodeAccount()).matches()) {
errors.rejectValue("codeAccount", "invalid.format");
}
}
}
In an #InitBinder in your controller method register this validator
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.setValidator(new QueryCriteriaValidator());
}
When using JSR-303 you don't need this and you could simply annotate the codeAccount field with the #Pattern annotation.
#Pattern(regexp="^[0-9]{1,20}$")
private String codeAccount;
The validation works nicely together with Spring MVC and error reporting using I18N. So instead of trying to hack around it with exceptions work with the framework.
I suggest a read of the validation section and binding section of the Spring Reference guide.

Is it possible to unregister jersey component?

Dropwizard registers jersey component A. I want to replace this component with my custom version. Is it possible?
from the docs:
https://eclipse-ee4j.github.io/jersey.github.io/documentation/latest/message-body-workers.html#providers-selection
This means:
If you have a custom provider, your custom provider will be used.
By example:
Say I have a user class:
public class User {
#JsonProperty("name")
private String name;
#JsonProperty("password")
private String password;
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
We have a resource returning the User:
#Path("/hello/world2")
#Produces(MediaType.APPLICATION_JSON)
public class MsgBodyWriterTest {
#GET
#Path("/v1")
#Produces(MediaType.APPLICATION_JSON)
public User test() {
User u = new User();
u.setName("Test");
return u;
}
}
And we have a MessageBodyWriter that deals with this User object:
#Provider
public class UserMsgBodyWriter implements MessageBodyWriter<User> {
#Override
public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return type == User.class;
}
#Override
public long getSize(User t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
// TODO Auto-generated method stub
return 0;
}
#Override
public void writeTo(User t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream)
throws IOException, WebApplicationException {
System.out.println("Use custom Provider");
ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(entityStream, t);
}
}
We then hook things together in the main by registering the resource and the provider:
public class Starter extends Application<Configuration> {
#Override
public void run(Configuration configuration, Environment environment) throws Exception {
environment.jersey().register(MsgBodyWriterTest.class);
environment.jersey().register(UserMsgBodyWriter.class);
}
public static void main(String[] args) throws Exception {
new Starter().run("server", "/Users/artur/dev/repo/dw-test/src/main/resources/configuration.yaml");
}
}
When started, it will publish the jersey resource, and we can then access it:
arturk:~ artur$ curl -XGET "localhost:9085/hello/world2/v1"
This will then log:
Use custom Provider
0:0:0:0:0:0:0:1 - - [02/Jun/2016:09:21:31 +0000] "GET /hello/world2/v1 HTTP/1.1" 200 31 "-" "curl/7.43.0" 30
Note the output: "Use custom Provider". This is simply to demonstrate that the right provider has been called.
I hope this is what your looking for.
In short, the default provider that is hooked in comes from Jaxb. it deals with all json types and enables default beans serialising. Without it, your json will not know how to be parsed. You don't want to get rid of it, but rather add to it.
By creating your own provider, the selection will take care of choosing the right thing for you. By default, jersey will sort custom providers before default providers. So as long as your isWriteable() method is implemented correctly, your provider will be chosen to do the work.
EDIT:
to answer your actual question. If you MUST get rid of the DW registered provider, there is a way for that too. You will have to overwrite the default server factory. Look at:
AbstractServerFactory#createAppServlet. This is where the default providers are hooked in. Again though, if you simply register your own, the default will never be used and you'll save yourself the work of creating a server factory (which is also fairly easy and straight forward in case you want to).
Cheers,
Artur

Categories

Resources