src/main/kotlin/de/uapcore/lightpit/RequestMapping.kt

Sat, 15 May 2021 16:19:29 +0200

author
Mike Becker <universe@uap-core.de>
date
Sat, 15 May 2021 16:19:29 +0200
changeset 199
59393c8cc557
parent 198
94f174d591ab
child 205
7725a79416f3
permissions
-rw-r--r--

#109 adds RSS feed button to project header and changes feed output slightly

     1 /*
     2  * Copyright 2021 Mike Becker. All rights reserved.
     3  *
     4  * Redistribution and use in source and binary forms, with or without
     5  * modification, are permitted provided that the following conditions are met:
     6  *
     7  * 1. Redistributions of source code must retain the above copyright
     8  * notice, this list of conditions and the following disclaimer.
     9  *
    10  * 2. Redistributions in binary form must reproduce the above copyright
    11  * notice, this list of conditions and the following disclaimer in the
    12  * documentation and/or other materials provided with the distribution.
    13  *
    14  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    15  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    17  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
    18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
    19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
    20  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
    21  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
    22  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    24  */
    26 package de.uapcore.lightpit
    28 import de.uapcore.lightpit.dao.DataAccessObject
    29 import de.uapcore.lightpit.viewmodel.NavMenu
    30 import de.uapcore.lightpit.viewmodel.View
    31 import javax.servlet.http.HttpServletRequest
    32 import javax.servlet.http.HttpServletResponse
    33 import javax.servlet.http.HttpSession
    34 import kotlin.math.min
    36 typealias MappingMethod = (HttpRequest, DataAccessObject) -> Unit
    37 typealias PathParameters = Map<String, String>
    39 class HttpRequest(
    40     val request: HttpServletRequest,
    41     val response: HttpServletResponse,
    42     val pathParams: PathParameters = emptyMap()
    43 ) {
    44     val session: HttpSession = request.session
    46     val remoteUser: String? = request.remoteUser
    48     /**
    49      * The name of the content page.
    50      *
    51      * @see Constants#REQ_ATTR_CONTENT_PAGE
    52      */
    53     var contentPage = ""
    54         set(value) {
    55             field = value
    56             request.setAttribute(Constants.REQ_ATTR_CONTENT_PAGE, jspPath(value))
    57         }
    59     /**
    60      * A list of additional style sheets.
    61      *
    62      * @see Constants#REQ_ATTR_STYLESHEET
    63      */
    64     var styleSheets = emptyList<String>()
    65         set(value) {
    66             field = value
    67             request.setAttribute(Constants.REQ_ATTR_STYLESHEET,
    68                 value.map { it.withExt(".css") }
    69             )
    70         }
    72     /**
    73      * The name of the navigation menu JSP.
    74      *
    75      * @see Constants#REQ_ATTR_NAVIGATION
    76      */
    77     var navigationMenu: NavMenu? = null
    78         set(value) {
    79             field = value
    80             request.setAttribute(Constants.REQ_ATTR_NAVIGATION, navigationMenu)
    81         }
    83     var redirectLocation: String? = null
    84         set(value) {
    85             field = value
    86             if (value == null) {
    87                 request.removeAttribute(Constants.REQ_ATTR_REDIRECT_LOCATION)
    88             } else {
    89                 request.setAttribute(Constants.REQ_ATTR_REDIRECT_LOCATION, baseHref + value)
    90             }
    91         }
    93     var feedPath: String? = null
    94         set(value) {
    95             field = value
    96             if (value == null) {
    97                 request.removeAttribute(Constants.REQ_ATTR_FEED_HREF)
    98             } else {
    99                 request.setAttribute(Constants.REQ_ATTR_FEED_HREF, baseHref + value)
   100             }
   101         }
   103     /**
   104      * The view object.
   105      *
   106      * @see Constants#REQ_ATTR_VIEWMODEL
   107      */
   108     var view: View? = null
   109         set(value) {
   110             field = value
   111             request.setAttribute(Constants.REQ_ATTR_VIEWMODEL, value)
   112         }
   114     /**
   115      * Additional port info, if necessary.
   116      */
   117     private val portInfo =
   118         if ((request.scheme == "http" && request.serverPort == 80)
   119             || (request.scheme == "https" && request.serverPort == 443)
   120         ) "" else ":${request.serverPort}"
   122     /**
   123      * The base path of this application.
   124      */
   125     val baseHref get() = "${request.scheme}://${request.serverName}$portInfo${request.contextPath}/"
   127     private fun String.withExt(ext: String) = if (endsWith(ext)) this else plus(ext)
   128     private fun jspPath(name: String) = Constants.JSP_PATH_PREFIX.plus(name).withExt(".jsp")
   130     fun param(name: String): String? = request.getParameter(name)
   131     fun paramArray(name: String): Array<String> = request.getParameterValues(name) ?: emptyArray()
   133     private fun forward(jsp: String) {
   134         request.getRequestDispatcher(jspPath(jsp)).forward(request, response)
   135     }
   137     fun renderFeed(page: String? = null) {
   138         page?.let { contentPage = it }
   139         forward("feed")
   140     }
   142     fun render(page: String? = null) {
   143         page?.let { contentPage = it }
   144         forward("site")
   145     }
   147     fun renderCommit(location: String? = null) {
   148         location?.let { redirectLocation = it }
   149         contentPage = Constants.JSP_COMMIT_SUCCESSFUL
   150         render()
   151     }
   152 }
   154 /**
   155  * A path pattern optionally containing placeholders.
   156  *
   157  * The special directories . and .. are disallowed in the pattern.
   158  * Placeholders start with a % sign.
   159  *
   160  * @param pattern the pattern
   161  */
   162 class PathPattern(pattern: String) {
   163     private val nodePatterns: List<String>
   164     private val collection: Boolean
   166     private fun parse(pattern: String): List<String> {
   167         val nodes = pattern.split("/").filter { it.isNotBlank() }.toList()
   168         require(nodes.none { it == "." || it == ".." }) { "Path must not contain '.' or '..' nodes." }
   169         return nodes
   170     }
   172     /**
   173      * Matches a path against this pattern.
   174      * The path must be canonical in the sense that no . or .. parts occur.
   175      *
   176      * @param path the path to match
   177      * @return true if the path matches the pattern, false otherwise
   178      */
   179     fun matches(path: String): Boolean {
   180         if (collection xor path.endsWith("/")) return false
   181         val nodes = parse(path)
   182         if (nodePatterns.size != nodes.size) return false
   183         for (i in nodePatterns.indices) {
   184             val pattern = nodePatterns[i]
   185             val node = nodes[i]
   186             if (pattern.startsWith("%")) continue
   187             if (pattern != node) return false
   188         }
   189         return true
   190     }
   192     /**
   193      * Returns the path parameters found in the specified path using this pattern.
   194      * The return value of this method is undefined, if the patter does not match.
   195      *
   196      * @param path the path
   197      * @return the path parameters, if any, or an empty map
   198      * @see .matches
   199      */
   200     fun obtainPathParameters(path: String): PathParameters {
   201         val params = mutableMapOf<String, String>()
   202         val nodes = parse(path)
   203         for (i in 0 until min(nodes.size, nodePatterns.size)) {
   204             val pattern = nodePatterns[i]
   205             val node = nodes[i]
   206             if (pattern.startsWith("%")) {
   207                 params[pattern.substring(1)] = node
   208             }
   209         }
   210         return params
   211     }
   213     override fun hashCode(): Int {
   214         val str = StringBuilder()
   215         for (node in nodePatterns) {
   216             if (node.startsWith("%")) {
   217                 str.append("/%")
   218             } else {
   219                 str.append('/')
   220                 str.append(node)
   221             }
   222         }
   223         if (collection) str.append('/')
   224         return str.toString().hashCode()
   225     }
   227     override fun equals(other: Any?): Boolean {
   228         if (other is PathPattern) {
   229             if (collection xor other.collection || nodePatterns.size != other.nodePatterns.size) return false
   230             for (i in nodePatterns.indices) {
   231                 val left = nodePatterns[i]
   232                 val right = other.nodePatterns[i]
   233                 if (left.startsWith("%") && right.startsWith("%")) continue
   234                 if (left != right) return false
   235             }
   236             return true
   237         } else {
   238             return false
   239         }
   240     }
   242     init {
   243         nodePatterns = parse(pattern)
   244         collection = pattern.endsWith("/")
   245     }
   246 }

mercurial