Mon, 09 Aug 2021 15:50:37 +0200
#105 fixes wrong font for form controls
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.DataSourceProvider.Companion.SC_ATTR_NAME
29 import de.uapcore.lightpit.dao.DataAccessObject
30 import de.uapcore.lightpit.dao.createDataAccessObject
31 import java.sql.SQLException
32 import java.util.*
33 import javax.servlet.http.HttpServlet
34 import javax.servlet.http.HttpServletRequest
35 import javax.servlet.http.HttpServletResponse
37 abstract class AbstractServlet : LoggingTrait, HttpServlet() {
39 /**
40 * Contains the GET request mappings.
41 */
42 private val getMappings = mutableMapOf<PathPattern, MappingMethod>()
44 /**
45 * Contains the POST request mappings.
46 */
47 private val postMappings = mutableMapOf<PathPattern, MappingMethod>()
49 protected fun get(pattern: String, method: MappingMethod) {
50 getMappings[PathPattern(pattern)] = method
51 }
53 protected fun post(pattern: String, method: MappingMethod) {
54 postMappings[PathPattern(pattern)] = method
55 }
57 private fun notFound(http: HttpRequest, dao: DataAccessObject) {
58 http.response.sendError(HttpServletResponse.SC_NOT_FOUND)
59 }
61 private fun findMapping(
62 mappings: Map<PathPattern, MappingMethod>,
63 req: HttpServletRequest
64 ): Pair<PathPattern, MappingMethod> {
65 val requestPath = sanitizedRequestPath(req)
66 val candidates = mappings.filter { it.key.matches(requestPath) }
67 return if (candidates.isEmpty()) {
68 Pair(PathPattern(requestPath), ::notFound)
69 } else {
70 if (candidates.size > 1) {
71 logger().warn("Ambiguous mapping for request path '{}'", requestPath)
72 }
73 candidates.entries.first().toPair()
74 }
75 }
77 private fun invokeMapping(
78 mapping: Pair<PathPattern, MappingMethod>,
79 req: HttpServletRequest,
80 resp: HttpServletResponse,
81 dao: DataAccessObject
82 ) {
83 val params = mapping.first.obtainPathParameters(sanitizedRequestPath(req))
84 val method = mapping.second
85 logger().trace("invoke {}", method)
86 method(HttpRequest(req, resp, params), dao)
87 }
89 private fun sanitizedRequestPath(req: HttpServletRequest) = req.pathInfo ?: "/"
91 private fun doProcess(
92 req: HttpServletRequest,
93 resp: HttpServletResponse,
94 mappings: Map<PathPattern, MappingMethod>
95 ) {
96 val session = req.session
98 // the very first thing to do is to force UTF-8
99 req.characterEncoding = "UTF-8"
101 // choose the requested language as session language (if available) or fall back to english, otherwise
102 if (session.getAttribute(Constants.SESSION_ATTR_LANGUAGE) == null) {
103 val availableLanguages = availableLanguages()
104 val reqLocale = req.locale
105 val sessionLocale = if (availableLanguages.contains(reqLocale)) reqLocale else availableLanguages.first()
106 session.setAttribute(Constants.SESSION_ATTR_LANGUAGE, sessionLocale)
107 resp.locale = sessionLocale
108 logger().debug(
109 "Setting language for new session {}: {}", session.id, sessionLocale.displayLanguage
110 )
111 } else {
112 val sessionLocale = session.getAttribute(Constants.SESSION_ATTR_LANGUAGE) as Locale
113 resp.locale = sessionLocale
114 logger().trace("Continuing session {} with language {}", session.id, sessionLocale)
115 }
117 // set some internal request attributes
118 val http = HttpRequest(req, resp)
119 val fullPath = req.servletPath + Optional.ofNullable(req.pathInfo).orElse("")
120 req.setAttribute(Constants.REQ_ATTR_BASE_HREF, http.baseHref)
121 req.setAttribute(Constants.REQ_ATTR_PATH, fullPath)
122 req.getHeader("Referer")?.let {
123 // TODO: add a sanity check to avoid link injection
124 req.setAttribute(Constants.REQ_ATTR_REFERER, it)
125 }
127 // if this is an error path, bypass the normal flow
128 if (fullPath.startsWith("/error/")) {
129 http.styleSheets = listOf("error")
130 http.render("error")
131 return
132 }
134 // obtain a connection and create the data access objects
135 val dsp = req.servletContext.getAttribute(SC_ATTR_NAME) as DataSourceProvider
136 val dialect = dsp.dialect
137 val ds = dsp.dataSource
138 if (ds == null) {
139 resp.sendError(
140 HttpServletResponse.SC_SERVICE_UNAVAILABLE,
141 "JNDI DataSource lookup failed. See log for details."
142 )
143 return
144 }
145 try {
146 ds.connection.use { connection ->
147 val dao = createDataAccessObject(dialect, connection)
148 try {
149 connection.autoCommit = false
150 invokeMapping(findMapping(mappings, req), req, resp, dao)
151 connection.commit()
152 } catch (ex: SQLException) {
153 logger().warn("Database transaction failed (Code {}): {}", ex.errorCode, ex.message)
154 logger().debug("Details: ", ex)
155 resp.sendError(
156 HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
157 "Unhandled Transaction Error - Code: " + ex.errorCode
158 )
159 connection.rollback()
160 }
161 }
162 } catch (ex: SQLException) {
163 logger().error("Severe Database Exception (Code {}): {}", ex.errorCode, ex.message)
164 logger().debug("Details: ", ex)
165 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Database Error - Code: " + ex.errorCode)
166 }
167 }
169 override fun doGet(req: HttpServletRequest, resp: HttpServletResponse) {
170 doProcess(req, resp, getMappings)
171 }
173 override fun doPost(req: HttpServletRequest, resp: HttpServletResponse) {
174 doProcess(req, resp, postMappings)
175 }
177 protected fun availableLanguages(): List<Locale> {
178 val langTags = servletContext.getInitParameter(Constants.CTX_ATTR_LANGUAGES)?.split(",")?.map(String::trim) ?: emptyList()
179 val locales = langTags.map(Locale::forLanguageTag).filter { it.language.isNotEmpty() }
180 return locales.ifEmpty { listOf(Locale.ENGLISH) }
181 }
183 }