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
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/setup/postgres/psql_create_tables.sql	Sat Mar 31 19:35:04 2018 +0200
     1.3 @@ -0,0 +1,27 @@
     1.4 +-- This script creates the module management tables
     1.5 +--
     1.6 +
     1.7 +create table lpitcore_module (
     1.8 +    modid       serial          primary key,
     1.9 +    classname   varchar(100)    not null unique,
    1.10 +    visible     boolean         not null default(true)
    1.11 +);
    1.12 +
    1.13 +create table lpitcore_user (
    1.14 +    userid          serial          primary key,
    1.15 +    username        varchar(50)     not null unique,
    1.16 +    lastname        varchar(50),
    1.17 +    givenname       varchar(50)
    1.18 +);
    1.19 +
    1.20 +create table lpitcore_authorization (
    1.21 +    modid           integer         not null references lpitcore_modules(modid) on delete cascade,
    1.22 +    userid          integer         not null references lpitcore_user(userid) on delete cascade,
    1.23 +    power           integer         not null check(power >= 0)
    1.24 +);
    1.25 +
    1.26 +create table lpitcore_menu (
    1.27 +    modid           integer         not null references lpitcore_modules(modid) on delete cascade,
    1.28 +    userid          integer         not null references lpitcore_user(userid) on delete cascade,
    1.29 +    seq             integer         not null
    1.30 +);
     2.1 --- a/src/java/de/uapcore/lightpit/AbstractLightPITServlet.java	Sat Mar 31 18:11:09 2018 +0200
     2.2 +++ b/src/java/de/uapcore/lightpit/AbstractLightPITServlet.java	Sat Mar 31 19:35:04 2018 +0200
     2.3 @@ -249,9 +249,11 @@
     2.4      private void doProcess(HttpMethod method, HttpServletRequest req, HttpServletResponse resp)
     2.5              throws ServletException, IOException {
     2.6          
     2.7 -        HttpSession session = req.getSession();
     2.8 +        // Synchronize module information with database
     2.9 +        getModuleManager().syncWithDatabase(getDatabaseFacade());
    2.10          
    2.11          // choose the requested language as session language (if available) or fall back to english, otherwise
    2.12 +        HttpSession session = req.getSession();
    2.13          if (session.getAttribute(Constants.SESSION_ATTR_LANGUAGE) == null) {
    2.14              Optional<List<String>> availableLanguages = Functions.availableLanguages(getServletContext()).map(Arrays::asList);
    2.15              Optional<Locale> reqLocale = Optional.of(req.getLocale());
     3.1 --- a/src/java/de/uapcore/lightpit/DatabaseFacade.java	Sat Mar 31 18:11:09 2018 +0200
     3.2 +++ b/src/java/de/uapcore/lightpit/DatabaseFacade.java	Sat Mar 31 19:35:04 2018 +0200
     3.3 @@ -151,7 +151,7 @@
     3.4              try {
     3.5                  dialect = Dialect.valueOf(dbDialect);
     3.6              } catch (IllegalArgumentException ex) {
     3.7 -                LOG.error(String.format("Unknown or unsupported database dialect %s. Defaulting to %s.", dbDialect, dialect));
     3.8 +                LOG.error("Unknown or unsupported database dialect {}. Defaulting to {}.", dbDialect, dialect);
     3.9              }
    3.10          }
    3.11  
     4.1 --- a/src/java/de/uapcore/lightpit/LightPITModule.java	Sat Mar 31 18:11:09 2018 +0200
     4.2 +++ b/src/java/de/uapcore/lightpit/LightPITModule.java	Sat Mar 31 19:35:04 2018 +0200
     4.3 @@ -89,6 +89,14 @@
     4.4      String titleKey() default "menuLabel";
     4.5      
     4.6      /**
     4.7 +     * If set to <code>true</code>, this module is always loaded, but never
     4.8 +     * visible in the menu or the Web UI module manager.
     4.9 +     * 
    4.10 +     * @return true, if this is a system module
    4.11 +     */
    4.12 +    boolean systemModule() default false;
    4.13 +    
    4.14 +    /**
    4.15       * Class representing the annotation.
    4.16       * This is necessary, because the EL resolver cannot deal with
    4.17       * annotation objects.
     5.1 --- a/src/java/de/uapcore/lightpit/ModuleManager.java	Sat Mar 31 18:11:09 2018 +0200
     5.2 +++ b/src/java/de/uapcore/lightpit/ModuleManager.java	Sat Mar 31 19:35:04 2018 +0200
     5.3 @@ -28,11 +28,19 @@
     5.4   */
     5.5  package de.uapcore.lightpit;
     5.6  
     5.7 +import java.sql.Connection;
     5.8 +import java.sql.PreparedStatement;
     5.9 +import java.sql.ResultSet;
    5.10 +import java.sql.SQLException;
    5.11  import java.util.ArrayList;
    5.12  import java.util.Collections;
    5.13 +import java.util.HashMap;
    5.14  import java.util.List;
    5.15 -import java.util.Objects;
    5.16 +import java.util.Map;
    5.17 +import java.util.Map.Entry;
    5.18  import java.util.Optional;
    5.19 +import java.util.concurrent.CopyOnWriteArrayList;
    5.20 +import java.util.concurrent.atomic.AtomicBoolean;
    5.21  import javax.servlet.Registration;
    5.22  import javax.servlet.ServletContext;
    5.23  import javax.servlet.ServletContextEvent;
    5.24 @@ -55,9 +63,19 @@
    5.25      public static final String SC_ATTR_NAME = ModuleManager.class.getName();
    5.26      private ServletContext sc;
    5.27      
    5.28 -    private final List<Menu> mainMenu = new ArrayList<>();
    5.29 +    /**
    5.30 +     * This flag is true, when synchronization is needed.
    5.31 +     */
    5.32 +    private AtomicBoolean dirty = new AtomicBoolean(true);
    5.33 +    
    5.34 +    private final CopyOnWriteArrayList<Menu> mainMenu = new CopyOnWriteArrayList<>();
    5.35      private final List<Menu> immutableMainMenu = Collections.unmodifiableList(mainMenu);
    5.36      
    5.37 +    /**
    5.38 +     * Maps class names to module information.
    5.39 +     */
    5.40 +    private final Map<String, LightPITModule> registeredModules = new HashMap<>();
    5.41 +    
    5.42      @Override
    5.43      public void contextInitialized(ServletContextEvent sce) {
    5.44          sc = sce.getServletContext();
    5.45 @@ -108,24 +126,10 @@
    5.46          }        
    5.47      }
    5.48      
    5.49 -    private void addModuleToMenu(String moduleClassName, LightPITModule moduleInfo) {
    5.50 -        final Menu menu = new Menu(
    5.51 -                moduleClassName,
    5.52 -                new ResourceKey(moduleInfo.bundleBaseName(), moduleInfo.menuKey()),
    5.53 -                moduleInfo.modulePath()
    5.54 -        );
    5.55 -        mainMenu.add(menu);
    5.56 -    }
    5.57 -    
    5.58      private void handleServletRegistration(String name, Registration reg) {
    5.59          final Optional<LightPITModule> moduleInfo = getModuleInfo(reg);
    5.60 -        if (moduleInfo.isPresent()) {            
    5.61 -            // TODO: implement dependency resolver
    5.62 -            
    5.63 -            if (!moduleInfo.get().menuKey().isEmpty()) {
    5.64 -                addModuleToMenu(reg.getClassName(), moduleInfo.get());
    5.65 -            }
    5.66 -            
    5.67 +        if (moduleInfo.isPresent()) {
    5.68 +            registeredModules.put(reg.getClassName(), moduleInfo.get());            
    5.69              LOG.info("Module detected: {}", name);
    5.70          } else {
    5.71              LOG.debug("Servlet {} is no module, skipping.", name);
    5.72 @@ -137,10 +141,66 @@
    5.73       */
    5.74      public void reloadAll() {
    5.75          sc.getServletRegistrations().forEach(this::handleServletRegistration);
    5.76 +        
    5.77 +        // TODO: implement dependency resolver
    5.78 +        
    5.79          LOG.info("Modules loaded.");
    5.80      }
    5.81      
    5.82      /**
    5.83 +     * Synchronizes module information with the database.
    5.84 +     * 
    5.85 +     * @param db interface to the database
    5.86 +     */
    5.87 +    public void syncWithDatabase(DatabaseFacade db) {
    5.88 +        if (dirty.compareAndSet(true, false)) {
    5.89 +            if (db.getDataSource().isPresent()) {
    5.90 +                try (Connection conn = db.getDataSource().get().getConnection()) {
    5.91 +                    PreparedStatement
    5.92 +                            check = conn.prepareStatement("SELECT visible FROM lpitcore_module WHERE classname = ?"),
    5.93 +                            insert = conn.prepareStatement("INSERT INTO lpitcore_module (classname, visible) VALUES (?, ?)");
    5.94 +                    insert.setBoolean(2, true);
    5.95 +                    // update/delete not required, we do this in the module management UI
    5.96 +                    
    5.97 +                    final List<Menu> updatedMenu = new ArrayList<>();
    5.98 +                    
    5.99 +                    for (Entry<String, LightPITModule> mod : registeredModules.entrySet()) {
   5.100 +                        if (mod.getValue().systemModule()) continue;
   5.101 +                        
   5.102 +                        check.setString(1, mod.getKey());
   5.103 +                        try (ResultSet r = check.executeQuery()) {
   5.104 +                            final boolean addToMenu;
   5.105 +                            if (r.next()) {
   5.106 +                                addToMenu = r.getBoolean(1);
   5.107 +                            } else {
   5.108 +                                insert.setString(1, mod.getKey());
   5.109 +                                insert.executeUpdate();
   5.110 +                                addToMenu = !mod.getValue().menuKey().isEmpty();
   5.111 +                            }
   5.112 +                            if (addToMenu) {
   5.113 +                                updatedMenu.add(new Menu(
   5.114 +                                        mod.getKey(),
   5.115 +                                        new ResourceKey(mod.getValue().bundleBaseName(), mod.getValue().menuKey()),
   5.116 +                                        mod.getValue().modulePath()
   5.117 +                                ));
   5.118 +                            }
   5.119 +                        }
   5.120 +                    }
   5.121 +                    
   5.122 +                    mainMenu.removeIf((e) -> !updatedMenu.contains(e));
   5.123 +                    mainMenu.addAllAbsent(updatedMenu);
   5.124 +                } catch (SQLException ex) {
   5.125 +                    LOG.error("Unexpected SQL Exception", ex);
   5.126 +                }
   5.127 +            } else {
   5.128 +                LOG.warn("No datasource present. Cannot sync module information with database.");
   5.129 +            }
   5.130 +        } else {
   5.131 +            LOG.debug("Module information clean - no synchronization required.");
   5.132 +        }
   5.133 +    }
   5.134 +    
   5.135 +    /**
   5.136       * Unloads all found modules.
   5.137       */
   5.138      public void unloadAll() {
     6.1 --- a/src/java/de/uapcore/lightpit/modules/ErrorModule.java	Sat Mar 31 18:11:09 2018 +0200
     6.2 +++ b/src/java/de/uapcore/lightpit/modules/ErrorModule.java	Sat Mar 31 19:35:04 2018 +0200
     6.3 @@ -43,8 +43,8 @@
     6.4  @LightPITModule(
     6.5          bundleBaseName = "de.uapcore.lightpit.resources.localization.error",
     6.6          modulePath = "error",
     6.7 -        menuKey = "",
     6.8 -        titleKey = "title"
     6.9 +        titleKey = "title",
    6.10 +        systemModule = true
    6.11  )
    6.12  @WebServlet(
    6.13          name = "ErrorModule",

mercurial