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

Sat, 09 May 2020 17:01:29 +0200

author
Mike Becker <universe@uap-core.de>
date
Sat, 09 May 2020 17:01:29 +0200
changeset 34
824d4042c857
parent 33
fd8c40ff78c3
child 36
0f4f8f255c32
permissions
-rw-r--r--

cleanup and simplification of database access layer

     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.entities.Module;
    32 import org.slf4j.Logger;
    33 import org.slf4j.LoggerFactory;
    35 import javax.servlet.Registration;
    36 import javax.servlet.ServletContext;
    37 import javax.servlet.ServletContextEvent;
    38 import javax.servlet.ServletContextListener;
    39 import javax.servlet.annotation.WebListener;
    40 import java.sql.Connection;
    41 import java.sql.SQLException;
    42 import java.util.*;
    43 import java.util.concurrent.atomic.AtomicBoolean;
    44 import java.util.stream.Collectors;
    46 /**
    47  * Scans registered servlets for LightPIT modules.
    48  */
    49 @WebListener
    50 public final class ModuleManager implements ServletContextListener {
    52     private static final Logger LOG = LoggerFactory.getLogger(ModuleManager.class);
    54     /**
    55      * The attribute name in the servlet context under which an instance of this class can be found.
    56      */
    57     public static final String SC_ATTR_NAME = ModuleManager.class.getName();
    58     private ServletContext sc;
    60     /**
    61      * Maps class names to module information.
    62      */
    63     private final Map<String, LightPITModule> registeredModules = new HashMap<>();
    65     /**
    66      * This flag is true, when synchronization is needed.
    67      */
    68     private final AtomicBoolean dirty = new AtomicBoolean(true);
    70     @Override
    71     public void contextInitialized(ServletContextEvent sce) {
    72         sc = sce.getServletContext();
    73         reloadAll();
    74         sc.setAttribute(SC_ATTR_NAME, this);
    75         LOG.info("Module manager injected into ServletContext.");
    76     }
    78     @Override
    79     public void contextDestroyed(ServletContextEvent sce) {
    80         unloadAll();
    81     }
    83     private Optional<LightPITModule> getModuleInfo(Registration reg) {
    84         try {
    85             final Class<?> scclass = Class.forName(reg.getClassName());
    87             final boolean lpservlet = AbstractLightPITServlet.class.isAssignableFrom(scclass);
    88             final boolean lpmodule = scclass.isAnnotationPresent(LightPITModule.class);
    90             if (lpservlet && !lpmodule) {
    91                 LOG.warn(
    92                         "{} is a LightPIT Servlet but is missing the module annotation.",
    93                         reg.getClassName()
    94                 );
    95             } else if (!lpservlet && lpmodule) {
    96                 LOG.warn(
    97                         "{} is annotated as a LightPIT Module but does not extend {}.",
    98                         reg.getClassName(),
    99                         AbstractLightPITServlet.class.getSimpleName()
   100                 );
   101             }
   103             if (lpservlet && lpmodule) {
   104                 final LightPITModule moduleInfo = scclass.getAnnotation(LightPITModule.class);
   105                 return Optional.of(moduleInfo);
   106             } else {
   107                 return Optional.empty();
   108             }
   109         } catch (ClassNotFoundException ex) {
   110             LOG.error(
   111                     "Servlet registration refers to class {} which cannot be found by the class loader (Reason: {})",
   112                     reg.getClassName(),
   113                     ex.getMessage()
   114             );
   115             return Optional.empty();
   116         }
   117     }
   119     private void handleServletRegistration(String name, Registration reg) {
   120         final Optional<LightPITModule> moduleInfo = getModuleInfo(reg);
   121         if (moduleInfo.isPresent()) {
   122             registeredModules.put(reg.getClassName(), moduleInfo.get());
   123             LOG.info("Module detected: {}", name);
   124         } else {
   125             LOG.debug("Servlet {} is no module, skipping.", name);
   126         }
   127     }
   129     /**
   130      * Scans for modules and reloads them all.
   131      */
   132     public void reloadAll() {
   133         registeredModules.clear();
   134         sc.getServletRegistrations().forEach(this::handleServletRegistration);
   136         // TODO: implement dependency resolver
   138         dirty.set(true);
   139         LOG.info("Modules loaded.");
   140     }
   142     /**
   143      * Synchronizes module information with the database.
   144      * <p>
   145      * This must be called from the {@link AbstractLightPITServlet}.
   146      * Admittedly the call will perform the synchronization once after reload
   147      * and be a no-op, afterwards.
   148      * However, since the DatabaseFacade might be loaded after the module
   149      * manager, we must defer the synchronization to the first request
   150      * handled by the Servlet.
   151      *
   152      * @param db interface to the database
   153      */
   154     public void syncWithDatabase(DatabaseFacade db) {
   155         if (dirty.compareAndSet(true, false)) {
   156             if (db.getDataSource().isPresent()) {
   157                 try (Connection conn = db.getDataSource().get().getConnection()) {
   158                     db.getDataAccessObjects()
   159                             .getModuleDao()
   160                             .syncRegisteredModuleClasses(conn, registeredModules.entrySet());
   161                 } catch (SQLException ex) {
   162                     LOG.error("Unexpected SQL Exception", ex);
   163                 }
   164             } else {
   165                 LOG.error("No datasource present. Cannot sync module information with database.");
   166             }
   167         } else {
   168             LOG.trace("Module information clean - no synchronization required.");
   169         }
   170     }
   172     /**
   173      * Unloads all found modules.
   174      */
   175     public void unloadAll() {
   176         registeredModules.clear();
   177         LOG.info("All modules unloaded.");
   178     }
   180     /**
   181      * Returns the main menu.
   182      *
   183      * @param db the interface to the database
   184      * @return a list of menus belonging to the main menu
   185      */
   186     public List<Menu> getMainMenu(DatabaseFacade db) {
   187         // TODO: user specific menu
   189         if (db.getDataSource().isPresent()) {
   190             try (Connection conn = db.getDataSource().get().getConnection()) {
   191                 final List<Module> modules = db.getDataAccessObjects().getModuleDao().list(conn);
   193                 return modules
   194                         .stream()
   195                         .filter(Module::isVisible)
   196                         .sorted(new Module.PriorityComparator())
   197                         .map(mod -> new Menu(
   198                                 mod.getClassname(),
   199                                 new ResourceKey(
   200                                         registeredModules.get(mod.getClassname()).bundleBaseName(),
   201                                         registeredModules.get(mod.getClassname()).menuKey()),
   202                                 registeredModules.get(mod.getClassname()).modulePath()))
   203                         .collect(Collectors.toList());
   204             } catch (SQLException ex) {
   205                 LOG.error("Unexpected SQLException when loading the main menu", ex);
   206                 return Collections.emptyList();
   207             }
   208         } else {
   209             return Collections.emptyList();
   210         }
   211     }
   213     /**
   214      * Returns an unmodifiable map of all registered modules.
   215      * <p>
   216      * The key is the classname of the module.
   217      *
   218      * @return the map of registered modules
   219      */
   220     public Map<String, LightPITModule> getRegisteredModules() {
   221         return Collections.unmodifiableMap(registeredModules);
   222     }
   223 }

mercurial