From bc214defd2008e08d22c43e02a9d4c58d49703be Mon Sep 17 00:00:00 2001 From: Gmugra Date: Wed, 30 Dec 2020 11:39:01 +0100 Subject: [PATCH] ParametersValidator interface; validation-javax module & SimpleParametersValidator with javax.validation --- README.md | 20 ++++---- core/pom.xml | 2 +- .../routing/EntryPointScanner.java | 11 +++-- .../cactusthorn/routing/RoutingConfig.java | 23 ++++++++-- .../cactusthorn/routing/RoutingServlet.java | 16 ++++--- .../routing/invoke/MethodInvoker.java | 21 +++++---- .../ParametersValidationException.java | 10 ++++ .../routing/validate/ParametersValidator.java | 15 ++++++ .../routing/RoutingConfigTest.java | 21 +++++++-- .../routing/RoutingServletTest.java | 25 +++++++++- .../routing/invoke/MethodInvokerTest.java | 20 ++++---- .../routing/scanner/ConsumesTest.java | 20 +++++--- .../scanner/ScannerCollectionTest.java | 6 ++- .../routing/scanner/ScannerSortingTest.java | 7 ++- .../routing/scanner/ScannerTest.java | 33 +++++++++++-- demo-jetty/README.md | 18 ++------ demo-jetty/pom.xml | 8 +++- .../routing/demo/jetty/Application.java | 3 +- .../demo/jetty/entrypoint/HtmlEntryPoint.java | 2 +- .../jetty/entrypoint/SimpleEntryPoint.java | 14 ++++-- .../src/main/resources/thymeleaf/form.html | 30 ++++++++++++ .../src/main/resources/thymeleaf/index.html | 33 ++++++++----- json-gson/pom.xml | 2 +- pom.xml | 46 ++++++++++++++++++- thymeleaf/pom.xml | 2 +- validation-javax/pom.xml | 40 ++++++++++++++++ .../javax/SimpleParametersValidator.java | 32 +++++++++++++ 27 files changed, 384 insertions(+), 96 deletions(-) create mode 100644 core/src/main/java/net/cactusthorn/routing/validate/ParametersValidationException.java create mode 100644 core/src/main/java/net/cactusthorn/routing/validate/ParametersValidator.java create mode 100644 demo-jetty/src/main/resources/thymeleaf/form.html create mode 100644 validation-javax/pom.xml create mode 100644 validation-javax/src/main/java/net/cactusthorn/routing/validation/javax/SimpleParametersValidator.java diff --git a/README.md b/README.md index 25cc4d5..ed987af 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Lightweight Java library for HTTP requests routing in context of Servlet API -[![Build Status](https://travis-ci.com/Gmugra/net.cactusthorn.routing.svg?branch=main)](https://travis-ci.com/Gmugra/net.cactusthorn.routing) [![Coverage Status](https://coveralls.io/repos/github/Gmugra/net.cactusthorn.routing/badge.svg?branch=main)](https://coveralls.io/github/Gmugra/net.cactusthorn.routing?branch=main) ![GitHub release (latest by date)](https://img.shields.io/github/v/release/Gmugra/net.cactusthorn.routing) +[![Build Status](https://travis-ci.com/Gmugra/net.cactusthorn.routing.svg?branch=main)](https://travis-ci.com/Gmugra/net.cactusthorn.routing) [![Coverage Status](https://coveralls.io/repos/github/Gmugra/net.cactusthorn.routing/badge.svg?branch=main)](https://coveralls.io/github/Gmugra/net.cactusthorn.routing?branch=main) ![GitHub release (latest by date)](https://img.shields.io/github/v/release/Gmugra/net.cactusthorn.routing) [![Build by Maven](http://maven.apache.org/images/logos/maven-feather.png)](http://maven.apache.org) ## Introduction @@ -57,6 +57,7 @@ public class Application { .addConsumer("application/json", new SimpleGsonConsumer()) .addProducer("text/html", new SimpleThymeleafProducer("/thymeleaf/")) .addConverter(java.time.LocalDate.class, converter); + .setParametersValidator(new SimpleParametersValidator()) .build(); MultipartConfigElement mpConfig = new MultipartConfigElement("/tmp", 1024 * 1024, 1024 * 1024 * 5, 1024 * 1024 * 5 * 5); @@ -82,7 +83,7 @@ The library is doing only routing, but it's expected that the application provid 1. multiple Producers which will generate responses based on Content-Type 1. multiple Consumers to convert request body to Java objects based on Content-Type -And that is **the basic idea**: you build your web application with only the components you prefer. There is nothing superfluous. +**The basic idea**: build web application with only the components you prefer. The Routing Library JAR itself is less then 100KB (+ ~ 40KB _SLF4J_ JAR ; + ~ 100KB _javax.servlet-api_ JAR). The flow is simple: @@ -106,13 +107,10 @@ It uses the embedded [Jetty](https://www.eclipse.org/jetty/) as servlet-containe and [Dagger 2](https://dagger.dev) for dependency injection and as the basis for the _ComponentProvider_. More or less there are examples of everything: -Various "simple" requests, JSON, File uploading (multipart/form-data), HTML with Thymeleaf, Scopes. +Various "simple" requests, JSON, File uploading (multipart/form-data), HTML with Thymeleaf, Scopes, parameters validation with javax.validation +## Features -## Developing status -It's full functional. - -The library is supporting: 1. @Path for class and/or method (regular expressions support; routing priority like in JAX-RS) 1. @GET @POST and so on 1. Types converting for: @@ -135,12 +133,12 @@ The library is supporting: 1. application/json: _SimpleGsonConsumer_ (**json-gson** module) 1. inject HttpServletRequest, HttpServletResponse, HttpSession, ServletContext in method parameters 1. _Response_ class to manually construct response. +1. ParametersValidator interface to integrate additional validations e.g. _javax.validation_ + 1. Implemetation example is **validation-javax** module +## LICENSE -Comming a bit later -1. Java Bean Validation Annotations (JSR 380) for parameters (as additional module) -1. ? - +net.cactusthorn.routing is released under the BSD license. See LICENSE file included for the details. diff --git a/core/pom.xml b/core/pom.xml index 77417d8..2d131b7 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -7,7 +7,7 @@ net.cactusthorn.routing root - 0.24 + 0.25 core diff --git a/core/src/main/java/net/cactusthorn/routing/EntryPointScanner.java b/core/src/main/java/net/cactusthorn/routing/EntryPointScanner.java index 94c3bec..6d2a650 100644 --- a/core/src/main/java/net/cactusthorn/routing/EntryPointScanner.java +++ b/core/src/main/java/net/cactusthorn/routing/EntryPointScanner.java @@ -11,6 +11,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.regex.Pattern; @@ -36,6 +37,8 @@ import net.cactusthorn.routing.convert.ConverterException; import net.cactusthorn.routing.convert.ConvertersHolder; import net.cactusthorn.routing.invoke.MethodInvoker; +import net.cactusthorn.routing.validate.ParametersValidator; +import net.cactusthorn.routing.validate.ParametersValidationException; public class EntryPointScanner { @@ -77,7 +80,7 @@ public String template() { } public Object invoke(HttpServletRequest req, HttpServletResponse res, ServletContext con, PathValues pathValues) - throws ConverterException { + throws ConverterException, ParametersValidationException { return methodInvoker.invoke(req, res, con, pathValues); } @@ -109,13 +112,15 @@ public boolean matchContentType(String contenttype) { private ConvertersHolder convertersHolder; private ComponentProvider componentProvider; private Map configProperties; + private Optional validator; public EntryPointScanner(Collection> classes, ComponentProvider componentProvider, ConvertersHolder convertersHolder, - Map configProperties) { + Map configProperties, Optional validator) { this.classes.addAll(classes); this.componentProvider = componentProvider; this.convertersHolder = convertersHolder; this.configProperties = configProperties; + this.validator = validator; } public Map, List> scan() { @@ -144,7 +149,7 @@ public Map, List> scan() { String template = findTemplate(method); MethodInvoker methodInvoker = new MethodInvoker(clazz, method, componentProvider, convertersHolder, contentType, - configProperties); + configProperties, validator); EntryPoint entryPoint = new EntryPoint(pathTemplate, produces, template, contentType, methodInvoker); entryPoints.get(type).add(entryPoint); diff --git a/core/src/main/java/net/cactusthorn/routing/RoutingConfig.java b/core/src/main/java/net/cactusthorn/routing/RoutingConfig.java index a1466b1..3a140a7 100644 --- a/core/src/main/java/net/cactusthorn/routing/RoutingConfig.java +++ b/core/src/main/java/net/cactusthorn/routing/RoutingConfig.java @@ -2,12 +2,14 @@ import java.util.List; import java.util.Map; +import java.util.Optional; import net.cactusthorn.routing.EntryPointScanner.EntryPoint; import net.cactusthorn.routing.convert.Converter; import net.cactusthorn.routing.convert.ConvertersHolder; import net.cactusthorn.routing.producer.Producer; import net.cactusthorn.routing.producer.TextPlainProducer; +import net.cactusthorn.routing.validate.ParametersValidator; import java.lang.annotation.Annotation; import java.util.ArrayList; @@ -46,6 +48,8 @@ public Object ddefault() { private Map configProperties; + private ParametersValidator validator; + // @formatter:off private RoutingConfig( ComponentProvider componentProvider, @@ -53,13 +57,15 @@ private RoutingConfig( List> entryPoints, Map producers, Map consumers, - Map configProperties) { + Map configProperties, + ParametersValidator validator) { this.componentProvider = componentProvider; this.entryPoints = entryPoints; this.producers = producers; this.consumers = consumers; this.configProperties = configProperties; + this.validator = validator; } // @formatter:off @@ -87,6 +93,10 @@ public Map properties() { return configProperties; } + public Optional validator() { + return Optional.ofNullable(validator); + } + public static final class Builder { private ComponentProvider componentProvider; @@ -101,6 +111,8 @@ public static final class Builder { private final Map configProperties = new HashMap<>(); + private ParametersValidator validator; + private Builder(ComponentProvider componentProvider) { if (componentProvider == null) { throw new IllegalArgumentException("ComponentProvider can not be null"); @@ -155,6 +167,11 @@ public Builder setDefaultRequestCharacterEncoding(String encoding) { return this; } + public Builder setParametersValidator(ParametersValidator parametersValidator) { + validator = parametersValidator; + return this; + } + public RoutingConfig build() { Map unmodifiableProducers = Collections.unmodifiableMap(producers); @@ -162,11 +179,11 @@ public RoutingConfig build() { Map unmodifiableConfigProperties = Collections.unmodifiableMap(configProperties); EntryPointScanner scanner = new EntryPointScanner(entryPointClasses, componentProvider, convertersHolder, - unmodifiableConfigProperties); + unmodifiableConfigProperties, Optional.ofNullable(validator)); Map, List> entryPoints = scanner.scan(); return new RoutingConfig(componentProvider, Collections.unmodifiableMap(entryPoints), unmodifiableProducers, - unmodifiableConsumers, unmodifiableConfigProperties); + unmodifiableConsumers, unmodifiableConfigProperties, validator); } } } diff --git a/core/src/main/java/net/cactusthorn/routing/RoutingServlet.java b/core/src/main/java/net/cactusthorn/routing/RoutingServlet.java index a9da43b..177ad6f 100644 --- a/core/src/main/java/net/cactusthorn/routing/RoutingServlet.java +++ b/core/src/main/java/net/cactusthorn/routing/RoutingServlet.java @@ -4,6 +4,7 @@ import java.lang.annotation.Annotation; import java.util.List; import java.util.Map; +import java.util.Optional; import javax.servlet.*; import javax.servlet.http.*; @@ -18,6 +19,8 @@ import net.cactusthorn.routing.annotation.*; import net.cactusthorn.routing.convert.ConverterException; import net.cactusthorn.routing.producer.Producer; +import net.cactusthorn.routing.validate.ParametersValidationException; +import net.cactusthorn.routing.validate.ParametersValidator; public class RoutingServlet extends HttpServlet { @@ -32,6 +35,7 @@ public class RoutingServlet extends HttpServlet { private transient ComponentProvider componentProvider; private transient String responseCharacterEncoding; private transient String defaultRequestCharacterEncoding; + private transient Optional parametersValidator; public RoutingServlet(RoutingConfig config) { super(); @@ -41,6 +45,7 @@ public RoutingServlet(RoutingConfig config) { consumers = config.consumers(); responseCharacterEncoding = (String) config.properties().get(ConfigProperty.RESPONSE_CHARACTER_ENCODING); defaultRequestCharacterEncoding = (String) config.properties().get(ConfigProperty.DEFAULT_REQUEST_CHARACTER_ENCODING); + parametersValidator = config.validator(); } @Override // @@ -50,6 +55,7 @@ public void init() throws ServletException { componentProvider.init(servletContext); producers.values().forEach(p -> p.init(servletContext, componentProvider)); consumers.values().forEach(p -> p.init(servletContext, componentProvider)); + parametersValidator.ifPresent(v -> v.init(servletContext, componentProvider)); } @Override // @@ -107,13 +113,9 @@ private void process(HttpServletRequest req, HttpServletResponse resp, List configProperties; + private Optional validator; + public MethodInvoker(Class clazz, Method method, ComponentProvider componentProvider, ConvertersHolder convertersHolder, - String contentType, Map configProperties) { + String contentType, Map configProperties, Optional validator) { this.clazz = clazz; this.method = method; this.componentProvider = componentProvider; this.configProperties = configProperties; + this.validator = validator; for (Parameter parameter : method.getParameters()) { if (parameter.isSynthetic()) { continue; @@ -44,7 +50,7 @@ public MethodInvoker(Class clazz, Method method, ComponentProvider componentP } public Object invoke(HttpServletRequest req, HttpServletResponse res, ServletContext con, PathValues pathValues) - throws ConverterException { + throws ConverterException, ParametersValidationException { Object object = componentProvider.provide(clazz, req); RequestData requestData; @@ -54,12 +60,7 @@ public Object invoke(HttpServletRequest req, HttpServletResponse res, ServletCon requestData = new RequestData(pathValues); } - Object[] values; - if (parameters.size() == 0) { - values = new Object[0]; - } else { - values = new Object[parameters.size()]; - } + Object[] values = parameters.size() == 0 ? new Object[0] : new Object[parameters.size()]; for (int i = 0; i < parameters.size(); i++) { MethodParameter parameter = parameters.get(i); @@ -70,6 +71,10 @@ public Object invoke(HttpServletRequest req, HttpServletResponse res, ServletCon } } + if (validator.isPresent()) { + validator.get().validate(object, method, values); + } + try { return method.invoke(object, values); } catch (Exception e) { diff --git a/core/src/main/java/net/cactusthorn/routing/validate/ParametersValidationException.java b/core/src/main/java/net/cactusthorn/routing/validate/ParametersValidationException.java new file mode 100644 index 0000000..c3ac453 --- /dev/null +++ b/core/src/main/java/net/cactusthorn/routing/validate/ParametersValidationException.java @@ -0,0 +1,10 @@ +package net.cactusthorn.routing.validate; + +public class ParametersValidationException extends Exception { + + private static final long serialVersionUID = 0L; + + public ParametersValidationException(String message) { + super(message); + } +} diff --git a/core/src/main/java/net/cactusthorn/routing/validate/ParametersValidator.java b/core/src/main/java/net/cactusthorn/routing/validate/ParametersValidator.java new file mode 100644 index 0000000..1044799 --- /dev/null +++ b/core/src/main/java/net/cactusthorn/routing/validate/ParametersValidator.java @@ -0,0 +1,15 @@ +package net.cactusthorn.routing.validate; + +import java.lang.reflect.Method; + +import javax.servlet.ServletContext; + +import net.cactusthorn.routing.ComponentProvider; + +public interface ParametersValidator { + + default void init(ServletContext servletContext, ComponentProvider componentProvider) { + } + + void validate(Object object, Method method, Object[] parameters) throws ParametersValidationException; +} diff --git a/core/src/test/java/net/cactusthorn/routing/RoutingConfigTest.java b/core/src/test/java/net/cactusthorn/routing/RoutingConfigTest.java index 5406e29..e9a7a9a 100644 --- a/core/src/test/java/net/cactusthorn/routing/RoutingConfigTest.java +++ b/core/src/test/java/net/cactusthorn/routing/RoutingConfigTest.java @@ -1,10 +1,9 @@ package net.cactusthorn.routing; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.*; import java.util.Arrays; +import java.util.Optional; import javax.servlet.http.HttpServletRequest; @@ -20,6 +19,8 @@ import net.cactusthorn.routing.convert.Converter; import net.cactusthorn.routing.convert.ConverterException; import net.cactusthorn.routing.producer.Producer; +import net.cactusthorn.routing.validate.ParametersValidationException; +import net.cactusthorn.routing.validate.ParametersValidator; public class RoutingConfigTest { @@ -35,6 +36,9 @@ public class RoutingConfigTest { return null; }; + private static final ParametersValidator TEST_VALIDATOR = (object, method, parameters) -> { + }; + public static class EntryPointWrong { @GET @Path("/dddd{/") // @@ -73,7 +77,7 @@ public Object provide(Class clazz, HttpServletRequest request) { } @Test // - public void converter() throws ConverterException { + public void converter() throws ConverterException, ParametersValidationException { RoutingConfig config = RoutingConfig.builder(new EntryPointDateProvider()).addEntryPoint(Arrays.asList(EntryPointDate.class)) .addConverter(java.util.Date.class, TEST_CONVERTER).build(); @@ -121,11 +125,18 @@ public void readBodyBufferSize() { int value = (int) config.properties().get(ConfigProperty.READ_BODY_BUFFER_SIZE); assertEquals(512, value); } - + @Test // public void defaultRequestCharacterEncoding() { RoutingConfig config = RoutingConfig.builder(new EntryPointDateProvider()).setDefaultRequestCharacterEncoding("KOI8-R").build(); String value = (String) config.properties().get(ConfigProperty.DEFAULT_REQUEST_CHARACTER_ENCODING); assertEquals("KOI8-R", value); } + + @Test // + public void parametersValidator() { + RoutingConfig config = RoutingConfig.builder(new EntryPointDateProvider()).setParametersValidator(TEST_VALIDATOR).build(); + Optional validator = config.validator(); + assertTrue(validator.isPresent()); + } } diff --git a/core/src/test/java/net/cactusthorn/routing/RoutingServletTest.java b/core/src/test/java/net/cactusthorn/routing/RoutingServletTest.java index da5a5e0..a3491d4 100644 --- a/core/src/test/java/net/cactusthorn/routing/RoutingServletTest.java +++ b/core/src/test/java/net/cactusthorn/routing/RoutingServletTest.java @@ -20,6 +20,8 @@ import net.cactusthorn.routing.annotation.*; import net.cactusthorn.routing.producer.Producer; +import net.cactusthorn.routing.validate.ParametersValidationException; +import net.cactusthorn.routing.validate.ParametersValidator; public class RoutingServletTest { @@ -27,6 +29,10 @@ public class RoutingServletTest { resp.getWriter().write(String.valueOf(object)); }; + public static final ParametersValidator TEST_VALIDATOR = (object, method, parameters) -> { + throw new ParametersValidationException("abc"); + }; + @Path("/") // public static class EntryPoint1 { @@ -150,7 +156,7 @@ public void wrong() throws ServletException, IOException { Mockito.verify(resp).sendError(code.capture(), Mockito.any()); - assertEquals(404, code.getValue()); + assertEquals(HttpServletResponse.SC_BAD_REQUEST, code.getValue()); } @Test // @@ -380,4 +386,21 @@ public void redirect() throws ServletException, IOException { assertEquals("/xyz", header.getValue()); assertEquals(303, code.getValue()); } + + @Test // + public void validation() throws ServletException, IOException { + RoutingConfig c = RoutingConfig.builder(new EntryPoint1Provider()).addEntryPoint(EntryPoint1.class) + .setParametersValidator(TEST_VALIDATOR).build(); + RoutingServlet s = new RoutingServlet(c); + + Mockito.when(req.getPathInfo()).thenReturn("/api/get"); + + s.doGet(req, resp); + + ArgumentCaptor code = ArgumentCaptor.forClass(Integer.class); + + Mockito.verify(resp).sendError(code.capture(), Mockito.any()); + + assertEquals(400, code.getValue()); + } } diff --git a/core/src/test/java/net/cactusthorn/routing/invoke/MethodInvokerTest.java b/core/src/test/java/net/cactusthorn/routing/invoke/MethodInvokerTest.java index 8b9f5da..a42e53a 100644 --- a/core/src/test/java/net/cactusthorn/routing/invoke/MethodInvokerTest.java +++ b/core/src/test/java/net/cactusthorn/routing/invoke/MethodInvokerTest.java @@ -9,6 +9,7 @@ import java.lang.reflect.*; import java.util.HashMap; import java.util.Map; +import java.util.Optional; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; @@ -26,6 +27,7 @@ import net.cactusthorn.routing.PathTemplate.PathValues; import net.cactusthorn.routing.annotation.*; import net.cactusthorn.routing.convert.ConvertersHolder; +import net.cactusthorn.routing.validate.ParametersValidator; public class MethodInvokerTest { @@ -33,6 +35,8 @@ public class MethodInvokerTest { return new java.util.Date(); }; + private static final Optional VALIDATOR = Optional.ofNullable(null); + public static class EntryPoint1 { public Integer m1(@PathParam("in") Integer val) { @@ -117,7 +121,7 @@ void mock() throws IOException { public void invokeM1() throws Exception { Method method = findMethod("m1"); - MethodInvoker caller = new MethodInvoker(EntryPoint1.class, method, provider, holder, "*/*", configProperties); + MethodInvoker caller = new MethodInvoker(EntryPoint1.class, method, provider, holder, "*/*", configProperties, VALIDATOR); PathValues values = new PathValues(); values.put("in", "123"); @@ -131,7 +135,7 @@ public void invokeM1() throws Exception { public void invokeM0() throws Exception { Method method = findMethod("m0"); - MethodInvoker caller = new MethodInvoker(EntryPoint1.class, method, provider, holder, "*/*", configProperties); + MethodInvoker caller = new MethodInvoker(EntryPoint1.class, method, provider, holder, "*/*", configProperties, VALIDATOR); PathValues values = PathValues.EMPTY; @@ -144,7 +148,7 @@ public void invokeM0() throws Exception { public void invokeM2() throws Exception { Method method = findMethod("m2"); - MethodInvoker caller = new MethodInvoker(EntryPoint1.class, method, provider, holder, "*/*", configProperties); + MethodInvoker caller = new MethodInvoker(EntryPoint1.class, method, provider, holder, "*/*", configProperties, VALIDATOR); PathValues values = PathValues.EMPTY; @@ -157,7 +161,7 @@ public void invokeM2() throws Exception { public void invokeM3() throws Exception { Method method = findMethod("m3"); - MethodInvoker caller = new MethodInvoker(EntryPoint1.class, method, provider, holder, "*/*", configProperties); + MethodInvoker caller = new MethodInvoker(EntryPoint1.class, method, provider, holder, "*/*", configProperties, VALIDATOR); PathValues values = new PathValues(); values.put("in", "123"); @@ -171,7 +175,7 @@ public void invokeM3() throws Exception { public void invokeM4() throws Exception { Method method = findMethod("m4"); - MethodInvoker caller = new MethodInvoker(EntryPoint1.class, method, provider, holder, "*/*", configProperties); + MethodInvoker caller = new MethodInvoker(EntryPoint1.class, method, provider, holder, "*/*", configProperties, VALIDATOR); String result = (String) caller.invoke(request, null, null, null); @@ -182,7 +186,7 @@ public void invokeM4() throws Exception { public void invokeM5() throws Exception { Method method = findMethod("m5"); - MethodInvoker caller = new MethodInvoker(EntryPoint1.class, method, provider, holder, "*/*", configProperties); + MethodInvoker caller = new MethodInvoker(EntryPoint1.class, method, provider, holder, "*/*", configProperties, VALIDATOR); String result = (String) caller.invoke(request, null, context, null); @@ -193,7 +197,7 @@ public void invokeM5() throws Exception { public void invokeM6() throws Exception { Method method = findMethod("m6"); - MethodInvoker caller = new MethodInvoker(EntryPoint1.class, method, provider, holder, "*/*", configProperties); + MethodInvoker caller = new MethodInvoker(EntryPoint1.class, method, provider, holder, "*/*", configProperties, VALIDATOR); String result = (String) caller.invoke(request, response, null, null); @@ -207,7 +211,7 @@ public void invokeM7() throws Exception { Mockito.when(request.getReader()).thenReturn(reader); Method method = findMethod("m7"); - MethodInvoker caller = new MethodInvoker(EntryPoint1.class, method, provider, holder, "test/date", configProperties); + MethodInvoker caller = new MethodInvoker(EntryPoint1.class, method, provider, holder, "test/date", configProperties, VALIDATOR); java.util.Date result = (java.util.Date) caller.invoke(request, response, null, null); diff --git a/core/src/test/java/net/cactusthorn/routing/scanner/ConsumesTest.java b/core/src/test/java/net/cactusthorn/routing/scanner/ConsumesTest.java index 872842c..6430164 100644 --- a/core/src/test/java/net/cactusthorn/routing/scanner/ConsumesTest.java +++ b/core/src/test/java/net/cactusthorn/routing/scanner/ConsumesTest.java @@ -7,6 +7,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import javax.servlet.http.HttpServletRequest; @@ -16,13 +17,15 @@ import net.cactusthorn.routing.*; import net.cactusthorn.routing.annotation.*; import net.cactusthorn.routing.convert.ConvertersHolder; +import net.cactusthorn.routing.validate.ParametersValidator; import net.cactusthorn.routing.EntryPointScanner.EntryPoint; import net.cactusthorn.routing.RoutingConfig.ConfigProperty; public class ConsumesTest { static final ConvertersHolder HOLDER = new ConvertersHolder(); - static Map PROPERTIES = new HashMap<>(); + static final Map PROPERTIES = new HashMap<>(); + private static final Optional VALIDATOR = Optional.ofNullable(null); @BeforeAll // static void setUp() { @@ -51,7 +54,8 @@ public Object provide(Class clazz, HttpServletRequest request) { @Test // public void all() { - EntryPointScanner scanner = new EntryPointScanner(Arrays.asList(EntryPoint1.class), new EntryPoint1Provider1(), HOLDER, PROPERTIES); + EntryPointScanner scanner = new EntryPointScanner(Arrays.asList(EntryPoint1.class), new EntryPoint1Provider1(), HOLDER, PROPERTIES, + VALIDATOR); Map, List> entryPoints = scanner.scan(); EntryPoint entryPoint = entryPoints.get(GET.class).get(0); assertEquals("*/*", entryPoint.consumes()); @@ -60,7 +64,8 @@ public void all() { @Test // public void post() { - EntryPointScanner scanner = new EntryPointScanner(Arrays.asList(EntryPoint1.class), new EntryPoint1Provider1(), HOLDER, PROPERTIES); + EntryPointScanner scanner = new EntryPointScanner(Arrays.asList(EntryPoint1.class), new EntryPoint1Provider1(), HOLDER, PROPERTIES, + VALIDATOR); Map, List> entryPoints = scanner.scan(); EntryPoint entryPoint = entryPoints.get(POST.class).get(0); assertTrue(entryPoint.matchContentType("text/html")); @@ -93,7 +98,8 @@ public Object provide(Class clazz, HttpServletRequest request) { @Test // public void global() { - EntryPointScanner scanner = new EntryPointScanner(Arrays.asList(EntryPoint2.class), new EntryPoint1Provider2(), HOLDER, PROPERTIES); + EntryPointScanner scanner = new EntryPointScanner(Arrays.asList(EntryPoint2.class), new EntryPoint1Provider2(), HOLDER, PROPERTIES, + VALIDATOR); Map, List> entryPoints = scanner.scan(); EntryPoint entryPoint = entryPoints.get(PUT.class).get(0); assertTrue(entryPoint.matchContentType("text/html")); @@ -102,7 +108,8 @@ public void global() { @Test // public void override() { - EntryPointScanner scanner = new EntryPointScanner(Arrays.asList(EntryPoint2.class), new EntryPoint1Provider2(), HOLDER, PROPERTIES); + EntryPointScanner scanner = new EntryPointScanner(Arrays.asList(EntryPoint2.class), new EntryPoint1Provider2(), HOLDER, PROPERTIES, + VALIDATOR); Map, List> entryPoints = scanner.scan(); EntryPoint entryPoint = entryPoints.get(GET.class).get(0); assertFalse(entryPoint.matchContentType("text/html")); @@ -111,7 +118,8 @@ public void override() { @Test // public void formData() { - EntryPointScanner scanner = new EntryPointScanner(Arrays.asList(EntryPoint2.class), new EntryPoint1Provider2(), HOLDER, PROPERTIES); + EntryPointScanner scanner = new EntryPointScanner(Arrays.asList(EntryPoint2.class), new EntryPoint1Provider2(), HOLDER, PROPERTIES, + VALIDATOR); Map, List> entryPoints = scanner.scan(); EntryPoint entryPoint = entryPoints.get(POST.class).get(0); assertTrue(entryPoint.matchContentType("multipart/form-data; boundary=----WebKitFormBoundaryqoNsVh2QtLJ19YqS")); diff --git a/core/src/test/java/net/cactusthorn/routing/scanner/ScannerCollectionTest.java b/core/src/test/java/net/cactusthorn/routing/scanner/ScannerCollectionTest.java index 6439ea9..2e82b6b 100644 --- a/core/src/test/java/net/cactusthorn/routing/scanner/ScannerCollectionTest.java +++ b/core/src/test/java/net/cactusthorn/routing/scanner/ScannerCollectionTest.java @@ -7,6 +7,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import javax.servlet.http.HttpServletRequest; @@ -20,9 +21,12 @@ import net.cactusthorn.routing.annotation.GET; import net.cactusthorn.routing.annotation.Path; import net.cactusthorn.routing.convert.ConvertersHolder; +import net.cactusthorn.routing.validate.ParametersValidator; public class ScannerCollectionTest { + private static final Optional VALIDATOR = Optional.ofNullable(null); + @Path("/api") // public static class EntryPoint1 { @@ -84,7 +88,7 @@ public Object provide(Class clazz, HttpServletRequest request) { @Test // public void entryPoint() { List> classes = Arrays.asList(EntryPoint1.class, EntryPoint2.class, EntryPoint3.class, EntryPoint4.class); - EntryPointScanner f = new EntryPointScanner(classes, new EntryPointProvider(), HOLDER, PROPERTIES); + EntryPointScanner f = new EntryPointScanner(classes, new EntryPointProvider(), HOLDER, PROPERTIES, VALIDATOR); Map, List> entryPoints = f.scan(); List gets = entryPoints.get(GET.class); assertEquals(4, gets.size()); diff --git a/core/src/test/java/net/cactusthorn/routing/scanner/ScannerSortingTest.java b/core/src/test/java/net/cactusthorn/routing/scanner/ScannerSortingTest.java index 26b2220..9b203cf 100644 --- a/core/src/test/java/net/cactusthorn/routing/scanner/ScannerSortingTest.java +++ b/core/src/test/java/net/cactusthorn/routing/scanner/ScannerSortingTest.java @@ -7,6 +7,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import javax.servlet.http.HttpServletRequest; @@ -20,9 +21,12 @@ import net.cactusthorn.routing.annotation.GET; import net.cactusthorn.routing.annotation.Path; import net.cactusthorn.routing.convert.ConvertersHolder; +import net.cactusthorn.routing.validate.ParametersValidator; public class ScannerSortingTest { + private static final Optional VALIDATOR = Optional.ofNullable(null); + @Path("/api") // public static class EntryPoint1 { @@ -59,7 +63,8 @@ static void setUp() { @Test // public void entryPoint1() { - EntryPointScanner f = new EntryPointScanner(Arrays.asList(EntryPoint1.class), new EntryPoint1Provider(), HOLDER, PROPERTIES); + EntryPointScanner f = new EntryPointScanner(Arrays.asList(EntryPoint1.class), new EntryPoint1Provider(), HOLDER, PROPERTIES, + VALIDATOR); Map, List> entryPoints = f.scan(); List gets = entryPoints.get(GET.class); diff --git a/core/src/test/java/net/cactusthorn/routing/scanner/ScannerTest.java b/core/src/test/java/net/cactusthorn/routing/scanner/ScannerTest.java index 2fdf824..cf9c483 100644 --- a/core/src/test/java/net/cactusthorn/routing/scanner/ScannerTest.java +++ b/core/src/test/java/net/cactusthorn/routing/scanner/ScannerTest.java @@ -8,6 +8,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import javax.servlet.http.HttpServletRequest; @@ -22,9 +23,12 @@ import net.cactusthorn.routing.PathTemplate.PathValues; import net.cactusthorn.routing.annotation.*; import net.cactusthorn.routing.convert.ConvertersHolder; +import net.cactusthorn.routing.validate.ParametersValidator; public class ScannerTest { + private static final Optional VALIDATOR = Optional.ofNullable(null); + @Path("/api") // public static class EntryPoint1 { @@ -61,7 +65,8 @@ static void setUp() { @Test // public void entryPoint1() { - EntryPointScanner f = new EntryPointScanner(Arrays.asList(EntryPoint1.class), new EntryPoint1Provider1(), HOLDER, PROPERTIES); + EntryPointScanner f = new EntryPointScanner(Arrays.asList(EntryPoint1.class), new EntryPoint1Provider1(), HOLDER, PROPERTIES, + VALIDATOR); Map, List> entryPoints = f.scan(); List gets = entryPoints.get(GET.class); @@ -97,7 +102,8 @@ public Object provide(Class clazz, HttpServletRequest request) { @Test // public void entryPoint2() { - EntryPointScanner f = new EntryPointScanner(Arrays.asList(EntryPoint2.class), new EntryPoint1Provider2(), HOLDER, PROPERTIES); + EntryPointScanner f = new EntryPointScanner(Arrays.asList(EntryPoint2.class), new EntryPoint1Provider2(), HOLDER, PROPERTIES, + VALIDATOR); Map, List> entryPoints = f.scan(); List gets = entryPoints.get(GET.class); @@ -122,7 +128,8 @@ public Object provide(Class clazz, HttpServletRequest request) { @Test // public void entryPoint3() { - EntryPointScanner f = new EntryPointScanner(Arrays.asList(EntryPoint3.class), new EntryPoint1Provider3(), HOLDER, PROPERTIES); + EntryPointScanner f = new EntryPointScanner(Arrays.asList(EntryPoint3.class), new EntryPoint1Provider3(), HOLDER, PROPERTIES, + VALIDATOR); Map, List> entryPoints = f.scan(); EntryPoint entryPoint = entryPoints.get(GET.class).get(0); @@ -133,7 +140,12 @@ public void entryPoint3() { @Path("/api") // public static class EntryPoint4 { - @GET @Produces("*/*") public void m1() { + @GET @Produces("*/*") // + public void m1() { + } + + @HEAD @Produces("text/html") @Template("wow.html") // + public void template() { } } @@ -147,7 +159,8 @@ public Object provide(Class clazz, HttpServletRequest request) { @Test // public void entryPoint4() { - EntryPointScanner f = new EntryPointScanner(Arrays.asList(EntryPoint4.class), new EntryPoint1Provider4(), HOLDER, PROPERTIES); + EntryPointScanner f = new EntryPointScanner(Arrays.asList(EntryPoint4.class), new EntryPoint1Provider4(), HOLDER, PROPERTIES, + VALIDATOR); Map, List> entryPoints = f.scan(); EntryPoint entryPoint = entryPoints.get(GET.class).get(0); PathTemplate.PathValues values = entryPoint.parse("/api/"); @@ -156,4 +169,14 @@ public void entryPoint4() { assertTrue(entryPoint.match("/api/")); assertEquals("*/*", entryPoint.produces()); } + + @Test // + public void wow() { + EntryPointScanner f = new EntryPointScanner(Arrays.asList(EntryPoint4.class), new EntryPoint1Provider4(), HOLDER, PROPERTIES, + VALIDATOR); + Map, List> entryPoints = f.scan(); + EntryPoint entryPoint = entryPoints.get(HEAD.class).get(0); + assertEquals("wow.html", entryPoint.template()); + + } } diff --git a/demo-jetty/README.md b/demo-jetty/README.md index b97f77b..5566b3f 100644 --- a/demo-jetty/README.md +++ b/demo-jetty/README.md @@ -1,22 +1,10 @@ -# Demo application with Jetty +## Demo application with Jetty -### Test request: +#### Notes -root: (GET) http://localhost:8080/ +Becasue of [Dagger 2](https://dagger.dev), in Eclipse need to install "m2e-apt" plugin: https://immutables.github.io/apt.html -pathParam value: (GET) http://localhost:8080/rest/api/test123?test=10.5 - -to get JSON: (GET) http://localhost:8080/rest/api/gson - -to post SON: (POST) http://localhost:8080/rest/api/gson with reguest body: -```json -{"name":"The Name ß","value":123} -``` - -to get HTML Thymeleaf-template: (GET) http://localhost:8080/text/html - -to submit HTML form: (POST) http://localhost:8080/text/form diff --git a/demo-jetty/pom.xml b/demo-jetty/pom.xml index 2a1f857..4512d4b 100644 --- a/demo-jetty/pom.xml +++ b/demo-jetty/pom.xml @@ -7,7 +7,7 @@ net.cactusthorn.routing root - 0.24 + 0.25 demo-jetty @@ -39,6 +39,12 @@ ${project.version} + + net.cactusthorn.routing + validation-javax + ${project.version} + + com.google.dagger dagger diff --git a/demo-jetty/src/main/java/net/cactusthorn/routing/demo/jetty/Application.java b/demo-jetty/src/main/java/net/cactusthorn/routing/demo/jetty/Application.java index b5b19e6..1ef4951 100644 --- a/demo-jetty/src/main/java/net/cactusthorn/routing/demo/jetty/Application.java +++ b/demo-jetty/src/main/java/net/cactusthorn/routing/demo/jetty/Application.java @@ -4,7 +4,7 @@ import net.cactusthorn.routing.gson.SimpleGsonConsumer; import net.cactusthorn.routing.gson.SimpleGsonProducer; import net.cactusthorn.routing.thymeleaf.SimpleThymeleafProducer; - +import net.cactusthorn.routing.validation.javax.SimpleParametersValidator; import net.cactusthorn.routing.demo.jetty.dagger.*; import java.time.LocalDate; @@ -35,6 +35,7 @@ public static void main(String... args) { .addConsumer("application/json", new SimpleGsonConsumer()) .addProducer("text/html", new SimpleThymeleafProducer("/thymeleaf/")) .addConverter(LocalDate.class, new LocalDateConverter()) + .setParametersValidator(new SimpleParametersValidator()) .build(); // @formatter:on diff --git a/demo-jetty/src/main/java/net/cactusthorn/routing/demo/jetty/entrypoint/HtmlEntryPoint.java b/demo-jetty/src/main/java/net/cactusthorn/routing/demo/jetty/entrypoint/HtmlEntryPoint.java index 44aa0a0..f7097af 100644 --- a/demo-jetty/src/main/java/net/cactusthorn/routing/demo/jetty/entrypoint/HtmlEntryPoint.java +++ b/demo-jetty/src/main/java/net/cactusthorn/routing/demo/jetty/entrypoint/HtmlEntryPoint.java @@ -19,7 +19,7 @@ public class HtmlEntryPoint implements EntryPoint { public HtmlEntryPoint() { } - @GET @Produces("text/html") @Template("/index.html") // + @GET @Produces("text/html") @Template("/form.html") // public String getitHtml() { return "TEST HTML PAGE"; } diff --git a/demo-jetty/src/main/java/net/cactusthorn/routing/demo/jetty/entrypoint/SimpleEntryPoint.java b/demo-jetty/src/main/java/net/cactusthorn/routing/demo/jetty/entrypoint/SimpleEntryPoint.java index cdb1e4b..ca0bd69 100644 --- a/demo-jetty/src/main/java/net/cactusthorn/routing/demo/jetty/entrypoint/SimpleEntryPoint.java +++ b/demo-jetty/src/main/java/net/cactusthorn/routing/demo/jetty/entrypoint/SimpleEntryPoint.java @@ -5,13 +5,17 @@ import java.time.LocalDate; import javax.inject.Inject; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotNull; import net.cactusthorn.routing.Response; import net.cactusthorn.routing.annotation.DefaultValue; import net.cactusthorn.routing.annotation.GET; import net.cactusthorn.routing.annotation.Path; import net.cactusthorn.routing.annotation.PathParam; +import net.cactusthorn.routing.annotation.Produces; import net.cactusthorn.routing.annotation.QueryParam; +import net.cactusthorn.routing.annotation.Template; import net.cactusthorn.routing.demo.jetty.dagger.EntryPoint; public class SimpleEntryPoint implements EntryPoint { @@ -20,9 +24,8 @@ public class SimpleEntryPoint implements EntryPoint { public SimpleEntryPoint() { } - @GET // - public String doroot() { - return "ROOT :: " + this.getClass().getSimpleName() + "@" + this.hashCode(); + @GET @Produces("text/html") @Template("/index.html") // + public void doroot() { } @GET @Path("/nocontent") // @@ -34,6 +37,11 @@ public String doit(@PathParam("var") int in, @DefaultValue("10.5") @QueryParam(" return in + " \u00DF " + q + " :: " + this.getClass().getSimpleName() + "@" + this.hashCode(); } + @GET @Path("/rest/api/validation") // + public String validation(@NotNull @Min(5) @QueryParam("test") String s) { + return ">>>" + s; + } + @GET @Path("/rest/api/{var : [abc]*}") // public String empty(@PathParam("var") @DefaultValue("DEFAULT") String sss) { return "|" + sss + "| :: " + this.getClass().getSimpleName() + "@" + this.hashCode(); diff --git a/demo-jetty/src/main/resources/thymeleaf/form.html b/demo-jetty/src/main/resources/thymeleaf/form.html new file mode 100644 index 0000000..25f5f76 --- /dev/null +++ b/demo-jetty/src/main/resources/thymeleaf/form.html @@ -0,0 +1,30 @@ + + + + + + Micro :: Test + + + + + +

test?



+ +
+ +

+ + +

+ +
+ 10
+ 20
+ 30

+ + +
+ + + \ No newline at end of file diff --git a/demo-jetty/src/main/resources/thymeleaf/index.html b/demo-jetty/src/main/resources/thymeleaf/index.html index 25f5f76..f7e1db7 100644 --- a/demo-jetty/src/main/resources/thymeleaf/index.html +++ b/demo-jetty/src/main/resources/thymeleaf/index.html @@ -9,22 +9,31 @@ -

test?



+

TEST LINKS



-
- -

+ Response HTTP 204 : "no content"

- -

+ With PathParam and QueryParam

-
- 10
- 20
- 30

+ With QueryParam default value

- -
+ With PathParam default value

+ + ParametersValidationException: HTTP 400

+ + Another one ParametersValidationException: HTTP 400

+ + ConverterException: HTTP 400

+ + Redirect HTTP 303 : "see other"

+ + Custom converter

+ + JSON Response

+ + Simple HTML form

+ + HTML form with files uploading

\ No newline at end of file diff --git a/json-gson/pom.xml b/json-gson/pom.xml index e88ae6f..9367d46 100644 --- a/json-gson/pom.xml +++ b/json-gson/pom.xml @@ -7,7 +7,7 @@ net.cactusthorn.routing root - 0.24 + 0.25 json-gson diff --git a/pom.xml b/pom.xml index aa40983..ab64466 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ net.cactusthorn.routing root - 0.24 + 0.25 pom Routing :: Root @@ -23,6 +23,7 @@ Alexei Khatskevich + alexei.khatskevich@gmail.com @@ -60,6 +61,8 @@ 2.8.6 3.0.11.RELEASE 2.30.1 + 6.2.0.Final + 3.0.1-b12 3.8.1 3.2.0 @@ -81,6 +84,7 @@ 3.1.1 8.38 4.3.0 + 1.6 @@ -88,6 +92,7 @@ core json-gson thymeleaf + validation-javax demo-jetty @@ -148,6 +153,17 @@ ${dependency.thymeleaf}
+ + org.hibernate.validator + hibernate-validator + ${dependency.hibernate-validator} + + + org.glassfish + javax.el + ${dependency.javax.el} + + org.eclipse.jetty jetty-server @@ -265,6 +281,9 @@ maven-surefire-plugin ${plugin.test} + + 2 + @@ -292,6 +311,11 @@ ${plugin.deploy} + + maven-gpg-plugin + ${plugin.gpg} + + maven-jar-plugin ${plugin.jar} @@ -463,6 +487,26 @@ + + gpg + + + + maven-gpg-plugin + + + sign-artifacts + verify + + sign + + + + + + + + diff --git a/thymeleaf/pom.xml b/thymeleaf/pom.xml index afd97b4..2c9a160 100644 --- a/thymeleaf/pom.xml +++ b/thymeleaf/pom.xml @@ -7,7 +7,7 @@ net.cactusthorn.routing root - 0.24 + 0.25 thymeleaf diff --git a/validation-javax/pom.xml b/validation-javax/pom.xml new file mode 100644 index 0000000..e7872be --- /dev/null +++ b/validation-javax/pom.xml @@ -0,0 +1,40 @@ + + + 4.0.0 + + + net.cactusthorn.routing + root + 0.25 + + + validation-javax + jar + Routing :: Validation with javax.validation + + + false + + + + + + net.cactusthorn.routing + core + ${project.version} + + + + org.hibernate.validator + hibernate-validator + + + org.glassfish + javax.el + + + + + diff --git a/validation-javax/src/main/java/net/cactusthorn/routing/validation/javax/SimpleParametersValidator.java b/validation-javax/src/main/java/net/cactusthorn/routing/validation/javax/SimpleParametersValidator.java new file mode 100644 index 0000000..91eec3d --- /dev/null +++ b/validation-javax/src/main/java/net/cactusthorn/routing/validation/javax/SimpleParametersValidator.java @@ -0,0 +1,32 @@ +package net.cactusthorn.routing.validation.javax; + +import java.lang.reflect.Method; +import java.util.Set; + +import javax.validation.ConstraintViolation; +import javax.validation.Validation; +import javax.validation.executable.ExecutableValidator; + +import net.cactusthorn.routing.validate.ParametersValidator; +import net.cactusthorn.routing.validate.ParametersValidationException; + +public class SimpleParametersValidator implements ParametersValidator { + + ExecutableValidator executableValidator = Validation.buildDefaultValidatorFactory().getValidator().forExecutables(); + + @Override // + public void validate(Object object, Method method, Object[] parameters) throws ParametersValidationException { + + Set> violations = executableValidator.validateParameters(object, method, parameters); + + if (violations.isEmpty()) { + return; + } + + String message = ""; + for (ConstraintViolation violation : violations) { + message += violation.getPropertyPath() + " :: " + violation.getMessage() + ";"; + } + throw new ParametersValidationException(message); + } +}