migrate DataSourceProvider

Sat, 24 Oct 2020 12:09:08 +0200

author
Mike Becker <universe@uap-core.de>
date
Sat, 24 Oct 2020 12:09:08 +0200
changeset 151
b3f14cd4f3ab
parent 150
822b7e3d064d
child 152
7761c37c5e61

migrate DataSourceProvider

build.gradle.kts file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/AbstractLightPITServlet.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/DatabaseFacade.java file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/DataSourceProvider.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/Logging.kt file | annotate | diff | comparison | revisions
     1.1 --- a/build.gradle.kts	Fri Oct 23 20:34:57 2020 +0200
     1.2 +++ b/build.gradle.kts	Sat Oct 24 12:09:08 2020 +0200
     1.3 @@ -1,3 +1,4 @@
     1.4 +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
     1.5  
     1.6  plugins {
     1.7      kotlin("jvm") version "1.4.10"
     1.8 @@ -13,6 +14,10 @@
     1.9      mavenCentral()
    1.10  }
    1.11  
    1.12 +tasks.withType<KotlinCompile>().configureEach {
    1.13 +    kotlinOptions.jvmTarget = "11"
    1.14 +}
    1.15 +
    1.16  tasks.war {
    1.17      archiveFileName.set("lightpit.war")
    1.18      from("src/main/resources")
     2.1 --- a/src/main/java/de/uapcore/lightpit/AbstractLightPITServlet.java	Fri Oct 23 20:34:57 2020 +0200
     2.2 +++ b/src/main/java/de/uapcore/lightpit/AbstractLightPITServlet.java	Sat Oct 24 12:09:08 2020 +0200
     2.3 @@ -101,11 +101,11 @@
     2.4       * @return a set of data access objects
     2.5       */
     2.6      private DataAccessObjects createDataAccessObjects(Connection connection) throws SQLException {
     2.7 -        final var df = (DatabaseFacade) getServletContext().getAttribute(DatabaseFacade.SC_ATTR_NAME);
     2.8 -        if (df.getSQLDialect() == DatabaseFacade.Dialect.Postgres) {
     2.9 +        final var df = (DataSourceProvider) getServletContext().getAttribute(DataSourceProvider.Companion.getSC_ATTR_NAME());
    2.10 +        if (df.getDialect() == DatabaseDialect.Postgres) {
    2.11              return new PGDataAccessObjects(connection);
    2.12          }
    2.13 -        throw new AssertionError("Non-exhaustive if-else - this is a bug.");
    2.14 +        throw new UnsupportedOperationException("Non-exhaustive if-else - this is a bug.");
    2.15      }
    2.16  
    2.17      private ResponseType invokeMapping(Map.Entry<PathPattern, Method> mapping, HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws IOException {
    2.18 @@ -434,7 +434,7 @@
    2.19          }
    2.20  
    2.21          // obtain a connection and create the data access objects
    2.22 -        final var db = (DatabaseFacade) req.getServletContext().getAttribute(DatabaseFacade.SC_ATTR_NAME);
    2.23 +        final var db = (DataSourceProvider) req.getServletContext().getAttribute(DataSourceProvider.Companion.getSC_ATTR_NAME());
    2.24          final var ds = db.getDataSource();
    2.25          if (ds == null) {
    2.26              resp.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, "JNDI DataSource lookup failed. See log for details.");
     3.1 --- a/src/main/java/de/uapcore/lightpit/DatabaseFacade.java	Fri Oct 23 20:34:57 2020 +0200
     3.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.3 @@ -1,176 +0,0 @@
     3.4 -/*
     3.5 - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     3.6 - *
     3.7 - * Copyright 2018 Mike Becker. All rights reserved.
     3.8 - *
     3.9 - * Redistribution and use in source and binary forms, with or without
    3.10 - * modification, are permitted provided that the following conditions are met:
    3.11 - *
    3.12 - *   1. Redistributions of source code must retain the above copyright
    3.13 - *      notice, this list of conditions and the following disclaimer.
    3.14 - *
    3.15 - *   2. Redistributions in binary form must reproduce the above copyright
    3.16 - *      notice, this list of conditions and the following disclaimer in the
    3.17 - *      documentation and/or other materials provided with the distribution.
    3.18 - *
    3.19 - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    3.20 - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    3.21 - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    3.22 - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
    3.23 - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
    3.24 - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
    3.25 - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
    3.26 - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
    3.27 - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    3.28 - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    3.29 - * POSSIBILITY OF SUCH DAMAGE.
    3.30 - *
    3.31 - */
    3.32 -package de.uapcore.lightpit;
    3.33 -
    3.34 -import org.slf4j.Logger;
    3.35 -import org.slf4j.LoggerFactory;
    3.36 -
    3.37 -import javax.naming.Context;
    3.38 -import javax.naming.InitialContext;
    3.39 -import javax.naming.NamingException;
    3.40 -import javax.servlet.ServletContext;
    3.41 -import javax.servlet.ServletContextEvent;
    3.42 -import javax.servlet.ServletContextListener;
    3.43 -import javax.servlet.annotation.WebListener;
    3.44 -import javax.sql.DataSource;
    3.45 -import java.sql.Connection;
    3.46 -import java.sql.DatabaseMetaData;
    3.47 -import java.sql.SQLException;
    3.48 -import java.util.Optional;
    3.49 -
    3.50 -/**
    3.51 - * Provides access to different privilege layers within the database.
    3.52 - */
    3.53 -@WebListener
    3.54 -public final class DatabaseFacade implements ServletContextListener {
    3.55 -
    3.56 -    private static final Logger LOG = LoggerFactory.getLogger(DatabaseFacade.class);
    3.57 -
    3.58 -    /**
    3.59 -     * Timeout in seconds for the validation test.
    3.60 -     */
    3.61 -    private static final int DB_TEST_TIMEOUT = 10;
    3.62 -
    3.63 -    /**
    3.64 -     * Specifies the database dialect.
    3.65 -     */
    3.66 -    public enum Dialect {
    3.67 -        Postgres
    3.68 -    }
    3.69 -
    3.70 -    /**
    3.71 -     * The database dialect to use.
    3.72 -     * <p>
    3.73 -     * May be overridden by context parameter.
    3.74 -     *
    3.75 -     * @see Constants#CTX_ATTR_DB_DIALECT
    3.76 -     */
    3.77 -    private Dialect dialect = Dialect.Postgres;
    3.78 -
    3.79 -    /**
    3.80 -     * The default schema to test against when validating the connection.
    3.81 -     * <p>
    3.82 -     * May be overridden by context parameter.
    3.83 -     *
    3.84 -     * @see Constants#CTX_ATTR_DB_SCHEMA
    3.85 -     */
    3.86 -    private static final String DB_DEFAULT_SCHEMA = "lightpit";
    3.87 -
    3.88 -    /**
    3.89 -     * The attribute name in the Servlet context under which an instance of this class can be found.
    3.90 -     */
    3.91 -    public static final String SC_ATTR_NAME = DatabaseFacade.class.getName();
    3.92 -
    3.93 -    private static final String DS_JNDI_NAME = "jdbc/lightpit/app";
    3.94 -    private DataSource dataSource;
    3.95 -
    3.96 -    /**
    3.97 -     * Returns the data source.
    3.98 -     *
    3.99 -     * @return a data source
   3.100 -     */
   3.101 -    public DataSource getDataSource() {
   3.102 -        return dataSource;
   3.103 -    }
   3.104 -
   3.105 -    public Dialect getSQLDialect() {
   3.106 -        return dialect;
   3.107 -    }
   3.108 -
   3.109 -    private static void checkConnection(DataSource ds, String testSchema) {
   3.110 -        try (Connection conn = ds.getConnection()) {
   3.111 -            if (!conn.isValid(DB_TEST_TIMEOUT)) {
   3.112 -                throw new SQLException("Validation check failed.");
   3.113 -            }
   3.114 -            if (conn.isReadOnly()) {
   3.115 -                throw new SQLException("Connection is read-only and thus unusable.");
   3.116 -            }
   3.117 -            if (!conn.getSchema().equals(testSchema)) {
   3.118 -                throw new SQLException(String.format("Connection is not configured to use the schema %s.", testSchema));
   3.119 -            }
   3.120 -            DatabaseMetaData metaData = conn.getMetaData();
   3.121 -            LOG.info("Connections as {} to {}/{} ready to go.", metaData.getUserName(), metaData.getURL(), conn.getSchema());
   3.122 -        } catch (SQLException ex) {
   3.123 -            LOG.error("Checking database connection failed", ex);
   3.124 -        }
   3.125 -    }
   3.126 -
   3.127 -    private static DataSource retrieveDataSource(Context ctx) {
   3.128 -        DataSource ret = null;
   3.129 -        try {
   3.130 -            ret = (DataSource) ctx.lookup(DS_JNDI_NAME);
   3.131 -            LOG.info("Data source retrieved.");
   3.132 -        } catch (NamingException ex) {
   3.133 -            LOG.error("Data source {} not available.", DS_JNDI_NAME);
   3.134 -            LOG.error("Reason for the missing data source: ", ex);
   3.135 -        }
   3.136 -        return ret;
   3.137 -    }
   3.138 -
   3.139 -    @Override
   3.140 -    public void contextInitialized(ServletContextEvent sce) {
   3.141 -        ServletContext sc = sce.getServletContext();
   3.142 -
   3.143 -        dataSource = null;
   3.144 -
   3.145 -        final String dbSchema = Optional
   3.146 -                .ofNullable(sc.getInitParameter(Constants.CTX_ATTR_DB_SCHEMA))
   3.147 -                .orElse(DB_DEFAULT_SCHEMA);
   3.148 -        final String dbDialect = sc.getInitParameter(Constants.CTX_ATTR_DB_DIALECT);
   3.149 -        if (dbDialect != null) {
   3.150 -            try {
   3.151 -                dialect = Dialect.valueOf(dbDialect);
   3.152 -            } catch (IllegalArgumentException ex) {
   3.153 -                LOG.error("Unknown or unsupported database dialect {}. Defaulting to {}.", dbDialect, dialect);
   3.154 -            }
   3.155 -        }
   3.156 -
   3.157 -        try {
   3.158 -            LOG.debug("Trying to access JNDI context ...");
   3.159 -            Context initialCtx = new InitialContext();
   3.160 -            Context ctx = (Context) initialCtx.lookup("java:comp/env");
   3.161 -
   3.162 -            dataSource = retrieveDataSource(ctx);
   3.163 -
   3.164 -            if (dataSource != null) {
   3.165 -                checkConnection(dataSource, dbSchema);
   3.166 -            }
   3.167 -        } catch (NamingException | ClassCastException ex) {
   3.168 -            LOG.error("Cannot access JNDI resources.", ex);
   3.169 -        }
   3.170 -
   3.171 -        sc.setAttribute(SC_ATTR_NAME, this);
   3.172 -        LOG.info("Database facade injected into ServletContext.");
   3.173 -    }
   3.174 -
   3.175 -    @Override
   3.176 -    public void contextDestroyed(ServletContextEvent sce) {
   3.177 -        dataSource = null;
   3.178 -    }
   3.179 -}
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/src/main/kotlin/de/uapcore/lightpit/DataSourceProvider.kt	Sat Oct 24 12:09:08 2020 +0200
     4.3 @@ -0,0 +1,165 @@
     4.4 +/*
     4.5 + * Copyright 2020 Mike Becker. All rights reserved.
     4.6 + *
     4.7 + * Redistribution and use in source and binary forms, with or without
     4.8 + * modification, are permitted provided that the following conditions are met:
     4.9 + *
    4.10 + * 1. Redistributions of source code must retain the above copyright
    4.11 + * notice, this list of conditions and the following disclaimer.
    4.12 + *
    4.13 + * 2. Redistributions in binary form must reproduce the above copyright
    4.14 + * notice, this list of conditions and the following disclaimer in the
    4.15 + * documentation and/or other materials provided with the distribution.
    4.16 + *
    4.17 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    4.18 + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    4.19 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    4.20 + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
    4.21 + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
    4.22 + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
    4.23 + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
    4.24 + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
    4.25 + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    4.26 + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    4.27 + */
    4.28 +
    4.29 +package de.uapcore.lightpit
    4.30 +
    4.31 +import java.sql.SQLException
    4.32 +import javax.naming.Context
    4.33 +import javax.naming.InitialContext
    4.34 +import javax.naming.NamingException
    4.35 +import javax.servlet.ServletContextEvent
    4.36 +import javax.servlet.ServletContextListener
    4.37 +import javax.servlet.annotation.WebListener
    4.38 +import javax.sql.DataSource
    4.39 +
    4.40 +enum class DatabaseDialect {
    4.41 +    Postgres
    4.42 +}
    4.43 +
    4.44 +/**
    4.45 + * Provides access to the database.
    4.46 + */
    4.47 +@WebListener
    4.48 +class DataSourceProvider : ServletContextListener, LoggingTrait {
    4.49 +
    4.50 +    /**
    4.51 +     * The database dialect to use.
    4.52 +     * May be overridden by context parameter.
    4.53 +     *
    4.54 +     * @see Constants.CTX_ATTR_DB_DIALECT
    4.55 +     */
    4.56 +    var dialect = DatabaseDialect.Postgres; private set
    4.57 +
    4.58 +    /**
    4.59 +     * The data source, if available.
    4.60 +     */
    4.61 +    var dataSource: DataSource? = null
    4.62 +
    4.63 +    companion object {
    4.64 +        /**
    4.65 +         * The attribute name in the Servlet context under which an instance of this class can be found.
    4.66 +         */
    4.67 +        val SC_ATTR_NAME = "lightpit.service.DataSourceProvider"
    4.68 +
    4.69 +        /**
    4.70 +         * Timeout in seconds for the validation test.
    4.71 +         */
    4.72 +        private val DB_TEST_TIMEOUT = 10
    4.73 +
    4.74 +        /**
    4.75 +         * The default schema to test against when validating the connection.
    4.76 +         * May be overridden by context parameter.
    4.77 +         *
    4.78 +         * @see Constants.CTX_ATTR_DB_SCHEMA
    4.79 +         */
    4.80 +        private val DB_DEFAULT_SCHEMA = "lightpit"
    4.81 +
    4.82 +        /**
    4.83 +         * The JNDI resource name for the data source.
    4.84 +         */
    4.85 +        private val DS_JNDI_NAME = "jdbc/lightpit/app"
    4.86 +    }
    4.87 +
    4.88 +    private fun checkConnection(ds: DataSource, testSchema: String) {
    4.89 +        try {
    4.90 +            ds.connection.use { conn ->
    4.91 +                if (!conn.isValid(DB_TEST_TIMEOUT)) {
    4.92 +                    throw SQLException("Validation check failed.")
    4.93 +                }
    4.94 +                if (conn.isReadOnly) {
    4.95 +                    throw SQLException("Connection is read-only and thus unusable.")
    4.96 +                }
    4.97 +                if (conn.schema != testSchema) {
    4.98 +                    throw SQLException(
    4.99 +                        String.format(
   4.100 +                            "Connection is not configured to use the schema %s.",
   4.101 +                            testSchema
   4.102 +                        )
   4.103 +                    )
   4.104 +                }
   4.105 +                val metaData = conn.metaData
   4.106 +                logger().info(
   4.107 +                    "Connections as {} to {}/{} ready to go.",
   4.108 +                    metaData.userName,
   4.109 +                    metaData.url,
   4.110 +                    conn.schema
   4.111 +                )
   4.112 +            }
   4.113 +        } catch (ex: SQLException) {
   4.114 +            logger().error("Checking database connection failed", ex)
   4.115 +        }
   4.116 +    }
   4.117 +
   4.118 +    private fun retrieveDataSource(ctx: Context): DataSource? {
   4.119 +        return try {
   4.120 +            val ret = ctx.lookup(DS_JNDI_NAME) as DataSource
   4.121 +            logger().info("Data source retrieved.")
   4.122 +            ret
   4.123 +        } catch (ex: NamingException) {
   4.124 +            logger().error("Data source {} not available.", DS_JNDI_NAME)
   4.125 +            logger().error("Reason for the missing data source: ", ex)
   4.126 +            null
   4.127 +        }
   4.128 +    }
   4.129 +
   4.130 +    override fun contextInitialized(sce: ServletContextEvent?) {
   4.131 +        val sc = sce!!.servletContext
   4.132 +
   4.133 +        val dbSchema = sc.getInitParameter(Constants.CTX_ATTR_DB_SCHEMA) ?: DB_DEFAULT_SCHEMA
   4.134 +        sc.getInitParameter(Constants.CTX_ATTR_DB_DIALECT)?.let {dbDialect ->
   4.135 +            try {
   4.136 +                dialect = DatabaseDialect.valueOf(dbDialect)
   4.137 +            } catch (ex: IllegalArgumentException) {
   4.138 +                logger().error(
   4.139 +                    "Unknown or unsupported database dialect {}. Defaulting to {}.",
   4.140 +                    dbDialect,
   4.141 +                    dialect
   4.142 +                )
   4.143 +            }
   4.144 +        }
   4.145 +
   4.146 +        dataSource = try {
   4.147 +            logger().debug("Trying to access JNDI context ...")
   4.148 +            val initialCtx: Context = InitialContext()
   4.149 +            val ctx = initialCtx.lookup("java:comp/env") as Context
   4.150 +            retrieveDataSource(ctx)
   4.151 +        } catch (ex: NamingException) {
   4.152 +            logger().error("Cannot access JNDI resources.", ex)
   4.153 +            null
   4.154 +        } catch (ex: ClassCastException) {
   4.155 +            logger().error("Cannot access JNDI resources.", ex)
   4.156 +            null
   4.157 +        }
   4.158 +
   4.159 +        dataSource?.let { checkConnection(it, dbSchema) }
   4.160 +
   4.161 +        sc.setAttribute(SC_ATTR_NAME, this)
   4.162 +        logger().info("Database facade injected into ServletContext.")
   4.163 +    }
   4.164 +
   4.165 +    override fun contextDestroyed(sce: ServletContextEvent?) {
   4.166 +        dataSource = null
   4.167 +    }
   4.168 +}
   4.169 \ No newline at end of file
     5.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     5.2 +++ b/src/main/kotlin/de/uapcore/lightpit/Logging.kt	Sat Oct 24 12:09:08 2020 +0200
     5.3 @@ -0,0 +1,32 @@
     5.4 +/*
     5.5 + * Copyright 2020 Mike Becker. All rights reserved.
     5.6 + *
     5.7 + * Redistribution and use in source and binary forms, with or without
     5.8 + * modification, are permitted provided that the following conditions are met:
     5.9 + *
    5.10 + * 1. Redistributions of source code must retain the above copyright
    5.11 + * notice, this list of conditions and the following disclaimer.
    5.12 + *
    5.13 + * 2. Redistributions in binary form must reproduce the above copyright
    5.14 + * notice, this list of conditions and the following disclaimer in the
    5.15 + * documentation and/or other materials provided with the distribution.
    5.16 + *
    5.17 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    5.18 + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    5.19 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    5.20 + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
    5.21 + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
    5.22 + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
    5.23 + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
    5.24 + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
    5.25 + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    5.26 + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    5.27 + */
    5.28 +
    5.29 +package de.uapcore.lightpit
    5.30 +
    5.31 +import org.slf4j.Logger
    5.32 +import org.slf4j.LoggerFactory
    5.33 +
    5.34 +interface LoggingTrait
    5.35 +inline fun <reified T : LoggingTrait> T.logger(): Logger = LoggerFactory.getLogger(T::class.java);

mercurial