Sat, 31 Mar 2018 19:35:04 +0200
module synchronization with database
--- /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",