Wed, 28 Dec 2022 13:21:30 +0100
#233 migrate to Jakarta EE and update dependencies
universe@151 | 1 | /* |
universe@180 | 2 | * Copyright 2021 Mike Becker. All rights reserved. |
universe@151 | 3 | * |
universe@151 | 4 | * Redistribution and use in source and binary forms, with or without |
universe@151 | 5 | * modification, are permitted provided that the following conditions are met: |
universe@151 | 6 | * |
universe@151 | 7 | * 1. Redistributions of source code must retain the above copyright |
universe@151 | 8 | * notice, this list of conditions and the following disclaimer. |
universe@151 | 9 | * |
universe@151 | 10 | * 2. Redistributions in binary form must reproduce the above copyright |
universe@151 | 11 | * notice, this list of conditions and the following disclaimer in the |
universe@151 | 12 | * documentation and/or other materials provided with the distribution. |
universe@151 | 13 | * |
universe@151 | 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
universe@151 | 15 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
universe@151 | 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
universe@151 | 17 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE |
universe@151 | 18 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
universe@151 | 19 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
universe@151 | 20 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
universe@151 | 21 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
universe@151 | 22 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
universe@151 | 23 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
universe@151 | 24 | */ |
universe@151 | 25 | |
universe@151 | 26 | package de.uapcore.lightpit |
universe@151 | 27 | |
universe@254 | 28 | import jakarta.servlet.ServletContextEvent |
universe@254 | 29 | import jakarta.servlet.ServletContextListener |
universe@254 | 30 | import jakarta.servlet.annotation.WebListener |
universe@151 | 31 | import java.sql.SQLException |
universe@151 | 32 | import javax.naming.Context |
universe@151 | 33 | import javax.naming.InitialContext |
universe@151 | 34 | import javax.naming.NamingException |
universe@151 | 35 | import javax.sql.DataSource |
universe@151 | 36 | |
universe@151 | 37 | /** |
universe@151 | 38 | * Provides access to the database. |
universe@151 | 39 | */ |
universe@151 | 40 | @WebListener |
universe@247 | 41 | class DataSourceProvider : ServletContextListener { |
universe@247 | 42 | |
universe@247 | 43 | private val logger = MyLogger() |
universe@151 | 44 | |
universe@158 | 45 | enum class Dialect { |
universe@158 | 46 | Postgres |
universe@158 | 47 | } |
universe@158 | 48 | |
universe@151 | 49 | /** |
universe@151 | 50 | * The database dialect to use. |
universe@151 | 51 | * May be overridden by context parameter. |
universe@151 | 52 | * |
universe@151 | 53 | * @see Constants.CTX_ATTR_DB_DIALECT |
universe@151 | 54 | */ |
universe@158 | 55 | var dialect = Dialect.Postgres; private set |
universe@151 | 56 | |
universe@151 | 57 | /** |
universe@151 | 58 | * The data source, if available. |
universe@151 | 59 | */ |
universe@151 | 60 | var dataSource: DataSource? = null |
universe@151 | 61 | |
universe@151 | 62 | companion object { |
universe@151 | 63 | /** |
universe@151 | 64 | * The attribute name in the Servlet context under which an instance of this class can be found. |
universe@151 | 65 | */ |
universe@184 | 66 | const val SC_ATTR_NAME = "lightpit.service.DataSourceProvider" |
universe@151 | 67 | |
universe@151 | 68 | /** |
universe@151 | 69 | * Timeout in seconds for the validation test. |
universe@151 | 70 | */ |
universe@184 | 71 | private const val DB_TEST_TIMEOUT = 10 |
universe@151 | 72 | |
universe@151 | 73 | /** |
universe@151 | 74 | * The default schema to test against when validating the connection. |
universe@151 | 75 | * May be overridden by context parameter. |
universe@151 | 76 | * |
universe@151 | 77 | * @see Constants.CTX_ATTR_DB_SCHEMA |
universe@151 | 78 | */ |
universe@184 | 79 | private const val DB_DEFAULT_SCHEMA = "lightpit" |
universe@151 | 80 | |
universe@151 | 81 | /** |
universe@151 | 82 | * The JNDI resource name for the data source. |
universe@151 | 83 | */ |
universe@184 | 84 | private const val DS_JNDI_NAME = "jdbc/lightpit/app" |
universe@151 | 85 | } |
universe@151 | 86 | |
universe@151 | 87 | private fun checkConnection(ds: DataSource, testSchema: String) { |
universe@151 | 88 | try { |
universe@151 | 89 | ds.connection.use { conn -> |
universe@151 | 90 | if (!conn.isValid(DB_TEST_TIMEOUT)) { |
universe@151 | 91 | throw SQLException("Validation check failed.") |
universe@151 | 92 | } |
universe@151 | 93 | if (conn.isReadOnly) { |
universe@151 | 94 | throw SQLException("Connection is read-only and thus unusable.") |
universe@151 | 95 | } |
universe@151 | 96 | if (conn.schema != testSchema) { |
universe@151 | 97 | throw SQLException( |
universe@151 | 98 | String.format( |
universe@151 | 99 | "Connection is not configured to use the schema %s.", |
universe@151 | 100 | testSchema |
universe@151 | 101 | ) |
universe@151 | 102 | ) |
universe@151 | 103 | } |
universe@151 | 104 | val metaData = conn.metaData |
universe@247 | 105 | logger.info( |
universe@151 | 106 | "Connections as {} to {}/{} ready to go.", |
universe@151 | 107 | metaData.userName, |
universe@151 | 108 | metaData.url, |
universe@151 | 109 | conn.schema |
universe@151 | 110 | ) |
universe@151 | 111 | } |
universe@151 | 112 | } catch (ex: SQLException) { |
universe@247 | 113 | logger.error("Checking database connection failed", ex) |
universe@151 | 114 | } |
universe@151 | 115 | } |
universe@151 | 116 | |
universe@151 | 117 | private fun retrieveDataSource(ctx: Context): DataSource? { |
universe@151 | 118 | return try { |
universe@151 | 119 | val ret = ctx.lookup(DS_JNDI_NAME) as DataSource |
universe@247 | 120 | logger.info("Data source retrieved.") |
universe@151 | 121 | ret |
universe@151 | 122 | } catch (ex: NamingException) { |
universe@247 | 123 | logger.error("Data source {0} not available.", DS_JNDI_NAME) |
universe@247 | 124 | logger.error("Reason for the missing data source: ", ex) |
universe@151 | 125 | null |
universe@151 | 126 | } |
universe@151 | 127 | } |
universe@151 | 128 | |
universe@151 | 129 | override fun contextInitialized(sce: ServletContextEvent?) { |
universe@151 | 130 | val sc = sce!!.servletContext |
universe@151 | 131 | |
universe@151 | 132 | val dbSchema = sc.getInitParameter(Constants.CTX_ATTR_DB_SCHEMA) ?: DB_DEFAULT_SCHEMA |
universe@167 | 133 | sc.getInitParameter(Constants.CTX_ATTR_DB_DIALECT)?.let { dbDialect -> |
universe@151 | 134 | try { |
universe@158 | 135 | dialect = Dialect.valueOf(dbDialect) |
universe@151 | 136 | } catch (ex: IllegalArgumentException) { |
universe@247 | 137 | logger.error( |
universe@247 | 138 | "Unknown or unsupported database dialect {0}. Defaulting to {1}.", |
universe@151 | 139 | dbDialect, |
universe@151 | 140 | dialect |
universe@151 | 141 | ) |
universe@151 | 142 | } |
universe@151 | 143 | } |
universe@151 | 144 | |
universe@151 | 145 | dataSource = try { |
universe@247 | 146 | logger.debug("Trying to access JNDI context ...") |
universe@151 | 147 | val initialCtx: Context = InitialContext() |
universe@151 | 148 | val ctx = initialCtx.lookup("java:comp/env") as Context |
universe@151 | 149 | retrieveDataSource(ctx) |
universe@151 | 150 | } catch (ex: NamingException) { |
universe@247 | 151 | logger.error("Cannot access JNDI resources.", ex) |
universe@151 | 152 | null |
universe@151 | 153 | } catch (ex: ClassCastException) { |
universe@247 | 154 | logger.error("Cannot access JNDI resources.", ex) |
universe@151 | 155 | null |
universe@151 | 156 | } |
universe@151 | 157 | |
universe@151 | 158 | dataSource?.let { checkConnection(it, dbSchema) } |
universe@151 | 159 | |
universe@151 | 160 | sc.setAttribute(SC_ATTR_NAME, this) |
universe@247 | 161 | logger.info("Database facade injected into ServletContext.") |
universe@151 | 162 | } |
universe@151 | 163 | |
universe@151 | 164 | override fun contextDestroyed(sce: ServletContextEvent?) { |
universe@151 | 165 | dataSource = null |
universe@151 | 166 | } |
universe@151 | 167 | } |