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

Fri, 06 Nov 2020 10:50:32 +0100

author
Mike Becker <universe@uap-core.de>
date
Fri, 06 Nov 2020 10:50:32 +0100
changeset 158
4f912cd42876
parent 151
b3f14cd4f3ab
child 167
3f30adba1c63
permissions
-rw-r--r--

migrates constants and removes global functions

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

mercurial