Skip to content

Commit

Permalink
Move validation logic from Jackson to Jersey
Browse files Browse the repository at this point in the history
  • Loading branch information
nickbabcock committed Nov 13, 2015
1 parent 597ad3a commit 32ab616
Show file tree
Hide file tree
Showing 26 changed files with 734 additions and 583 deletions.
Expand Up @@ -3,8 +3,16 @@
import io.dropwizard.jersey.validation.ConstraintMessage; import io.dropwizard.jersey.validation.ConstraintMessage;
import io.dropwizard.jersey.validation.Validators; import io.dropwizard.jersey.validation.Validators;
import io.dropwizard.logging.BootstrapLogging; import io.dropwizard.logging.BootstrapLogging;
import org.glassfish.jersey.process.Inflector;
import org.glassfish.jersey.server.model.Invocable;
import org.hibernate.validator.constraints.NotEmpty; import org.hibernate.validator.constraints.NotEmpty;
import org.openjdk.jmh.annotations.*; import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.options.OptionsBuilder; import org.openjdk.jmh.runner.options.OptionsBuilder;


Expand All @@ -13,6 +21,7 @@
import javax.validation.Validator; import javax.validation.Validator;
import javax.validation.executable.ExecutableValidator; import javax.validation.executable.ExecutableValidator;
import javax.ws.rs.HeaderParam; import javax.ws.rs.HeaderParam;
import javax.ws.rs.core.Request;
import java.util.Set; import java.util.Set;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;


Expand Down Expand Up @@ -45,6 +54,13 @@ public static class Foo {
private ConstraintViolation<ConstraintViolationBenchmark.Resource> paramViolation; private ConstraintViolation<ConstraintViolationBenchmark.Resource> paramViolation;
private ConstraintViolation<ConstraintViolationBenchmark.Resource> objViolation; private ConstraintViolation<ConstraintViolationBenchmark.Resource> objViolation;


final Invocable invocable = Invocable.create(new Inflector<Request, Object>() {
@Override
public Object apply(Request request) {
return null;
}
});

@Setup @Setup
public void prepare() { public void prepare() {
final Validator validator = Validators.newValidator(); final Validator validator = Validators.newValidator();
Expand All @@ -69,12 +85,12 @@ public void prepare() {


@Benchmark @Benchmark
public String paramViolation() { public String paramViolation() {
return ConstraintMessage.getMessage(paramViolation); return ConstraintMessage.getMessage(paramViolation, invocable);
} }


@Benchmark @Benchmark
public String objViolation() { public String objViolation() {
return ConstraintMessage.getMessage(objViolation); return ConstraintMessage.getMessage(objViolation, invocable);
} }


public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
Expand Down
Expand Up @@ -10,6 +10,7 @@
import io.dropwizard.jersey.gzip.GZipDecoder; import io.dropwizard.jersey.gzip.GZipDecoder;
import io.dropwizard.jersey.jackson.JacksonMessageBodyProvider; import io.dropwizard.jersey.jackson.JacksonMessageBodyProvider;
import io.dropwizard.jersey.validation.Validators; import io.dropwizard.jersey.validation.Validators;
import io.dropwizard.jersey.validation.HibernateValidationFeature;
import io.dropwizard.lifecycle.Managed; import io.dropwizard.lifecycle.Managed;
import io.dropwizard.setup.Environment; import io.dropwizard.setup.Environment;
import org.apache.http.client.CredentialsProvider; import org.apache.http.client.CredentialsProvider;
Expand Down Expand Up @@ -354,7 +355,8 @@ private Configuration buildConfig(final String name, final ExecutorService threa
config.register(provider); config.register(provider);
} }


config.register(new JacksonMessageBodyProvider(objectMapper, validator)); config.register(new JacksonMessageBodyProvider(objectMapper));
config.register(HibernateValidationFeature.class);


for (Map.Entry<String, Object> property : this.properties.entrySet()) { for (Map.Entry<String, Object> property : this.properties.entrySet()) {
config.property(property.getKey(), property.getValue()); config.property(property.getKey(), property.getValue());
Expand Down
Expand Up @@ -20,6 +20,7 @@
import io.dropwizard.jersey.validation.ConstraintViolationExceptionMapper; import io.dropwizard.jersey.validation.ConstraintViolationExceptionMapper;
import io.dropwizard.jersey.jackson.JacksonMessageBodyProvider; import io.dropwizard.jersey.jackson.JacksonMessageBodyProvider;
import io.dropwizard.jersey.setup.JerseyEnvironment; import io.dropwizard.jersey.setup.JerseyEnvironment;
import io.dropwizard.jersey.validation.JerseyViolationExceptionMapper;
import io.dropwizard.jetty.GzipFilterFactory; import io.dropwizard.jetty.GzipFilterFactory;
import io.dropwizard.jetty.MutableServletContextHandler; import io.dropwizard.jetty.MutableServletContextHandler;
import io.dropwizard.jetty.NonblockingServletHolder; import io.dropwizard.jetty.NonblockingServletHolder;
Expand Down Expand Up @@ -479,10 +480,11 @@ protected Handler createAppServlet(Server server,
urlPattern += "*"; urlPattern += "*";
} }
jersey.setUrlPattern(urlPattern); jersey.setUrlPattern(urlPattern);
jersey.register(new JacksonMessageBodyProvider(objectMapper, validator)); jersey.register(new JacksonMessageBodyProvider(objectMapper));
if (registerDefaultExceptionMappers == null || registerDefaultExceptionMappers) { if (registerDefaultExceptionMappers == null || registerDefaultExceptionMappers) {
jersey.register(new LoggingExceptionMapper<Throwable>() { jersey.register(new LoggingExceptionMapper<Throwable>() {
}); });
jersey.register(new JerseyViolationExceptionMapper());
jersey.register(new ConstraintViolationExceptionMapper()); jersey.register(new ConstraintViolationExceptionMapper());
jersey.register(new JsonProcessingExceptionMapper()); jersey.register(new JsonProcessingExceptionMapper());
jersey.register(new EarlyEofExceptionMapper()); jersey.register(new EarlyEofExceptionMapper());
Expand Down
Expand Up @@ -12,6 +12,7 @@
import io.dropwizard.jersey.jackson.JsonProcessingExceptionMapper; import io.dropwizard.jersey.jackson.JsonProcessingExceptionMapper;
import io.dropwizard.jersey.validation.ConstraintViolationExceptionMapper; import io.dropwizard.jersey.validation.ConstraintViolationExceptionMapper;
import io.dropwizard.jersey.validation.Validators; import io.dropwizard.jersey.validation.Validators;
import io.dropwizard.jersey.validation.JerseyViolationExceptionMapper;
import io.dropwizard.jetty.HttpConnectorFactory; import io.dropwizard.jetty.HttpConnectorFactory;
import io.dropwizard.logging.ConsoleAppenderFactory; import io.dropwizard.logging.ConsoleAppenderFactory;
import io.dropwizard.logging.FileAppenderFactory; import io.dropwizard.logging.FileAppenderFactory;
Expand Down Expand Up @@ -108,6 +109,7 @@ public void registersDefaultExceptionMappers() throws Exception {
assertThat(singletons).hasAtLeastOneElementOfType(ConstraintViolationExceptionMapper.class); assertThat(singletons).hasAtLeastOneElementOfType(ConstraintViolationExceptionMapper.class);
assertThat(singletons).hasAtLeastOneElementOfType(JsonProcessingExceptionMapper.class); assertThat(singletons).hasAtLeastOneElementOfType(JsonProcessingExceptionMapper.class);
assertThat(singletons).hasAtLeastOneElementOfType(EarlyEofExceptionMapper.class); assertThat(singletons).hasAtLeastOneElementOfType(EarlyEofExceptionMapper.class);
assertThat(singletons).hasAtLeastOneElementOfType(JerseyViolationExceptionMapper.class);


} }


Expand Down
Expand Up @@ -127,17 +127,15 @@ protected Application configure() {
final DropwizardResourceConfig config = DropwizardResourceConfig.forTesting(new MetricRegistry()); final DropwizardResourceConfig config = DropwizardResourceConfig.forTesting(new MetricRegistry());
config.register(new UnitOfWorkApplicationListener("hr-db", sessionFactory)); config.register(new UnitOfWorkApplicationListener("hr-db", sessionFactory));
config.register(new PersonResource(new PersonDAO(sessionFactory))); config.register(new PersonResource(new PersonDAO(sessionFactory)));
config.register(new JacksonMessageBodyProvider(Jackson.newObjectMapper(), config.register(new JacksonMessageBodyProvider(Jackson.newObjectMapper()));
Validators.newValidator()));
config.register(new DataExceptionMapper()); config.register(new DataExceptionMapper());


return config; return config;
} }


@Override @Override
protected void configureClient(ClientConfig config) { protected void configureClient(ClientConfig config) {
config.register(new JacksonMessageBodyProvider(Jackson.newObjectMapper(), config.register(new JacksonMessageBodyProvider(Jackson.newObjectMapper()));
Validators.newValidator()));
} }


@Test @Test
Expand Down
Expand Up @@ -13,6 +13,7 @@
import io.dropwizard.jersey.params.NonEmptyStringParamFeature; import io.dropwizard.jersey.params.NonEmptyStringParamFeature;
import io.dropwizard.jersey.sessions.SessionFactoryProvider; import io.dropwizard.jersey.sessions.SessionFactoryProvider;
import io.dropwizard.jersey.validation.HibernateValidationFeature; import io.dropwizard.jersey.validation.HibernateValidationFeature;
import io.dropwizard.jersey.validation.JerseyViolationExceptionMapper;
import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.ServerProperties; import org.glassfish.jersey.server.ServerProperties;
import org.glassfish.jersey.server.model.Resource; import org.glassfish.jersey.server.model.Resource;
Expand Down Expand Up @@ -67,7 +68,6 @@ public DropwizardResourceConfig(boolean testOnly, MetricRegistry metricRegistry)
register(NonEmptyStringParamFeature.class); register(NonEmptyStringParamFeature.class);
register(new SessionFactoryProvider.Binder()); register(new SessionFactoryProvider.Binder());
register(HibernateValidationFeature.class); register(HibernateValidationFeature.class);
register(ValidationFeature.class);
} }


public static DropwizardResourceConfig forTesting(MetricRegistry metricRegistry) { public static DropwizardResourceConfig forTesting(MetricRegistry metricRegistry) {
Expand Down
Expand Up @@ -3,23 +3,10 @@
import com.fasterxml.jackson.annotation.JsonIgnoreType; import com.fasterxml.jackson.annotation.JsonIgnoreType;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider; import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider;
import io.dropwizard.validation.ConstraintViolations;
import io.dropwizard.validation.Validated;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Valid;
import javax.validation.Validator;
import javax.validation.groups.Default;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.*;


/** /**
* A Jersey provider which enables using Jackson to parse request entities into objects and generate * A Jersey provider which enables using Jackson to parse request entities into objects and generate
Expand All @@ -31,16 +18,9 @@
* {@link JsonIgnoreType}.) * {@link JsonIgnoreType}.)
*/ */
public class JacksonMessageBodyProvider extends JacksonJaxbJsonProvider { public class JacksonMessageBodyProvider extends JacksonJaxbJsonProvider {
private static final Logger LOGGER = LoggerFactory.getLogger(JacksonMessageBodyProvider.class);
/**
* The default group array used in case any of the validate methods is called without a group.
*/
private static final Class<?>[] DEFAULT_GROUP_ARRAY = new Class<?>[]{Default.class};
private final ObjectMapper mapper; private final ObjectMapper mapper;
private final Validator validator;


public JacksonMessageBodyProvider(ObjectMapper mapper, Validator validator) { public JacksonMessageBodyProvider(ObjectMapper mapper) {
this.validator = validator;
this.mapper = mapper; this.mapper = mapper;
setMapper(mapper); setMapper(mapper);
} }
Expand All @@ -53,79 +33,6 @@ public boolean isReadable(Class<?> type,
return isProvidable(type) && super.isReadable(type, genericType, annotations, mediaType); return isProvidable(type) && super.isReadable(type, genericType, annotations, mediaType);
} }


@Override
public Object readFrom(Class<Object> type,
Type genericType,
Annotation[] annotations,
MediaType mediaType,
MultivaluedMap<String, String> httpHeaders,
InputStream entityStream) throws IOException {
return validate(annotations, super.readFrom(type,
genericType,
annotations,
mediaType,
httpHeaders,
entityStream));
}

private Object validate(Annotation[] annotations, Object value) {
if (null == value) {
throw new ConstraintViolationException("The request entity was empty",
Collections.<ConstraintViolation<Object>>emptySet());
}

final Class<?>[] classes = findValidationGroups(annotations);

if (classes != null) {
Set<ConstraintViolation<Object>> violations = null;

if (value instanceof Map) {
violations = validate(((Map) value).values(), classes);
} else if (value instanceof Iterable) {
violations = validate((Iterable) value, classes);
} else if (value.getClass().isArray()) {
violations = new HashSet<>();

Object[] values = (Object[]) value;
for (Object item : values) {
violations.addAll(validator.validate(item, classes));
}
} else {
violations = validator.validate(value, classes);
}

if (violations != null && !violations.isEmpty()) {
Set<ConstraintViolation<?>> constraintViolations = ConstraintViolations.copyOf(violations);
LOGGER.trace("Validation failed: {}; original data was {}",
ConstraintViolations.formatUntyped(constraintViolations), value);
throw new ConstraintViolationException("The request entity had the following errors:",
constraintViolations);
}
}

return value;
}

private Set<ConstraintViolation<Object>> validate(Iterable values, Class<?>[] classes) {
Set<ConstraintViolation<Object>> violations = new HashSet<>();
for (Object value : values) {
violations.addAll(validator.validate(value, classes));
}

return violations;
}

private Class<?>[] findValidationGroups(Annotation[] annotations) {
for (Annotation annotation : annotations) {
if (annotation.annotationType() == Valid.class) {
return DEFAULT_GROUP_ARRAY;
} else if (annotation.annotationType() == Validated.class) {
return ((Validated) annotation).value();
}
}
return null;
}

@Override @Override
public boolean isWriteable(Class<?> type, public boolean isWriteable(Class<?> type,
Type genericType, Type genericType,
Expand Down

0 comments on commit 32ab616

Please sign in to comment.