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

changeset 29
27a0fdd7bca7
parent 27
1f2a96efa69f
child 30
fb30f7b78f9b
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/de/uapcore/lightpit/ModuleManager.java	Sat May 09 14:26:31 2020 +0200
@@ -0,0 +1,231 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ * 
+ * Copyright 2018 Mike Becker. All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ * 
+ */
+package de.uapcore.lightpit;
+
+import de.uapcore.lightpit.entities.CoreDAOFactory;
+import de.uapcore.lightpit.entities.Module;
+import de.uapcore.lightpit.entities.ModuleDao;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.Collectors;
+import javax.servlet.Registration;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import javax.servlet.annotation.WebListener;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Scans registered servlets for LightPIT modules.
+ */
+@WebListener
+public final class ModuleManager implements ServletContextListener {
+    
+    private static final Logger LOG = LoggerFactory.getLogger(ModuleManager.class);
+    
+    /**
+     * The attribute name in the servlet context under which an instance of this class can be found.
+     */
+    public static final String SC_ATTR_NAME = ModuleManager.class.getName();
+    private ServletContext sc;
+    
+    /**
+     * Maps class names to module information.
+     */
+    private final Map<String, LightPITModule> registeredModules = new HashMap<>();
+    
+    /**
+     * This flag is true, when synchronization is needed.
+     */
+    private final AtomicBoolean dirty = new AtomicBoolean(true);
+    
+    @Override
+    public void contextInitialized(ServletContextEvent sce) {
+        sc = sce.getServletContext();
+        reloadAll();
+        sc.setAttribute(SC_ATTR_NAME, this);
+        LOG.info("Module manager injected into ServletContext.");
+    }
+
+    @Override
+    public void contextDestroyed(ServletContextEvent sce) {
+        unloadAll();
+    }
+    
+    private Optional<LightPITModule> getModuleInfo(Registration reg) {
+        try {
+            final Class scclass = Class.forName(reg.getClassName());
+            
+            final boolean lpservlet = AbstractLightPITServlet.class.isAssignableFrom(scclass);
+            final boolean lpmodule = scclass.isAnnotationPresent(LightPITModule.class);
+            
+            if (lpservlet && !lpmodule) {
+                LOG.warn(
+                    "{} is a LightPIT Servlet but is missing the module annotation.",
+                    reg.getClassName()
+                );
+            } else if (!lpservlet && lpmodule) {
+                LOG.warn(
+                    "{} is annotated as a LightPIT Module but does not extend {}.",
+                    reg.getClassName(),
+                    AbstractLightPITServlet.class.getSimpleName()
+                );
+            }
+            
+            if (lpservlet && lpmodule) {
+                final Class<? extends AbstractLightPITServlet> clazz = scclass;
+                final LightPITModule moduleInfo = clazz.getAnnotation(LightPITModule.class);
+                return Optional.of(moduleInfo);
+            } else {
+                return Optional.empty();
+            }
+        } catch (ClassNotFoundException ex) {
+            LOG.error(
+                    "Servlet registration refers to class {} which cannot be found by the class loader (Reason: {})",
+                    reg.getClassName(),
+                    ex.getMessage()
+            );
+            return Optional.empty();
+        }        
+    }
+    
+    private void handleServletRegistration(String name, Registration reg) {
+        final Optional<LightPITModule> moduleInfo = getModuleInfo(reg);
+        if (moduleInfo.isPresent()) {
+            registeredModules.put(reg.getClassName(), moduleInfo.get());            
+            LOG.info("Module detected: {}", name);
+        } else {
+            LOG.debug("Servlet {} is no module, skipping.", name);
+        }
+    }
+    
+    /**
+     * Scans for modules and reloads them all.
+     */
+    public void reloadAll() {
+        registeredModules.clear();
+        sc.getServletRegistrations().forEach(this::handleServletRegistration);
+        
+        // TODO: implement dependency resolver
+        
+        dirty.set(true);
+        LOG.info("Modules loaded.");
+    }
+    
+    /**
+     * Synchronizes module information with the database.
+     * 
+     * This must be called from the {@link AbstractLightPITServlet}.
+     * Admittedly the call will perform the synchronization once after reload
+     * and be a no-op, afterwards.
+     * However, we since the DatabaseFacade might be loaded after the module
+     * manager, we must defer the synchronization to the first request
+     * handled by the Servlet.
+     * 
+     * @param db interface to the database
+     */
+    public void syncWithDatabase(DatabaseFacade db) {
+        if (dirty.compareAndSet(true, false)) {
+            if (db.getDataSource().isPresent()) {
+                try (Connection conn = db.getDataSource().get().getConnection()) {
+                    final ModuleDao moduleDao = CoreDAOFactory.getModuleDao(db.getSQLDialect());
+                    moduleDao.syncRegisteredModuleClasses(conn, registeredModules.entrySet());
+                } catch (SQLException ex) {
+                    LOG.error("Unexpected SQL Exception", ex);
+                }
+            } else {
+                LOG.error("No datasource present. Cannot sync module information with database.");
+            }
+        } else {
+            LOG.trace("Module information clean - no synchronization required.");
+        }
+    }
+    
+    /**
+     * Unloads all found modules.
+     */
+    public void unloadAll() {
+        registeredModules.clear();
+        LOG.info("All modules unloaded.");
+    }
+
+    /**
+     * Returns the main menu.
+     * 
+     * @param db the interface to the database
+     * @return a list of menus belonging to the main menu
+     */
+    public List<Menu> getMainMenu(DatabaseFacade db) {
+        // TODO: user specific menu
+        
+        if (db.getDataSource().isPresent()) {
+            try (Connection conn = db.getDataSource().get().getConnection()) {
+                final ModuleDao dao = CoreDAOFactory.getModuleDao(db.getSQLDialect());
+                final List<Module> modules = dao.listAll(conn);
+                
+                final List<Menu> menu = modules
+                    .stream()
+                    .filter((mod) -> mod.isVisible())
+                    .collect(Collectors.mapping(
+                            (mod) -> new Menu(
+                                    mod.getClassname(),
+                                    new ResourceKey(
+                                            registeredModules.get(mod.getClassname()).bundleBaseName(),
+                                            registeredModules.get(mod.getClassname()).menuKey()),
+                                    registeredModules.get(mod.getClassname()).modulePath()),
+                            Collectors.toList())
+                    );
+                return menu;
+            } catch (SQLException ex) {
+                LOG.error("Unexpected SQLException when loading the main menu", ex);
+                return Collections.emptyList();
+            }
+        } else {
+            return Collections.emptyList();
+        }
+    }
+    
+    /**
+     * Returns an unmodifiable map of all registered modules.
+     * 
+     * The key is the classname of the module.
+     * 
+     * @return the map of registered modules
+     */
+    public Map<String, LightPITModule> getRegisteredModules() {
+        return Collections.unmodifiableMap(registeredModules);
+    }
+}

mercurial