--- a/src/main/java/de/uapcore/lightpit/AbstractLightPITServlet.java Thu Oct 15 14:01:49 2020 +0200 +++ b/src/main/java/de/uapcore/lightpit/AbstractLightPITServlet.java Thu Oct 15 18:36:05 2020 +0200 @@ -84,7 +84,7 @@ * The reason for this is the different handling of empty paths in * {@link HttpServletRequest#getPathInfo()}. */ - private final Map<HttpMethod, Map<String, Method>> mappings = new HashMap<>(); + private final Map<HttpMethod, Map<PathPattern, Method>> mappings = new HashMap<>(); /** * Returns the name of the resource bundle associated with this servlet. @@ -108,7 +108,9 @@ throw new AssertionError("Non-exhaustive if-else - this is a bug."); } - private ResponseType invokeMapping(Method method, HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws IOException { + private ResponseType invokeMapping(Map.Entry<PathPattern, Method> mapping, HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws IOException { + final var pathPattern = mapping.getKey(); + final var method = mapping.getValue(); try { LOG.trace("invoke {}#{}", method.getDeclaringClass().getName(), method.getName()); final var paramTypes = method.getParameterTypes(); @@ -122,6 +124,9 @@ if (paramTypes[i].isAssignableFrom(DataAccessObjects.class)) { paramValues[i] = dao; } + if (paramTypes[i].isAssignableFrom(PathParameters.class)) { + paramValues[i] = pathPattern.obtainPathParameters(sanitizeRequestPath(req)); + } } return (ResponseType) method.invoke(this, paramValues); } catch (InvocationTargetException ex) { @@ -152,6 +157,13 @@ for (Method method : methods) { Optional<RequestMapping> mapping = Optional.ofNullable(method.getAnnotation(RequestMapping.class)); if (mapping.isPresent()) { + if (mapping.get().requestPath().isBlank()) { + LOG.warn("{} is annotated with {} but request path is empty", + method.getName(), RequestMapping.class.getSimpleName() + ); + continue; + } + if (!Modifier.isPublic(method.getModifiers())) { LOG.warn("{} is annotated with {} but is not public", method.getName(), RequestMapping.class.getSimpleName() @@ -175,28 +187,35 @@ for (var param : method.getParameterTypes()) { paramsInjectible &= HttpServletRequest.class.isAssignableFrom(param) || HttpServletResponse.class.isAssignableFrom(param) + || PathParameters.class.isAssignableFrom(param) || DataAccessObjects.class.isAssignableFrom(param); } if (paramsInjectible) { - String requestPath = "/" + mapping.get().requestPath(); + try { + PathPattern pathPattern = new PathPattern(mapping.get().requestPath()); - if (mappings - .computeIfAbsent(mapping.get().method(), k -> new HashMap<>()) - .putIfAbsent(requestPath, method) != null) { - LOG.warn("{} {} has multiple mappings", + if (mappings + .computeIfAbsent(mapping.get().method(), k -> new HashMap<>()) + .putIfAbsent(pathPattern, method) != null) { + LOG.warn("{} {} has multiple mappings", + mapping.get().method(), + mapping.get().requestPath() + ); + } + + LOG.debug("{} {} maps to {}::{}", mapping.get().method(), - mapping.get().requestPath() + mapping.get().requestPath(), + getClass().getSimpleName(), + method.getName() + ); + } catch (IllegalArgumentException ex) { + LOG.warn("Request mapping for {} failed: path pattern '{}' is syntactically invalid", + method.getName(), mapping.get().requestPath() ); } - - LOG.debug("{} {} maps to {}::{}", - mapping.get().method(), - requestPath, - getClass().getSimpleName(), - method.getName() - ); } else { - LOG.warn("{} is annotated with {} but has the wrong parameters - only HttpServletRequest. HttpServletResponse, and DataAccessObjects are allowed", + LOG.warn("{} is annotated with {} but has the wrong parameters - only HttpServletRequest, HttpServletResponse, PathParameters, and DataAccessObjects are allowed", method.getName(), RequestMapping.class.getSimpleName() ); } @@ -373,8 +392,12 @@ return Optional.ofNullable(req.getPathInfo()).orElse("/"); } - private Optional<Method> findMapping(HttpMethod method, HttpServletRequest req) { - return Optional.ofNullable(mappings.get(method)).map(rm -> rm.get(sanitizeRequestPath(req))); + private Optional<Map.Entry<PathPattern, Method>> findMapping(HttpMethod method, HttpServletRequest req) { + return Optional.ofNullable(mappings.get(method)).flatMap(rm -> + rm.entrySet().stream().filter( + kv -> kv.getKey().matches(sanitizeRequestPath(req)) + ).findAny() + ); } private void forwardAsSpecified(ResponseType type, HttpServletRequest req, HttpServletResponse resp)