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.
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,16 @@
import io.dropwizard.jersey.validation.ConstraintMessage;
import io.dropwizard.jersey.validation.Validators;
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.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.options.OptionsBuilder;

Expand All @@ -13,6 +21,7 @@
import javax.validation.Validator;
import javax.validation.executable.ExecutableValidator;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.core.Request;
import java.util.Set;
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> objViolation;

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

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

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

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

public static void main(String[] args) throws Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import io.dropwizard.jersey.gzip.GZipDecoder;
import io.dropwizard.jersey.jackson.JacksonMessageBodyProvider;
import io.dropwizard.jersey.validation.Validators;
import io.dropwizard.jersey.validation.HibernateValidationFeature;
import io.dropwizard.lifecycle.Managed;
import io.dropwizard.setup.Environment;
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(new JacksonMessageBodyProvider(objectMapper, validator));
config.register(new JacksonMessageBodyProvider(objectMapper));
config.register(HibernateValidationFeature.class);

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

}

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

return config;
}

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

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import io.dropwizard.jersey.params.NonEmptyStringParamFeature;
import io.dropwizard.jersey.sessions.SessionFactoryProvider;
import io.dropwizard.jersey.validation.HibernateValidationFeature;
import io.dropwizard.jersey.validation.JerseyViolationExceptionMapper;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.ServerProperties;
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(new SessionFactoryProvider.Binder());
register(HibernateValidationFeature.class);
register(ValidationFeature.class);
}

public static DropwizardResourceConfig forTesting(MetricRegistry metricRegistry) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,10 @@
import com.fasterxml.jackson.annotation.JsonIgnoreType;
import com.fasterxml.jackson.databind.ObjectMapper;
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.MultivaluedMap;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.*;

/**
* A Jersey provider which enables using Jackson to parse request entities into objects and generate
Expand All @@ -31,16 +18,9 @@
* {@link JsonIgnoreType}.)
*/
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 Validator validator;

public JacksonMessageBodyProvider(ObjectMapper mapper, Validator validator) {
this.validator = validator;
public JacksonMessageBodyProvider(ObjectMapper mapper) {
this.mapper = mapper;
setMapper(mapper);
}
Expand All @@ -53,79 +33,6 @@ public boolean isReadable(Class<?> type,
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
public boolean isWriteable(Class<?> type,
Type genericType,
Expand Down
Loading

0 comments on commit 32ab616

Please sign in to comment.