Sun, 10 May 2020 10:11:37 +0200
removes features that are not (and probably will not) used anyway
1 /*
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3 *
4 * Copyright 2018 Mike Becker. All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
8 *
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
27 *
28 */
29 package de.uapcore.lightpit;
31 import de.uapcore.lightpit.dao.DataAccessObjects;
32 import de.uapcore.lightpit.dao.postgres.PGDataAccessObjects;
33 import org.slf4j.Logger;
34 import org.slf4j.LoggerFactory;
36 import javax.naming.Context;
37 import javax.naming.InitialContext;
38 import javax.naming.NamingException;
39 import javax.servlet.ServletContext;
40 import javax.servlet.ServletContextEvent;
41 import javax.servlet.ServletContextListener;
42 import javax.servlet.annotation.WebListener;
43 import javax.sql.DataSource;
44 import java.sql.Connection;
45 import java.sql.DatabaseMetaData;
46 import java.sql.SQLException;
47 import java.util.Optional;
49 /**
50 * Provides access to different privilege layers within the database.
51 */
52 @WebListener
53 public final class DatabaseFacade implements ServletContextListener {
55 private static final Logger LOG = LoggerFactory.getLogger(DatabaseFacade.class);
57 /**
58 * Timeout in seconds for the validation test.
59 */
60 private static final int DB_TEST_TIMEOUT = 10;
62 /**
63 * Specifies the database dialect.
64 */
65 public enum Dialect {
66 Postgres
67 }
69 /**
70 * The database dialect to use.
71 * <p>
72 * May be overridden by context parameter.
73 *
74 * @see Constants#CTX_ATTR_DB_DIALECT
75 */
76 private Dialect dialect = Dialect.Postgres;
78 /**
79 * The default schema to test against when validating the connection.
80 * <p>
81 * May be overridden by context parameter.
82 *
83 * @see Constants#CTX_ATTR_DB_SCHEMA
84 */
85 private static final String DB_DEFAULT_SCHEMA = "lightpit";
87 /**
88 * The attribute name in the Servlet context under which an instance of this class can be found.
89 */
90 public static final String SC_ATTR_NAME = DatabaseFacade.class.getName();
92 private static final String DS_JNDI_NAME = "jdbc/lightpit/app";
93 private DataSource dataSource;
94 private DataAccessObjects dataAccessObjects;
96 /**
97 * Returns the data source.
98 * <p>
99 * The Optional returned should never be empty. However, if something goes
100 * wrong during initialization, the data source might be absent.
101 * Hence, users of this data source are forced to check the existence.
102 *
103 * @return a data source
104 */
105 public Optional<DataSource> getDataSource() {
106 // TODO: this should not be an optional, if an empty optional is actually an exception
107 return Optional.ofNullable(dataSource);
108 }
110 /**
111 * Returns the data access objects.
112 *
113 * @return an interface to obtain the data access objects
114 */
115 public DataAccessObjects getDataAccessObjects() {
116 return dataAccessObjects;
117 }
119 public Dialect getSQLDialect() {
120 return dialect;
121 }
123 private static void checkConnection(DataSource ds, String testSchema) {
124 try (Connection conn = ds.getConnection()) {
125 if (!conn.isValid(DB_TEST_TIMEOUT)) {
126 throw new SQLException("Validation check failed.");
127 }
128 if (conn.isReadOnly()) {
129 throw new SQLException("Connection is read-only and thus unusable.");
130 }
131 if (!conn.getSchema().equals(testSchema)) {
132 throw new SQLException(String.format("Connection is not configured to use the schema %s.", testSchema));
133 }
134 DatabaseMetaData metaData = conn.getMetaData();
135 LOG.info("Connections as {} to {}/{} ready to go.", metaData.getUserName(), metaData.getURL(), conn.getSchema());
136 } catch (SQLException ex) {
137 LOG.error("Checking database connection failed", ex);
138 }
139 }
141 private static DataSource retrieveDataSource(Context ctx) {
142 DataSource ret = null;
143 try {
144 ret = (DataSource) ctx.lookup(DS_JNDI_NAME);
145 LOG.info("Data source retrieved.");
146 } catch (NamingException ex) {
147 LOG.error("Data source {} not available.", DS_JNDI_NAME);
148 LOG.error("Reason for the missing data source: ", ex);
149 }
150 return ret;
151 }
153 @Override
154 public void contextInitialized(ServletContextEvent sce) {
155 ServletContext sc = sce.getServletContext();
157 dataSource = null;
159 final String contextName = Optional
160 .ofNullable(sc.getInitParameter(Constants.CTX_ATTR_JNDI_CONTEXT))
161 .orElse("java:comp/env");
162 final String dbSchema = Optional
163 .ofNullable(sc.getInitParameter(Constants.CTX_ATTR_DB_SCHEMA))
164 .orElse(DB_DEFAULT_SCHEMA);
165 final String dbDialect = sc.getInitParameter(Constants.CTX_ATTR_DB_DIALECT);
166 if (dbDialect != null) {
167 try {
168 dialect = Dialect.valueOf(dbDialect);
169 } catch (IllegalArgumentException ex) {
170 LOG.error("Unknown or unsupported database dialect {}. Defaulting to {}.", dbDialect, dialect);
171 }
172 }
174 dataAccessObjects = createDataAccessObjects(dialect);
176 try {
177 LOG.debug("Trying to access JNDI context {}...", contextName);
178 Context initialCtx = new InitialContext();
179 Context ctx = (Context) initialCtx.lookup(contextName);
181 dataSource = retrieveDataSource(ctx);
183 if (dataSource != null) {
184 checkConnection(dataSource, dbSchema);
185 }
186 } catch (NamingException | ClassCastException ex) {
187 LOG.error("Cannot access JNDI resources.", ex);
188 }
190 sc.setAttribute(SC_ATTR_NAME, this);
191 LOG.info("Database facade injected into ServletContext.");
192 }
194 private static DataAccessObjects createDataAccessObjects(Dialect dialect) {
195 switch (dialect) {
196 case Postgres:
197 return new PGDataAccessObjects();
198 default:
199 throw new AssertionError("Non-exhaustive switch - this is a bug.");
200 }
201 }
203 @Override
204 public void contextDestroyed(ServletContextEvent sce) {
205 dataSource = null;
206 }
207 }