module synchronization with database

Sat, 31 Mar 2018 19:35:04 +0200

author
Mike Becker <universe@uap-core.de>
date
Sat, 31 Mar 2018 19:35:04 +0200
changeset 20
bd1a76c91d5b
parent 19
1a0ac419f714
child 21
b213fef2539e

module synchronization with database

setup/postgres/psql_create_tables.sql file | annotate | diff | comparison | revisions
src/java/de/uapcore/lightpit/AbstractLightPITServlet.java file | annotate | diff | comparison | revisions
src/java/de/uapcore/lightpit/DatabaseFacade.java file | annotate | diff | comparison | revisions
src/java/de/uapcore/lightpit/LightPITModule.java file | annotate | diff | comparison | revisions
src/java/de/uapcore/lightpit/ModuleManager.java file | annotate | diff | comparison | revisions
src/java/de/uapcore/lightpit/modules/ErrorModule.java file | annotate | diff | comparison | revisions
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/setup/postgres/psql_create_tables.sql	Sat Mar 31 19:35:04 2018 +0200
@@ -0,0 +1,27 @@
+-- This script creates the module management tables
+--
+
+create table lpitcore_module (
+    modid       serial          primary key,
+    classname   varchar(100)    not null unique,
+    visible     boolean         not null default(true)
+);
+
+create table lpitcore_user (
+    userid          serial          primary key,
+    username        varchar(50)     not null unique,
+    lastname        varchar(50),
+    givenname       varchar(50)
+);
+
+create table lpitcore_authorization (
+    modid           integer         not null references lpitcore_modules(modid) on delete cascade,
+    userid          integer         not null references lpitcore_user(userid) on delete cascade,
+    power           integer         not null check(power >= 0)
+);
+
+create table lpitcore_menu (
+    modid           integer         not null references lpitcore_modules(modid) on delete cascade,
+    userid          integer         not null references lpitcore_user(userid) on delete cascade,
+    seq             integer         not null
+);
--- a/src/java/de/uapcore/lightpit/AbstractLightPITServlet.java	Sat Mar 31 18:11:09 2018 +0200
+++ b/src/java/de/uapcore/lightpit/AbstractLightPITServlet.java	Sat Mar 31 19:35:04 2018 +0200
@@ -249,9 +249,11 @@
     private void doProcess(HttpMethod method, HttpServletRequest req, HttpServletResponse resp)
             throws ServletException, IOException {
         
-        HttpSession session = req.getSession();
+        // Synchronize module information with database
+        getModuleManager().syncWithDatabase(getDatabaseFacade());
         
         // choose the requested language as session language (if available) or fall back to english, otherwise
+        HttpSession session = req.getSession();
         if (session.getAttribute(Constants.SESSION_ATTR_LANGUAGE) == null) {
             Optional<List<String>> availableLanguages = Functions.availableLanguages(getServletContext()).map(Arrays::asList);
             Optional<Locale> reqLocale = Optional.of(req.getLocale());
--- a/src/java/de/uapcore/lightpit/DatabaseFacade.java	Sat Mar 31 18:11:09 2018 +0200
+++ b/src/java/de/uapcore/lightpit/DatabaseFacade.java	Sat Mar 31 19:35:04 2018 +0200
@@ -151,7 +151,7 @@
             try {
                 dialect = Dialect.valueOf(dbDialect);
             } catch (IllegalArgumentException ex) {
-                LOG.error(String.format("Unknown or unsupported database dialect %s. Defaulting to %s.", dbDialect, dialect));
+                LOG.error("Unknown or unsupported database dialect {}. Defaulting to {}.", dbDialect, dialect);
             }
         }
 
--- a/src/java/de/uapcore/lightpit/LightPITModule.java	Sat Mar 31 18:11:09 2018 +0200
+++ b/src/java/de/uapcore/lightpit/LightPITModule.java	Sat Mar 31 19:35:04 2018 +0200
@@ -89,6 +89,14 @@
     String titleKey() default "menuLabel";
     
     /**
+     * If set to <code>true</code>, this module is always loaded, but never
+     * visible in the menu or the Web UI module manager.
+     * 
+     * @return true, if this is a system module
+     */
+    boolean systemModule() default false;
+    
+    /**
      * Class representing the annotation.
      * This is necessary, because the EL resolver cannot deal with
      * annotation objects.
--- a/src/java/de/uapcore/lightpit/ModuleManager.java	Sat Mar 31 18:11:09 2018 +0200
+++ b/src/java/de/uapcore/lightpit/ModuleManager.java	Sat Mar 31 19:35:04 2018 +0200
@@ -28,11 +28,19 @@
  */
 package de.uapcore.lightpit;
 
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
-import java.util.Objects;
+import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Optional;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.atomic.AtomicBoolean;
 import javax.servlet.Registration;
 import javax.servlet.ServletContext;
 import javax.servlet.ServletContextEvent;
@@ -55,9 +63,19 @@
     public static final String SC_ATTR_NAME = ModuleManager.class.getName();
     private ServletContext sc;
     
-    private final List<Menu> mainMenu = new ArrayList<>();
+    /**
+     * This flag is true, when synchronization is needed.
+     */
+    private AtomicBoolean dirty = new AtomicBoolean(true);
+    
+    private final CopyOnWriteArrayList<Menu> mainMenu = new CopyOnWriteArrayList<>();
     private final List<Menu> immutableMainMenu = Collections.unmodifiableList(mainMenu);
     
+    /**
+     * Maps class names to module information.
+     */
+    private final Map<String, LightPITModule> registeredModules = new HashMap<>();
+    
     @Override
     public void contextInitialized(ServletContextEvent sce) {
         sc = sce.getServletContext();
@@ -108,24 +126,10 @@
         }        
     }
     
-    private void addModuleToMenu(String moduleClassName, LightPITModule moduleInfo) {
-        final Menu menu = new Menu(
-                moduleClassName,
-                new ResourceKey(moduleInfo.bundleBaseName(), moduleInfo.menuKey()),
-                moduleInfo.modulePath()
-        );
-        mainMenu.add(menu);
-    }
-    
     private void handleServletRegistration(String name, Registration reg) {
         final Optional<LightPITModule> moduleInfo = getModuleInfo(reg);
-        if (moduleInfo.isPresent()) {            
-            // TODO: implement dependency resolver
-            
-            if (!moduleInfo.get().menuKey().isEmpty()) {
-                addModuleToMenu(reg.getClassName(), moduleInfo.get());
-            }
-            
+        if (moduleInfo.isPresent()) {
+            registeredModules.put(reg.getClassName(), moduleInfo.get());            
             LOG.info("Module detected: {}", name);
         } else {
             LOG.debug("Servlet {} is no module, skipping.", name);
@@ -137,10 +141,66 @@
      */
     public void reloadAll() {
         sc.getServletRegistrations().forEach(this::handleServletRegistration);
+        
+        // TODO: implement dependency resolver
+        
         LOG.info("Modules loaded.");
     }
     
     /**
+     * Synchronizes module information with the database.
+     * 
+     * @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()) {
+                    PreparedStatement
+                            check = conn.prepareStatement("SELECT visible FROM lpitcore_module WHERE classname = ?"),
+                            insert = conn.prepareStatement("INSERT INTO lpitcore_module (classname, visible) VALUES (?, ?)");
+                    insert.setBoolean(2, true);
+                    // update/delete not required, we do this in the module management UI
+                    
+                    final List<Menu> updatedMenu = new ArrayList<>();
+                    
+                    for (Entry<String, LightPITModule> mod : registeredModules.entrySet()) {
+                        if (mod.getValue().systemModule()) continue;
+                        
+                        check.setString(1, mod.getKey());
+                        try (ResultSet r = check.executeQuery()) {
+                            final boolean addToMenu;
+                            if (r.next()) {
+                                addToMenu = r.getBoolean(1);
+                            } else {
+                                insert.setString(1, mod.getKey());
+                                insert.executeUpdate();
+                                addToMenu = !mod.getValue().menuKey().isEmpty();
+                            }
+                            if (addToMenu) {
+                                updatedMenu.add(new Menu(
+                                        mod.getKey(),
+                                        new ResourceKey(mod.getValue().bundleBaseName(), mod.getValue().menuKey()),
+                                        mod.getValue().modulePath()
+                                ));
+                            }
+                        }
+                    }
+                    
+                    mainMenu.removeIf((e) -> !updatedMenu.contains(e));
+                    mainMenu.addAllAbsent(updatedMenu);
+                } catch (SQLException ex) {
+                    LOG.error("Unexpected SQL Exception", ex);
+                }
+            } else {
+                LOG.warn("No datasource present. Cannot sync module information with database.");
+            }
+        } else {
+            LOG.debug("Module information clean - no synchronization required.");
+        }
+    }
+    
+    /**
      * Unloads all found modules.
      */
     public void unloadAll() {
--- a/src/java/de/uapcore/lightpit/modules/ErrorModule.java	Sat Mar 31 18:11:09 2018 +0200
+++ b/src/java/de/uapcore/lightpit/modules/ErrorModule.java	Sat Mar 31 19:35:04 2018 +0200
@@ -43,8 +43,8 @@
 @LightPITModule(
         bundleBaseName = "de.uapcore.lightpit.resources.localization.error",
         modulePath = "error",
-        menuKey = "",
-        titleKey = "title"
+        titleKey = "title",
+        systemModule = true
 )
 @WebServlet(
         name = "ErrorModule",

mercurial