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