26 * POSSIBILITY OF SUCH DAMAGE. |
26 * POSSIBILITY OF SUCH DAMAGE. |
27 * |
27 * |
28 */ |
28 */ |
29 package de.uapcore.lightpit; |
29 package de.uapcore.lightpit; |
30 |
30 |
|
31 import de.uapcore.lightpit.dao.DataAccessObjects; |
|
32 import de.uapcore.lightpit.dao.postgres.PGDataAccessObjects; |
31 import org.slf4j.Logger; |
33 import org.slf4j.Logger; |
32 import org.slf4j.LoggerFactory; |
34 import org.slf4j.LoggerFactory; |
33 |
35 |
34 import javax.servlet.ServletException; |
36 import javax.servlet.ServletException; |
35 import javax.servlet.http.HttpServlet; |
37 import javax.servlet.http.HttpServlet; |
37 import javax.servlet.http.HttpServletResponse; |
39 import javax.servlet.http.HttpServletResponse; |
38 import javax.servlet.http.HttpSession; |
40 import javax.servlet.http.HttpSession; |
39 import java.io.IOException; |
41 import java.io.IOException; |
40 import java.lang.reflect.Method; |
42 import java.lang.reflect.Method; |
41 import java.lang.reflect.Modifier; |
43 import java.lang.reflect.Modifier; |
|
44 import java.sql.Connection; |
|
45 import java.sql.SQLException; |
42 import java.util.*; |
46 import java.util.*; |
43 |
47 |
44 /** |
48 /** |
45 * A special implementation of a HTTPServlet which is focused on implementing |
49 * A special implementation of a HTTPServlet which is focused on implementing |
46 * the necessary functionality for {@link LightPITModule}s. |
50 * the necessary functionality for {@link LightPITModule}s. |
57 private LightPITModule.ELProxy moduleInfo = null; |
61 private LightPITModule.ELProxy moduleInfo = null; |
58 |
62 |
59 |
63 |
60 @FunctionalInterface |
64 @FunctionalInterface |
61 private interface HandlerMethod { |
65 private interface HandlerMethod { |
62 ResponseType apply(HttpServletRequest t, HttpServletResponse u) throws IOException; |
66 ResponseType apply(HttpServletRequest request, HttpServletResponse response, DataAccessObjects dao) throws IOException, SQLException; |
63 } |
67 } |
64 |
68 |
65 /** |
69 /** |
66 * Invocation mapping gathered from the {@link RequestMapping} annotations. |
70 * Invocation mapping gathered from the {@link RequestMapping} annotations. |
67 * <p> |
71 * <p> |
80 */ |
84 */ |
81 protected final ModuleManager getModuleManager() { |
85 protected final ModuleManager getModuleManager() { |
82 return (ModuleManager) getServletContext().getAttribute(ModuleManager.SC_ATTR_NAME); |
86 return (ModuleManager) getServletContext().getAttribute(ModuleManager.SC_ATTR_NAME); |
83 } |
87 } |
84 |
88 |
85 /** |
89 |
86 * Gives implementing modules access to the {@link DatabaseFacade}. |
90 /** |
|
91 * Creates a set of data access objects for the specified connection. |
87 * |
92 * |
88 * @return the database facade |
93 * @param connection the SQL connection |
89 */ |
94 * @return a set of data access objects |
90 protected final DatabaseFacade getDatabaseFacade() { |
95 */ |
91 return (DatabaseFacade) getServletContext().getAttribute(DatabaseFacade.SC_ATTR_NAME); |
96 private DataAccessObjects createDataAccessObjects(Connection connection) throws SQLException { |
92 } |
97 final var df = (DatabaseFacade) getServletContext().getAttribute(DatabaseFacade.SC_ATTR_NAME); |
93 |
98 switch (df.getSQLDialect()) { |
94 private ResponseType invokeMapping(Method method, HttpServletRequest req, HttpServletResponse resp) throws IOException { |
99 case Postgres: |
|
100 return new PGDataAccessObjects(connection); |
|
101 default: |
|
102 throw new AssertionError("Non-exhaustive switch - this is a bug."); |
|
103 } |
|
104 } |
|
105 |
|
106 private ResponseType invokeMapping(Method method, HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws IOException { |
95 try { |
107 try { |
96 LOG.trace("invoke {}#{}", method.getDeclaringClass().getName(), method.getName()); |
108 LOG.trace("invoke {}#{}", method.getDeclaringClass().getName(), method.getName()); |
97 return (ResponseType) method.invoke(this, req, resp); |
109 return (ResponseType) method.invoke(this, req, resp, dao); |
98 } catch (ReflectiveOperationException | ClassCastException ex) { |
110 } catch (ReflectiveOperationException | ClassCastException ex) { |
99 LOG.error(String.format("invocation of method %s failed", method.getName()), ex); |
111 LOG.error("invocation of method {} failed: {}", method.getName(), ex.getMessage()); |
|
112 LOG.debug("Details: ", ex); |
100 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); |
113 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); |
101 return ResponseType.NONE; |
114 return ResponseType.NONE; |
102 } |
115 } |
103 } |
116 } |
104 |
117 |
138 ); |
151 ); |
139 continue; |
152 continue; |
140 } |
153 } |
141 |
154 |
142 Class<?>[] params = method.getParameterTypes(); |
155 Class<?>[] params = method.getParameterTypes(); |
143 if (params.length == 2 |
156 if (params.length == 3 |
144 && HttpServletRequest.class.isAssignableFrom(params[0]) |
157 && HttpServletRequest.class.isAssignableFrom(params[0]) |
145 && HttpServletResponse.class.isAssignableFrom(params[1])) { |
158 && HttpServletResponse.class.isAssignableFrom(params[1]) |
|
159 && DataAccessObjects.class.isAssignableFrom(params[2])) { |
146 |
160 |
147 final String requestPath = "/" + mapping.get().requestPath(); |
161 final String requestPath = "/" + mapping.get().requestPath(); |
148 |
162 |
149 if (mappings.computeIfAbsent(mapping.get().method(), k -> new HashMap<>()). |
163 if (mappings.computeIfAbsent(mapping.get().method(), k -> new HashMap<>()). |
150 putIfAbsent(requestPath, |
164 putIfAbsent(requestPath, |
151 (req, resp) -> invokeMapping(method, req, resp)) != null) { |
165 (req, resp, dao) -> invokeMapping(method, req, resp, dao)) != null) { |
152 LOG.warn("{} {} has multiple mappings", |
166 LOG.warn("{} {} has multiple mappings", |
153 mapping.get().method(), |
167 mapping.get().method(), |
154 mapping.get().requestPath() |
168 mapping.get().requestPath() |
155 ); |
169 ); |
156 } |
170 } |
236 default: |
250 default: |
237 throw new AssertionError("ResponseType switch is not exhaustive - this is a bug!"); |
251 throw new AssertionError("ResponseType switch is not exhaustive - this is a bug!"); |
238 } |
252 } |
239 } |
253 } |
240 |
254 |
241 private void doProcess(HttpMethod method, HttpServletRequest req, HttpServletResponse resp) |
255 private void doProcess(HttpMethod method, HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { |
242 throws ServletException, IOException { |
|
243 |
256 |
244 // choose the requested language as session language (if available) or fall back to english, otherwise |
257 // choose the requested language as session language (if available) or fall back to english, otherwise |
245 HttpSession session = req.getSession(); |
258 HttpSession session = req.getSession(); |
246 if (session.getAttribute(Constants.SESSION_ATTR_LANGUAGE) == null) { |
259 if (session.getAttribute(Constants.SESSION_ATTR_LANGUAGE) == null) { |
247 Optional<List<String>> availableLanguages = Functions.availableLanguages(getServletContext()).map(Arrays::asList); |
260 Optional<List<String>> availableLanguages = Functions.availableLanguages(getServletContext()).map(Arrays::asList); |
258 // set some internal request attributes |
271 // set some internal request attributes |
259 req.setAttribute(Constants.REQ_ATTR_PATH, Functions.fullPath(req)); |
272 req.setAttribute(Constants.REQ_ATTR_PATH, Functions.fullPath(req)); |
260 req.setAttribute(Constants.REQ_ATTR_MODULE_CLASSNAME, this.getClass().getName()); |
273 req.setAttribute(Constants.REQ_ATTR_MODULE_CLASSNAME, this.getClass().getName()); |
261 Optional.ofNullable(moduleInfo).ifPresent((proxy) -> req.setAttribute(Constants.REQ_ATTR_MODULE_INFO, proxy)); |
274 Optional.ofNullable(moduleInfo).ifPresent((proxy) -> req.setAttribute(Constants.REQ_ATTR_MODULE_INFO, proxy)); |
262 |
275 |
263 |
276 // obtain a connection and create the data access objects |
264 // call the handler, if available, or send an HTTP 404 error |
277 final var db = (DatabaseFacade) req.getServletContext().getAttribute(DatabaseFacade.SC_ATTR_NAME); |
265 Optional<HandlerMethod> mapping = findMapping(method, req); |
278 try (final var connection = db.getDataSource().getConnection()) { |
266 if (mapping.isPresent()) { |
279 final var dao = createDataAccessObjects(connection); |
267 forwardAsSpecified(mapping.get().apply(req, resp), req, resp); |
280 // call the handler, if available, or send an HTTP 404 error |
268 } else { |
281 final var mapping = findMapping(method, req); |
269 resp.sendError(HttpServletResponse.SC_NOT_FOUND); |
282 if (mapping.isPresent()) { |
|
283 forwardAsSpecified(mapping.get().apply(req, resp, dao), req, resp); |
|
284 } else { |
|
285 resp.sendError(HttpServletResponse.SC_NOT_FOUND); |
|
286 } |
|
287 } catch (SQLException ex) { |
|
288 LOG.error("Database exception (Code {}): {}", ex.getErrorCode(), ex.getMessage()); |
|
289 LOG.debug("Details: ", ex); |
|
290 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Database Error - Code:" + ex.getErrorCode()); |
270 } |
291 } |
271 } |
292 } |
272 |
293 |
273 @Override |
294 @Override |
274 protected final void doGet(HttpServletRequest req, HttpServletResponse resp) |
295 protected final void doGet(HttpServletRequest req, HttpServletResponse resp) |