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

changeset 151
b3f14cd4f3ab
child 158
4f912cd42876
equal deleted inserted replaced
150:822b7e3d064d 151:b3f14cd4f3ab
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 */
25
26 package de.uapcore.lightpit
27
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
36
37 enum class DatabaseDialect {
38 Postgres
39 }
40
41 /**
42 * Provides access to the database.
43 */
44 @WebListener
45 class DataSourceProvider : ServletContextListener, LoggingTrait {
46
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 = DatabaseDialect.Postgres; private set
54
55 /**
56 * The data source, if available.
57 */
58 var dataSource: DataSource? = null
59
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"
65
66 /**
67 * Timeout in seconds for the validation test.
68 */
69 private val DB_TEST_TIMEOUT = 10
70
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"
78
79 /**
80 * The JNDI resource name for the data source.
81 */
82 private val DS_JNDI_NAME = "jdbc/lightpit/app"
83 }
84
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 }
114
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 }
126
127 override fun contextInitialized(sce: ServletContextEvent?) {
128 val sc = sce!!.servletContext
129
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 = DatabaseDialect.valueOf(dbDialect)
134 } catch (ex: IllegalArgumentException) {
135 logger().error(
136 "Unknown or unsupported database dialect {}. Defaulting to {}.",
137 dbDialect,
138 dialect
139 )
140 }
141 }
142
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 }
155
156 dataSource?.let { checkConnection(it, dbSchema) }
157
158 sc.setAttribute(SC_ATTR_NAME, this)
159 logger().info("Database facade injected into ServletContext.")
160 }
161
162 override fun contextDestroyed(sce: ServletContextEvent?) {
163 dataSource = null
164 }
165 }

mercurial