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

Tue, 03 Aug 2021 12:22:10 +0200

author
Mike Becker <universe@uap-core.de>
date
Tue, 03 Aug 2021 12:22:10 +0200
changeset 208
785820da6485
parent 207
479dd7993ef9
child 209
c9c6abf167c7
permissions
-rw-r--r--

fixes response locale not set for new sessions

     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 java.util.*
    32 import javax.servlet.http.HttpServletRequest
    33 import javax.servlet.http.HttpServletResponse
    34 import javax.servlet.http.HttpSession
    35 import kotlin.math.min
    37 typealias MappingMethod = (HttpRequest, DataAccessObject) -> Unit
    38 typealias PathParameters = Map<String, String>
    40 class HttpRequest(
    41     val request: HttpServletRequest,
    42     val response: HttpServletResponse,
    43     val pathParams: PathParameters = emptyMap()
    44 ) {
    45     val session: HttpSession = request.session
    47     val remoteUser: String? = request.remoteUser
    49     /**
    50      * The name of the content page.
    51      *
    52      * @see Constants#REQ_ATTR_CONTENT_PAGE
    53      */
    54     var contentPage = ""
    55         set(value) {
    56             field = value
    57             request.setAttribute(Constants.REQ_ATTR_CONTENT_PAGE, jspPath(value))
    58         }
    60     /**
    61      * The name of the content page.
    62      *
    63      * @see Constants#REQ_ATTR_PAGE_TITLE
    64      */
    65     var pageTitle = ""
    66         set(value) {
    67             field = value
    68             request.setAttribute(Constants.REQ_ATTR_PAGE_TITLE, value)
    69         }
    71     /**
    72      * A list of additional style sheets.
    73      *
    74      * @see Constants#REQ_ATTR_STYLESHEET
    75      */
    76     var styleSheets = emptyList<String>()
    77         set(value) {
    78             field = value
    79             request.setAttribute(Constants.REQ_ATTR_STYLESHEET,
    80                 value.map { it.withExt(".css") }
    81             )
    82         }
    84     /**
    85      * A list of additional style sheets.
    86      *
    87      * @see Constants#REQ_ATTR_JAVASCRIPT
    88      */
    89     var javascript = ""
    90         set(value) {
    91             field = value
    92             request.setAttribute(Constants.REQ_ATTR_JAVASCRIPT,
    93                 value.withExt(".js")
    94             )
    95         }
    97     /**
    98      * The name of the navigation menu JSP.
    99      *
   100      * @see Constants#REQ_ATTR_NAVIGATION
   101      */
   102     var navigationMenu: NavMenu? = null
   103         set(value) {
   104             field = value
   105             request.setAttribute(Constants.REQ_ATTR_NAVIGATION, navigationMenu)
   106         }
   108     var redirectLocation: String? = null
   109         set(value) {
   110             field = value
   111             if (value == null) {
   112                 request.removeAttribute(Constants.REQ_ATTR_REDIRECT_LOCATION)
   113             } else {
   114                 request.setAttribute(Constants.REQ_ATTR_REDIRECT_LOCATION, baseHref + value)
   115             }
   116         }
   118     var feedPath: String? = null
   119         set(value) {
   120             field = value
   121             if (value == null) {
   122                 request.removeAttribute(Constants.REQ_ATTR_FEED_HREF)
   123             } else {
   124                 request.setAttribute(Constants.REQ_ATTR_FEED_HREF, baseHref + value)
   125             }
   126         }
   128     /**
   129      * The view object.
   130      *
   131      * @see Constants#REQ_ATTR_VIEWMODEL
   132      */
   133     var view: View? = null
   134         set(value) {
   135             field = value
   136             request.setAttribute(Constants.REQ_ATTR_VIEWMODEL, value)
   137         }
   139     /**
   140      * Additional port info, if necessary.
   141      */
   142     private val portInfo =
   143         if ((request.scheme == "http" && request.serverPort == 80)
   144             || (request.scheme == "https" && request.serverPort == 443)
   145         ) "" else ":${request.serverPort}"
   147     /**
   148      * The base path of this application.
   149      */
   150     val baseHref get() = "${request.scheme}://${request.serverName}$portInfo${request.contextPath}/"
   152     private fun String.withExt(ext: String) = if (endsWith(ext)) this else plus(ext)
   153     private fun jspPath(name: String) = Constants.JSP_PATH_PREFIX.plus(name).withExt(".jsp")
   155     fun param(name: String): String? = request.getParameter(name)
   156     fun paramArray(name: String): Array<String> = request.getParameterValues(name) ?: emptyArray()
   158     private fun forward(jsp: String) {
   159         request.getRequestDispatcher(jspPath(jsp)).forward(request, response)
   160     }
   162     fun renderFeed(page: String? = null) {
   163         page?.let { contentPage = it }
   164         forward("feed")
   165     }
   167     fun render(page: String? = null) {
   168         page?.let { contentPage = it }
   169         forward("site")
   170     }
   172     fun renderCommit(location: String? = null) {
   173         location?.let { redirectLocation = it }
   174         contentPage = Constants.JSP_COMMIT_SUCCESSFUL
   175         render()
   176     }
   178     fun i18n(key: String) = ResourceBundle.getBundle("localization/strings", response.locale).getString(key)
   179 }
   181 /**
   182  * A path pattern optionally containing placeholders.
   183  *
   184  * The special directories . and .. are disallowed in the pattern.
   185  * Placeholders start with a % sign.
   186  *
   187  * @param pattern the pattern
   188  */
   189 class PathPattern(pattern: String) {
   190     private val nodePatterns: List<String>
   191     private val collection: Boolean
   193     private fun parse(pattern: String): List<String> {
   194         val nodes = pattern.split("/").filter { it.isNotBlank() }.toList()
   195         require(nodes.none { it == "." || it == ".." }) { "Path must not contain '.' or '..' nodes." }
   196         return nodes
   197     }
   199     /**
   200      * Matches a path against this pattern.
   201      * The path must be canonical in the sense that no . or .. parts occur.
   202      *
   203      * @param path the path to match
   204      * @return true if the path matches the pattern, false otherwise
   205      */
   206     fun matches(path: String): Boolean {
   207         if (collection xor path.endsWith("/")) return false
   208         val nodes = parse(path)
   209         if (nodePatterns.size != nodes.size) return false
   210         for (i in nodePatterns.indices) {
   211             val pattern = nodePatterns[i]
   212             val node = nodes[i]
   213             if (pattern.startsWith("%")) continue
   214             if (pattern != node) return false
   215         }
   216         return true
   217     }
   219     /**
   220      * Returns the path parameters found in the specified path using this pattern.
   221      * The return value of this method is undefined, if the patter does not match.
   222      *
   223      * @param path the path
   224      * @return the path parameters, if any, or an empty map
   225      * @see .matches
   226      */
   227     fun obtainPathParameters(path: String): PathParameters {
   228         val params = mutableMapOf<String, String>()
   229         val nodes = parse(path)
   230         for (i in 0 until min(nodes.size, nodePatterns.size)) {
   231             val pattern = nodePatterns[i]
   232             val node = nodes[i]
   233             if (pattern.startsWith("%")) {
   234                 params[pattern.substring(1)] = node
   235             }
   236         }
   237         return params
   238     }
   240     override fun hashCode(): Int {
   241         val str = StringBuilder()
   242         for (node in nodePatterns) {
   243             if (node.startsWith("%")) {
   244                 str.append("/%")
   245             } else {
   246                 str.append('/')
   247                 str.append(node)
   248             }
   249         }
   250         if (collection) str.append('/')
   251         return str.toString().hashCode()
   252     }
   254     override fun equals(other: Any?): Boolean {
   255         if (other is PathPattern) {
   256             if (collection xor other.collection || nodePatterns.size != other.nodePatterns.size) return false
   257             for (i in nodePatterns.indices) {
   258                 val left = nodePatterns[i]
   259                 val right = other.nodePatterns[i]
   260                 if (left.startsWith("%") && right.startsWith("%")) continue
   261                 if (left != right) return false
   262             }
   263             return true
   264         } else {
   265             return false
   266         }
   267     }
   269     init {
   270         nodePatterns = parse(pattern)
   271         collection = pattern.endsWith("/")
   272     }
   273 }

mercurial