src/main/java/de/uapcore/lightpit/DatabaseFacade.java

Sun, 21 Jun 2020 12:32:25 +0200

author
Mike Becker <universe@uap-core.de>
date
Sun, 21 Jun 2020 12:32:25 +0200
changeset 94
edba952cfc57
parent 50
2a90d105edec
permissions
-rw-r--r--

fix: issue form should always include the currently selected versions

     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 org.slf4j.Logger;
    32 import org.slf4j.LoggerFactory;
    34 import javax.naming.Context;
    35 import javax.naming.InitialContext;
    36 import javax.naming.NamingException;
    37 import javax.servlet.ServletContext;
    38 import javax.servlet.ServletContextEvent;
    39 import javax.servlet.ServletContextListener;
    40 import javax.servlet.annotation.WebListener;
    41 import javax.sql.DataSource;
    42 import java.sql.Connection;
    43 import java.sql.DatabaseMetaData;
    44 import java.sql.SQLException;
    45 import java.util.Optional;
    47 /**
    48  * Provides access to different privilege layers within the database.
    49  */
    50 @WebListener
    51 public final class DatabaseFacade implements ServletContextListener {
    53     private static final Logger LOG = LoggerFactory.getLogger(DatabaseFacade.class);
    55     /**
    56      * Timeout in seconds for the validation test.
    57      */
    58     private static final int DB_TEST_TIMEOUT = 10;
    60     /**
    61      * Specifies the database dialect.
    62      */
    63     public enum Dialect {
    64         Postgres
    65     }
    67     /**
    68      * The database dialect to use.
    69      * <p>
    70      * May be overridden by context parameter.
    71      *
    72      * @see Constants#CTX_ATTR_DB_DIALECT
    73      */
    74     private Dialect dialect = Dialect.Postgres;
    76     /**
    77      * The default schema to test against when validating the connection.
    78      * <p>
    79      * May be overridden by context parameter.
    80      *
    81      * @see Constants#CTX_ATTR_DB_SCHEMA
    82      */
    83     private static final String DB_DEFAULT_SCHEMA = "lightpit";
    85     /**
    86      * The attribute name in the Servlet context under which an instance of this class can be found.
    87      */
    88     public static final String SC_ATTR_NAME = DatabaseFacade.class.getName();
    90     private static final String DS_JNDI_NAME = "jdbc/lightpit/app";
    91     private DataSource dataSource;
    93     /**
    94      * Returns the data source.
    95      *
    96      * @return a data source
    97      */
    98     public DataSource getDataSource() {
    99         return dataSource;
   100     }
   102     public Dialect getSQLDialect() {
   103         return dialect;
   104     }
   106     private static void checkConnection(DataSource ds, String testSchema) {
   107         try (Connection conn = ds.getConnection()) {
   108             if (!conn.isValid(DB_TEST_TIMEOUT)) {
   109                 throw new SQLException("Validation check failed.");
   110             }
   111             if (conn.isReadOnly()) {
   112                 throw new SQLException("Connection is read-only and thus unusable.");
   113             }
   114             if (!conn.getSchema().equals(testSchema)) {
   115                 throw new SQLException(String.format("Connection is not configured to use the schema %s.", testSchema));
   116             }
   117             DatabaseMetaData metaData = conn.getMetaData();
   118             LOG.info("Connections as {} to {}/{} ready to go.", metaData.getUserName(), metaData.getURL(), conn.getSchema());
   119         } catch (SQLException ex) {
   120             LOG.error("Checking database connection failed", ex);
   121         }
   122     }
   124     private static DataSource retrieveDataSource(Context ctx) {
   125         DataSource ret = null;
   126         try {
   127             ret = (DataSource) ctx.lookup(DS_JNDI_NAME);
   128             LOG.info("Data source retrieved.");
   129         } catch (NamingException ex) {
   130             LOG.error("Data source {} not available.", DS_JNDI_NAME);
   131             LOG.error("Reason for the missing data source: ", ex);
   132         }
   133         return ret;
   134     }
   136     @Override
   137     public void contextInitialized(ServletContextEvent sce) {
   138         ServletContext sc = sce.getServletContext();
   140         dataSource = null;
   142         final String dbSchema = Optional
   143                 .ofNullable(sc.getInitParameter(Constants.CTX_ATTR_DB_SCHEMA))
   144                 .orElse(DB_DEFAULT_SCHEMA);
   145         final String dbDialect = sc.getInitParameter(Constants.CTX_ATTR_DB_DIALECT);
   146         if (dbDialect != null) {
   147             try {
   148                 dialect = Dialect.valueOf(dbDialect);
   149             } catch (IllegalArgumentException ex) {
   150                 LOG.error("Unknown or unsupported database dialect {}. Defaulting to {}.", dbDialect, dialect);
   151             }
   152         }
   154         try {
   155             LOG.debug("Trying to access JNDI context ...");
   156             Context initialCtx = new InitialContext();
   157             Context ctx = (Context) initialCtx.lookup("java:comp/env");
   159             dataSource = retrieveDataSource(ctx);
   161             if (dataSource != null) {
   162                 checkConnection(dataSource, dbSchema);
   163             }
   164         } catch (NamingException | ClassCastException ex) {
   165             LOG.error("Cannot access JNDI resources.", ex);
   166         }
   168         sc.setAttribute(SC_ATTR_NAME, this);
   169         LOG.info("Database facade injected into ServletContext.");
   170     }
   172     @Override
   173     public void contextDestroyed(ServletContextEvent sce) {
   174         dataSource = null;
   175     }
   176 }

mercurial