universe@179: /* universe@179: * Copyright 2021 Mike Becker. All rights reserved. universe@179: * universe@179: * Redistribution and use in source and binary forms, with or without universe@179: * modification, are permitted provided that the following conditions are met: universe@179: * universe@179: * 1. Redistributions of source code must retain the above copyright universe@179: * notice, this list of conditions and the following disclaimer. universe@179: * universe@179: * 2. Redistributions in binary form must reproduce the above copyright universe@179: * notice, this list of conditions and the following disclaimer in the universe@179: * documentation and/or other materials provided with the distribution. universe@179: * universe@179: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" universe@179: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE universe@179: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE universe@179: * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE universe@179: * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL universe@179: * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR universe@179: * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER universe@179: * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, universe@179: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE universe@179: * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. universe@179: */ universe@179: universe@179: package de.uapcore.lightpit universe@179: universe@179: import kotlin.math.min universe@179: universe@179: /** universe@179: * Maps requests to methods. universe@179: * universe@179: * This annotation is used to annotate methods within classes which universe@179: * override [AbstractServlet]. universe@179: */ universe@179: @MustBeDocumented universe@179: @Retention(AnnotationRetention.RUNTIME) universe@179: @Target(AnnotationTarget.FUNCTION) universe@179: annotation class RequestMapping( universe@179: universe@179: /** universe@179: * Specifies the HTTP method. universe@179: * universe@179: * @return the HTTP method handled by the annotated Java method universe@179: */ universe@179: val method: HttpMethod, universe@179: universe@179: /** universe@179: * Specifies the request path relative to the module path. universe@179: * The trailing slash is important. universe@179: * A node may start with a dollar ($) sign. universe@179: * This part of the path is then treated as an path parameter. universe@179: * Path parameters can be obtained by including the [PathParameters] type in the signature. universe@179: * universe@179: * @return the request path the annotated method should handle universe@179: */ universe@179: val requestPath: String = "/" universe@179: ) universe@179: universe@179: class PathParameters : HashMap() universe@179: universe@179: /** universe@179: * A path pattern optionally containing placeholders. universe@179: * universe@179: * The special directories . and .. are disallowed in the pattern. universe@179: * Placeholders start with a $ sign. universe@179: * universe@179: * @param pattern the pattern universe@179: */ universe@179: class PathPattern(pattern: String) { universe@179: private val nodePatterns: List universe@179: private val collection: Boolean universe@179: universe@179: private fun parse(pattern: String): List { universe@179: val nodes = pattern.split("/").filter { it.isNotBlank() }.toList() universe@179: require(nodes.none { it == "." || it == ".." }) { "Path must not contain '.' or '..' nodes." } universe@179: return nodes universe@179: } universe@179: universe@179: /** universe@179: * Matches a path against this pattern. universe@179: * The path must be canonical in the sense that no . or .. parts occur. universe@179: * universe@179: * @param path the path to match universe@179: * @return true if the path matches the pattern, false otherwise universe@179: */ universe@179: fun matches(path: String): Boolean { universe@179: if (collection xor path.endsWith("/")) return false universe@179: val nodes = parse(path) universe@179: if (nodePatterns.size != nodes.size) return false universe@179: for (i in nodePatterns.indices) { universe@179: val pattern = nodePatterns[i] universe@179: val node = nodes[i] universe@179: if (pattern.startsWith("$")) continue universe@179: if (pattern != node) return false universe@179: } universe@179: return true universe@179: } universe@179: universe@179: /** universe@179: * Returns the path parameters found in the specified path using this pattern. universe@179: * The return value of this method is undefined, if the patter does not match. universe@179: * universe@179: * @param path the path universe@179: * @return the path parameters, if any, or an empty map universe@179: * @see .matches universe@179: */ universe@179: fun obtainPathParameters(path: String): PathParameters { universe@179: val params = PathParameters() universe@179: val nodes = parse(path) universe@179: for (i in 0 until min(nodes.size, nodePatterns.size)) { universe@179: val pattern = nodePatterns[i] universe@179: val node = nodes[i] universe@179: if (pattern.startsWith("$")) { universe@179: params[pattern.substring(1)] = node universe@179: } universe@179: } universe@179: return params universe@179: } universe@179: universe@179: override fun hashCode(): Int { universe@179: val str = StringBuilder() universe@179: for (node in nodePatterns) { universe@179: if (node.startsWith("$")) { universe@179: str.append("/$") universe@179: } else { universe@179: str.append('/') universe@179: str.append(node) universe@179: } universe@179: } universe@179: if (collection) str.append('/') universe@179: return str.toString().hashCode() universe@179: } universe@179: universe@179: override fun equals(other: Any?): Boolean { universe@179: if (other is PathPattern) { universe@179: if (collection xor other.collection || nodePatterns.size != other.nodePatterns.size) return false universe@179: for (i in nodePatterns.indices) { universe@179: val left = nodePatterns[i] universe@179: val right = other.nodePatterns[i] universe@179: if (left.startsWith("$") && right.startsWith("$")) continue universe@179: if (left != right) return false universe@179: } universe@179: return true universe@179: } else { universe@179: return false universe@179: } universe@179: } universe@179: universe@179: init { universe@179: nodePatterns = parse(pattern) universe@179: collection = pattern.endsWith("/") universe@179: } universe@179: } universe@179: