26 * POSSIBILITY OF SUCH DAMAGE. |
26 * POSSIBILITY OF SUCH DAMAGE. |
27 * |
27 * |
28 */ |
28 */ |
29 package de.uapcore.lightpit; |
29 package de.uapcore.lightpit; |
30 |
30 |
31 import java.sql.Connection; |
31 import org.slf4j.Logger; |
32 import java.sql.DatabaseMetaData; |
32 import org.slf4j.LoggerFactory; |
33 import java.sql.SQLException; |
33 |
34 import java.util.Optional; |
|
35 import javax.naming.Context; |
34 import javax.naming.Context; |
36 import javax.naming.InitialContext; |
35 import javax.naming.InitialContext; |
37 import javax.naming.NamingException; |
36 import javax.naming.NamingException; |
38 import javax.servlet.ServletContext; |
37 import javax.servlet.ServletContext; |
39 import javax.servlet.ServletContextEvent; |
38 import javax.servlet.ServletContextEvent; |
40 import javax.servlet.ServletContextListener; |
39 import javax.servlet.ServletContextListener; |
41 import javax.servlet.annotation.WebListener; |
40 import javax.servlet.annotation.WebListener; |
42 import javax.sql.DataSource; |
41 import javax.sql.DataSource; |
43 import org.slf4j.Logger; |
42 import java.sql.Connection; |
44 import org.slf4j.LoggerFactory; |
43 import java.sql.DatabaseMetaData; |
|
44 import java.sql.SQLException; |
|
45 import java.util.Optional; |
45 |
46 |
46 /** |
47 /** |
47 * Provides access to different privilege layers within the database. |
48 * Provides access to different privilege layers within the database. |
48 */ |
49 */ |
49 @WebListener |
50 @WebListener |
50 public final class DatabaseFacade implements ServletContextListener { |
51 public final class DatabaseFacade implements ServletContextListener { |
51 |
52 |
52 private static final Logger LOG = LoggerFactory.getLogger(DatabaseFacade.class); |
53 private static final Logger LOG = LoggerFactory.getLogger(DatabaseFacade.class); |
53 |
54 |
54 /** |
55 /** |
55 * Timeout in seconds for the validation test. |
56 * Timeout in seconds for the validation test. |
56 */ |
57 */ |
57 private static final int DB_TEST_TIMEOUT = 10; |
58 private static final int DB_TEST_TIMEOUT = 10; |
58 |
59 |
59 public static enum Dialect { |
60 public enum Dialect { |
60 Postgres; |
61 Postgres |
61 } |
62 } |
62 |
63 |
63 /** |
64 /** |
64 * The database dialect to use. |
65 * The database dialect to use. |
65 * |
66 * <p> |
66 * May be override by context parameter. |
67 * May be override by context parameter. |
67 * |
68 * |
68 * @see Constants#CTX_ATTR_DB_DIALECT |
69 * @see Constants#CTX_ATTR_DB_DIALECT |
69 */ |
70 */ |
70 private Dialect dialect = Dialect.Postgres; |
71 private Dialect dialect = Dialect.Postgres; |
71 |
72 |
72 /** |
73 /** |
80 |
81 |
81 /** |
82 /** |
82 * The attribute name in the Servlet context under which an instance of this class can be found. |
83 * The attribute name in the Servlet context under which an instance of this class can be found. |
83 */ |
84 */ |
84 public static final String SC_ATTR_NAME = DatabaseFacade.class.getName(); |
85 public static final String SC_ATTR_NAME = DatabaseFacade.class.getName(); |
85 private ServletContext sc; |
86 |
86 |
|
87 private static final String DS_JNDI_NAME = "jdbc/lightpit/app"; |
87 private static final String DS_JNDI_NAME = "jdbc/lightpit/app"; |
88 private Optional<DataSource> dataSource; |
88 private DataSource dataSource; |
89 |
89 |
90 /** |
90 /** |
91 * Returns the data source. |
91 * Returns the data source. |
92 * |
92 * |
93 * The Optional returned should never be empty. However, if something goes |
93 * The Optional returned should never be empty. However, if something goes |
95 * Hence, users of this data source are forced to check the existence. |
95 * Hence, users of this data source are forced to check the existence. |
96 * |
96 * |
97 * @return a data source |
97 * @return a data source |
98 */ |
98 */ |
99 public Optional<DataSource> getDataSource() { |
99 public Optional<DataSource> getDataSource() { |
100 return dataSource; |
100 // TODO: this should not be an optional, if an empty optional is actually an exception |
|
101 return Optional.ofNullable(dataSource); |
101 } |
102 } |
102 |
103 |
103 public Dialect getSQLDialect() { |
104 public Dialect getSQLDialect() { |
104 return dialect; |
105 return dialect; |
105 } |
106 } |
106 |
107 |
107 private static void checkConnection(DataSource ds, String testSchema, String errMsg) { |
108 private static void checkConnection(DataSource ds, String testSchema) { |
108 try (Connection conn = ds.getConnection()) { |
109 try (Connection conn = ds.getConnection()) { |
109 if (!conn.isValid(DB_TEST_TIMEOUT)) { |
110 if (!conn.isValid(DB_TEST_TIMEOUT)) { |
110 throw new SQLException("Validation check failed."); |
111 throw new SQLException("Validation check failed."); |
111 } |
112 } |
112 if (conn.isReadOnly()) { |
113 if (conn.isReadOnly()) { |
116 throw new SQLException(String.format("Connection is not configured to use the schema %s.", testSchema)); |
117 throw new SQLException(String.format("Connection is not configured to use the schema %s.", testSchema)); |
117 } |
118 } |
118 DatabaseMetaData metaData = conn.getMetaData(); |
119 DatabaseMetaData metaData = conn.getMetaData(); |
119 LOG.info("Connections as {} to {}/{} ready to go.", metaData.getUserName(), metaData.getURL(), conn.getSchema()); |
120 LOG.info("Connections as {} to {}/{} ready to go.", metaData.getUserName(), metaData.getURL(), conn.getSchema()); |
120 } catch (SQLException ex) { |
121 } catch (SQLException ex) { |
121 LOG.error(errMsg, ex); |
122 LOG.error("Checking database connection failed", ex); |
122 } |
123 } |
123 } |
124 } |
124 |
125 |
125 private static Optional<DataSource> retrieveDataSource(Context ctx) { |
126 private static DataSource retrieveDataSource(Context ctx) { |
126 DataSource ret = null; |
127 DataSource ret = null; |
127 try { |
128 try { |
128 ret = (DataSource)ctx.lookup(DS_JNDI_NAME); |
129 ret = (DataSource) ctx.lookup(DS_JNDI_NAME); |
129 LOG.info("Data source retrieved."); |
130 LOG.info("Data source retrieved."); |
130 } catch (NamingException ex) { |
131 } catch (NamingException ex) { |
131 LOG.error("Data source {} not available.", DS_JNDI_NAME); |
132 LOG.error("Data source {} not available.", DS_JNDI_NAME); |
132 LOG.error("Reason for the missing data source: ", ex); |
133 LOG.error("Reason for the missing data source: ", ex); |
133 } |
134 } |
134 return Optional.ofNullable(ret); |
135 return ret; |
135 } |
136 } |
136 |
137 |
137 @Override |
138 @Override |
138 public void contextInitialized(ServletContextEvent sce) { |
139 public void contextInitialized(ServletContextEvent sce) { |
139 sc = sce.getServletContext(); |
140 ServletContext sc = sce.getServletContext(); |
140 |
141 |
141 dataSource = null; |
142 dataSource = null; |
142 |
143 |
143 final String contextName = Optional |
144 final String contextName = Optional |
144 .ofNullable(sc.getInitParameter(Constants.CTX_ATTR_JNDI_CONTEXT)) |
145 .ofNullable(sc.getInitParameter(Constants.CTX_ATTR_JNDI_CONTEXT)) |
157 |
158 |
158 try { |
159 try { |
159 LOG.debug("Trying to access JNDI context {}...", contextName); |
160 LOG.debug("Trying to access JNDI context {}...", contextName); |
160 Context initialCtx = new InitialContext(); |
161 Context initialCtx = new InitialContext(); |
161 Context ctx = (Context) initialCtx.lookup(contextName); |
162 Context ctx = (Context) initialCtx.lookup(contextName); |
162 |
163 |
163 dataSource = retrieveDataSource(ctx); |
164 dataSource = retrieveDataSource(ctx); |
164 |
165 |
165 dataSource.ifPresent((ds) -> checkConnection(ds, dbSchema, "Checking database connection failed")); |
166 if (dataSource != null) { |
|
167 checkConnection(dataSource, dbSchema); |
|
168 } |
166 } catch (NamingException | ClassCastException ex) { |
169 } catch (NamingException | ClassCastException ex) { |
167 LOG.error("Cannot access JNDI resources.", ex); |
170 LOG.error("Cannot access JNDI resources.", ex); |
168 } |
171 } |
169 |
172 |
170 sc.setAttribute(SC_ATTR_NAME, this); |
173 sc.setAttribute(SC_ATTR_NAME, this); |