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

Wed, 28 Dec 2022 13:21:30 +0100

author
Mike Becker <universe@uap-core.de>
date
Wed, 28 Dec 2022 13:21:30 +0100
changeset 254
55ca6cafc3dd
parent 247
e71ae69c68c0
permissions
-rw-r--r--

#233 migrate to Jakarta EE and update dependencies

     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 jakarta.servlet.ServletContextEvent
    29 import jakarta.servlet.ServletContextListener
    30 import jakarta.servlet.annotation.WebListener
    31 import java.sql.SQLException
    32 import javax.naming.Context
    33 import javax.naming.InitialContext
    34 import javax.naming.NamingException
    35 import javax.sql.DataSource
    37 /**
    38  * Provides access to the database.
    39  */
    40 @WebListener
    41 class DataSourceProvider : ServletContextListener {
    43     private val logger = MyLogger()
    45     enum class Dialect {
    46         Postgres
    47     }
    49     /**
    50      * The database dialect to use.
    51      * May be overridden by context parameter.
    52      *
    53      * @see Constants.CTX_ATTR_DB_DIALECT
    54      */
    55     var dialect = Dialect.Postgres; private set
    57     /**
    58      * The data source, if available.
    59      */
    60     var dataSource: DataSource? = null
    62     companion object {
    63         /**
    64          * The attribute name in the Servlet context under which an instance of this class can be found.
    65          */
    66         const val SC_ATTR_NAME = "lightpit.service.DataSourceProvider"
    68         /**
    69          * Timeout in seconds for the validation test.
    70          */
    71         private const val DB_TEST_TIMEOUT = 10
    73         /**
    74          * The default schema to test against when validating the connection.
    75          * May be overridden by context parameter.
    76          *
    77          * @see Constants.CTX_ATTR_DB_SCHEMA
    78          */
    79         private const val DB_DEFAULT_SCHEMA = "lightpit"
    81         /**
    82          * The JNDI resource name for the data source.
    83          */
    84         private const val DS_JNDI_NAME = "jdbc/lightpit/app"
    85     }
    87     private fun checkConnection(ds: DataSource, testSchema: String) {
    88         try {
    89             ds.connection.use { conn ->
    90                 if (!conn.isValid(DB_TEST_TIMEOUT)) {
    91                     throw SQLException("Validation check failed.")
    92                 }
    93                 if (conn.isReadOnly) {
    94                     throw SQLException("Connection is read-only and thus unusable.")
    95                 }
    96                 if (conn.schema != testSchema) {
    97                     throw SQLException(
    98                         String.format(
    99                             "Connection is not configured to use the schema %s.",
   100                             testSchema
   101                         )
   102                     )
   103                 }
   104                 val metaData = conn.metaData
   105                 logger.info(
   106                     "Connections as {} to {}/{} ready to go.",
   107                     metaData.userName,
   108                     metaData.url,
   109                     conn.schema
   110                 )
   111             }
   112         } catch (ex: SQLException) {
   113             logger.error("Checking database connection failed", ex)
   114         }
   115     }
   117     private fun retrieveDataSource(ctx: Context): DataSource? {
   118         return try {
   119             val ret = ctx.lookup(DS_JNDI_NAME) as DataSource
   120             logger.info("Data source retrieved.")
   121             ret
   122         } catch (ex: NamingException) {
   123             logger.error("Data source {0} not available.", DS_JNDI_NAME)
   124             logger.error("Reason for the missing data source: ", ex)
   125             null
   126         }
   127     }
   129     override fun contextInitialized(sce: ServletContextEvent?) {
   130         val sc = sce!!.servletContext
   132         val dbSchema = sc.getInitParameter(Constants.CTX_ATTR_DB_SCHEMA) ?: DB_DEFAULT_SCHEMA
   133         sc.getInitParameter(Constants.CTX_ATTR_DB_DIALECT)?.let { dbDialect ->
   134             try {
   135                 dialect = Dialect.valueOf(dbDialect)
   136             } catch (ex: IllegalArgumentException) {
   137                 logger.error(
   138                     "Unknown or unsupported database dialect {0}. Defaulting to {1}.",
   139                     dbDialect,
   140                     dialect
   141                 )
   142             }
   143         }
   145         dataSource = try {
   146             logger.debug("Trying to access JNDI context ...")
   147             val initialCtx: Context = InitialContext()
   148             val ctx = initialCtx.lookup("java:comp/env") as Context
   149             retrieveDataSource(ctx)
   150         } catch (ex: NamingException) {
   151             logger.error("Cannot access JNDI resources.", ex)
   152             null
   153         } catch (ex: ClassCastException) {
   154             logger.error("Cannot access JNDI resources.", ex)
   155             null
   156         }
   158         dataSource?.let { checkConnection(it, dbSchema) }
   160         sc.setAttribute(SC_ATTR_NAME, this)
   161         logger.info("Database facade injected into ServletContext.")
   162     }
   164     override fun contextDestroyed(sce: ServletContextEvent?) {
   165         dataSource = null
   166     }
   167 }

mercurial