58 /** |
58 /** |
59 * The EL proxy is necessary, because the EL resolver cannot handle annotation properties. |
59 * The EL proxy is necessary, because the EL resolver cannot handle annotation properties. |
60 */ |
60 */ |
61 private LightPITModule.ELProxy moduleInfo = null; |
61 private LightPITModule.ELProxy moduleInfo = null; |
62 |
62 |
63 |
|
64 @FunctionalInterface |
|
65 private interface HandlerMethod { |
|
66 ResponseType apply(HttpServletRequest request, HttpServletResponse response, DataAccessObjects dao) throws IOException, SQLException; |
|
67 } |
|
68 |
|
69 /** |
63 /** |
70 * Invocation mapping gathered from the {@link RequestMapping} annotations. |
64 * Invocation mapping gathered from the {@link RequestMapping} annotations. |
71 * <p> |
65 * <p> |
72 * Paths in this map must always start with a leading slash, although |
66 * Paths in this map must always start with a leading slash, although |
73 * the specification in the annotation must not start with a leading slash. |
67 * the specification in the annotation must not start with a leading slash. |
74 * <p> |
68 * <p> |
75 * The reason for this is the different handling of empty paths in |
69 * The reason for this is the different handling of empty paths in |
76 * {@link HttpServletRequest#getPathInfo()}. |
70 * {@link HttpServletRequest#getPathInfo()}. |
77 */ |
71 */ |
78 private final Map<HttpMethod, Map<String, HandlerMethod>> mappings = new HashMap<>(); |
72 private final Map<HttpMethod, Map<String, Method>> mappings = new HashMap<>(); |
79 |
73 |
80 /** |
74 /** |
81 * Gives implementing modules access to the {@link ModuleManager}. |
75 * Gives implementing modules access to the {@link ModuleManager}. |
82 * |
76 * |
83 * @return the module manager |
77 * @return the module manager |
93 * @param connection the SQL connection |
87 * @param connection the SQL connection |
94 * @return a set of data access objects |
88 * @return a set of data access objects |
95 */ |
89 */ |
96 private DataAccessObjects createDataAccessObjects(Connection connection) throws SQLException { |
90 private DataAccessObjects createDataAccessObjects(Connection connection) throws SQLException { |
97 final var df = (DatabaseFacade) getServletContext().getAttribute(DatabaseFacade.SC_ATTR_NAME); |
91 final var df = (DatabaseFacade) getServletContext().getAttribute(DatabaseFacade.SC_ATTR_NAME); |
98 switch (df.getSQLDialect()) { |
92 if (df.getSQLDialect() == DatabaseFacade.Dialect.Postgres) { |
99 case Postgres: |
93 return new PGDataAccessObjects(connection); |
100 return new PGDataAccessObjects(connection); |
94 } |
101 default: |
95 throw new AssertionError("Non-exhaustive if-else - this is a bug."); |
102 throw new AssertionError("Non-exhaustive switch - this is a bug."); |
|
103 } |
|
104 } |
96 } |
105 |
97 |
106 private ResponseType invokeMapping(Method method, HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws IOException { |
98 private ResponseType invokeMapping(Method method, HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws IOException { |
107 try { |
99 try { |
108 LOG.trace("invoke {}#{}", method.getDeclaringClass().getName(), method.getName()); |
100 LOG.trace("invoke {}#{}", method.getDeclaringClass().getName(), method.getName()); |
158 && HttpServletResponse.class.isAssignableFrom(params[1]) |
150 && HttpServletResponse.class.isAssignableFrom(params[1]) |
159 && DataAccessObjects.class.isAssignableFrom(params[2])) { |
151 && DataAccessObjects.class.isAssignableFrom(params[2])) { |
160 |
152 |
161 final String requestPath = "/" + mapping.get().requestPath(); |
153 final String requestPath = "/" + mapping.get().requestPath(); |
162 |
154 |
163 if (mappings.computeIfAbsent(mapping.get().method(), k -> new HashMap<>()). |
155 if (mappings |
164 putIfAbsent(requestPath, |
156 .computeIfAbsent(mapping.get().method(), k -> new HashMap<>()) |
165 (req, resp, dao) -> invokeMapping(method, req, resp, dao)) != null) { |
157 .putIfAbsent(requestPath, method) != null) { |
166 LOG.warn("{} {} has multiple mappings", |
158 LOG.warn("{} {} has multiple mappings", |
167 mapping.get().method(), |
159 mapping.get().method(), |
168 mapping.get().requestPath() |
160 mapping.get().requestPath() |
169 ); |
161 ); |
170 } |
162 } |
230 |
222 |
231 req.setAttribute(Constants.REQ_ATTR_MENU, getModuleManager().getMainMenu()); |
223 req.setAttribute(Constants.REQ_ATTR_MENU, getModuleManager().getMainMenu()); |
232 req.getRequestDispatcher(HTML_FULL_DISPATCHER).forward(req, resp); |
224 req.getRequestDispatcher(HTML_FULL_DISPATCHER).forward(req, resp); |
233 } |
225 } |
234 |
226 |
235 private Optional<HandlerMethod> findMapping(HttpMethod method, HttpServletRequest req) { |
227 private Optional<Method> findMapping(HttpMethod method, HttpServletRequest req) { |
236 return Optional.ofNullable(mappings.get(method)).map( |
228 return Optional.ofNullable(mappings.get(method)) |
237 (rm) -> rm.get(Optional.ofNullable(req.getPathInfo()).orElse("/")) |
229 .map(rm -> rm.get(Optional.ofNullable(req.getPathInfo()).orElse("/")) |
238 ); |
230 ); |
239 } |
231 } |
240 |
232 |
241 private void forwardAsSpecified(ResponseType type, HttpServletRequest req, HttpServletResponse resp) |
233 private void forwardAsSpecified(ResponseType type, HttpServletRequest req, HttpServletResponse resp) |
242 throws ServletException, IOException { |
234 throws ServletException, IOException { |
243 switch (type) { |
235 switch (type) { |
275 |
267 |
276 // obtain a connection and create the data access objects |
268 // obtain a connection and create the data access objects |
277 final var db = (DatabaseFacade) req.getServletContext().getAttribute(DatabaseFacade.SC_ATTR_NAME); |
269 final var db = (DatabaseFacade) req.getServletContext().getAttribute(DatabaseFacade.SC_ATTR_NAME); |
278 try (final var connection = db.getDataSource().getConnection()) { |
270 try (final var connection = db.getDataSource().getConnection()) { |
279 final var dao = createDataAccessObjects(connection); |
271 final var dao = createDataAccessObjects(connection); |
280 // call the handler, if available, or send an HTTP 404 error |
272 try { |
281 final var mapping = findMapping(method, req); |
273 connection.setAutoCommit(false); |
282 if (mapping.isPresent()) { |
274 // call the handler, if available, or send an HTTP 404 error |
283 forwardAsSpecified(mapping.get().apply(req, resp, dao), req, resp); |
275 final var mapping = findMapping(method, req); |
284 } else { |
276 if (mapping.isPresent()) { |
285 resp.sendError(HttpServletResponse.SC_NOT_FOUND); |
277 forwardAsSpecified(invokeMapping(mapping.get(), req, resp, dao), req, resp); |
|
278 } else { |
|
279 resp.sendError(HttpServletResponse.SC_NOT_FOUND); |
|
280 } |
|
281 connection.commit(); |
|
282 } catch (SQLException ex) { |
|
283 LOG.warn("Database transaction failed (Code {}): {}", ex.getErrorCode(), ex.getMessage()); |
|
284 LOG.debug("Details: ", ex); |
|
285 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Unhandled Transaction Error - Code:" + ex.getErrorCode()); |
|
286 connection.rollback(); |
286 } |
287 } |
287 } catch (SQLException ex) { |
288 } catch (SQLException ex) { |
288 LOG.error("Database exception (Code {}): {}", ex.getErrorCode(), ex.getMessage()); |
289 LOG.error("Severe Database Exception (Code {}): {}", ex.getErrorCode(), ex.getMessage()); |
289 LOG.debug("Details: ", ex); |
290 LOG.debug("Details: ", ex); |
290 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Database Error - Code:" + ex.getErrorCode()); |
291 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Database Error - Code:" + ex.getErrorCode()); |
291 } |
292 } |
292 } |
293 } |
293 |
294 |