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

Sun, 23 Jul 2023 18:17:03 +0200

author
Mike Becker <universe@uap-core.de>
date
Sun, 23 Jul 2023 18:17:03 +0200
changeset 285
8da71efbaa35
parent 254
55ca6cafc3dd
child 310
bbf4eb9a71f8
permissions
-rw-r--r--

add SQL patch script

relates to #276

/*
 * Copyright 2021 Mike Becker. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package de.uapcore.lightpit

import jakarta.servlet.ServletContextEvent
import jakarta.servlet.ServletContextListener
import jakarta.servlet.annotation.WebListener
import java.sql.SQLException
import javax.naming.Context
import javax.naming.InitialContext
import javax.naming.NamingException
import javax.sql.DataSource

/**
 * Provides access to the database.
 */
@WebListener
class DataSourceProvider : ServletContextListener {

    private val logger = MyLogger()

    enum class Dialect {
        Postgres
    }

    /**
     * The database dialect to use.
     * May be overridden by context parameter.
     *
     * @see Constants.CTX_ATTR_DB_DIALECT
     */
    var dialect = Dialect.Postgres; private set

    /**
     * The data source, if available.
     */
    var dataSource: DataSource? = null

    companion object {
        /**
         * The attribute name in the Servlet context under which an instance of this class can be found.
         */
        const val SC_ATTR_NAME = "lightpit.service.DataSourceProvider"

        /**
         * Timeout in seconds for the validation test.
         */
        private const val DB_TEST_TIMEOUT = 10

        /**
         * The default schema to test against when validating the connection.
         * May be overridden by context parameter.
         *
         * @see Constants.CTX_ATTR_DB_SCHEMA
         */
        private const val DB_DEFAULT_SCHEMA = "lightpit"

        /**
         * The JNDI resource name for the data source.
         */
        private const val DS_JNDI_NAME = "jdbc/lightpit/app"
    }

    private fun checkConnection(ds: DataSource, testSchema: String) {
        try {
            ds.connection.use { conn ->
                if (!conn.isValid(DB_TEST_TIMEOUT)) {
                    throw SQLException("Validation check failed.")
                }
                if (conn.isReadOnly) {
                    throw SQLException("Connection is read-only and thus unusable.")
                }
                if (conn.schema != testSchema) {
                    throw SQLException(
                        String.format(
                            "Connection is not configured to use the schema %s.",
                            testSchema
                        )
                    )
                }
                val metaData = conn.metaData
                logger.info(
                    "Connections as {} to {}/{} ready to go.",
                    metaData.userName,
                    metaData.url,
                    conn.schema
                )
            }
        } catch (ex: SQLException) {
            logger.error("Checking database connection failed", ex)
        }
    }

    private fun retrieveDataSource(ctx: Context): DataSource? {
        return try {
            val ret = ctx.lookup(DS_JNDI_NAME) as DataSource
            logger.info("Data source retrieved.")
            ret
        } catch (ex: NamingException) {
            logger.error("Data source {0} not available.", DS_JNDI_NAME)
            logger.error("Reason for the missing data source: ", ex)
            null
        }
    }

    override fun contextInitialized(sce: ServletContextEvent?) {
        val sc = sce!!.servletContext

        val dbSchema = sc.getInitParameter(Constants.CTX_ATTR_DB_SCHEMA) ?: DB_DEFAULT_SCHEMA
        sc.getInitParameter(Constants.CTX_ATTR_DB_DIALECT)?.let { dbDialect ->
            try {
                dialect = Dialect.valueOf(dbDialect)
            } catch (ex: IllegalArgumentException) {
                logger.error(
                    "Unknown or unsupported database dialect {0}. Defaulting to {1}.",
                    dbDialect,
                    dialect
                )
            }
        }

        dataSource = try {
            logger.debug("Trying to access JNDI context ...")
            val initialCtx: Context = InitialContext()
            val ctx = initialCtx.lookup("java:comp/env") as Context
            retrieveDataSource(ctx)
        } catch (ex: NamingException) {
            logger.error("Cannot access JNDI resources.", ex)
            null
        } catch (ex: ClassCastException) {
            logger.error("Cannot access JNDI resources.", ex)
            null
        }

        dataSource?.let { checkConnection(it, dbSchema) }

        sc.setAttribute(SC_ATTR_NAME, this)
        logger.info("Database facade injected into ServletContext.")
    }

    override fun contextDestroyed(sce: ServletContextEvent?) {
        dataSource = null
    }
}

mercurial