Sat, 24 Oct 2020 12:09:08 +0200
migrate DataSourceProvider
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);