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

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

mercurial