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

Sun, 10 May 2020 10:11:37 +0200

author
Mike Becker <universe@uap-core.de>
date
Sun, 10 May 2020 10:11:37 +0200
changeset 36
0f4f8f255c32
parent 34
824d4042c857
child 38
cf85ef18f231
permissions
-rw-r--r--

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 }

mercurial