106 return new PGDataAccessObjects(connection); |
106 return new PGDataAccessObjects(connection); |
107 } |
107 } |
108 throw new UnsupportedOperationException("Non-exhaustive if-else - this is a bug."); |
108 throw new UnsupportedOperationException("Non-exhaustive if-else - this is a bug."); |
109 } |
109 } |
110 |
110 |
111 private ResponseType invokeMapping(Map.Entry<PathPattern, Method> mapping, HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws IOException { |
111 private void invokeMapping(Map.Entry<PathPattern, Method> mapping, HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws IOException { |
112 final var pathPattern = mapping.getKey(); |
112 final var pathPattern = mapping.getKey(); |
113 final var method = mapping.getValue(); |
113 final var method = mapping.getValue(); |
114 try { |
114 try { |
115 LOG.trace("invoke {}#{}", method.getDeclaringClass().getName(), method.getName()); |
115 LOG.trace("invoke {}#{}", method.getDeclaringClass().getName(), method.getName()); |
116 final var paramTypes = method.getParameterTypes(); |
116 final var paramTypes = method.getParameterTypes(); |
126 } |
126 } |
127 if (paramTypes[i].isAssignableFrom(PathParameters.class)) { |
127 if (paramTypes[i].isAssignableFrom(PathParameters.class)) { |
128 paramValues[i] = pathPattern.obtainPathParameters(sanitizeRequestPath(req)); |
128 paramValues[i] = pathPattern.obtainPathParameters(sanitizeRequestPath(req)); |
129 } |
129 } |
130 } |
130 } |
131 return (ResponseType) method.invoke(this, paramValues); |
131 method.invoke(this, paramValues); |
132 } catch (InvocationTargetException ex) { |
132 } catch (InvocationTargetException ex) { |
133 LOG.error("invocation of method {}::{} failed: {}", |
133 LOG.error("invocation of method {}::{} failed: {}", |
134 method.getDeclaringClass().getName(), method.getName(), ex.getTargetException().getMessage()); |
134 method.getDeclaringClass().getName(), method.getName(), ex.getTargetException().getMessage()); |
135 LOG.debug("Details: ", ex.getTargetException()); |
135 LOG.debug("Details: ", ex.getTargetException()); |
136 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex.getTargetException().getMessage()); |
136 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex.getTargetException().getMessage()); |
137 return ResponseType.NONE; |
|
138 } catch (ReflectiveOperationException | ClassCastException ex) { |
137 } catch (ReflectiveOperationException | ClassCastException ex) { |
139 LOG.error("invocation of method {}::{} failed: {}", |
138 LOG.error("invocation of method {}::{} failed: {}", |
140 method.getDeclaringClass().getName(), method.getName(), ex.getMessage()); |
139 method.getDeclaringClass().getName(), method.getName(), ex.getMessage()); |
141 LOG.debug("Details: ", ex); |
140 LOG.debug("Details: ", ex); |
142 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex.getMessage()); |
141 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex.getMessage()); |
143 return ResponseType.NONE; |
|
144 } |
142 } |
145 } |
143 } |
146 |
144 |
147 @Override |
145 @Override |
148 public void init() throws ServletException { |
146 public void init() throws ServletException { |
170 ); |
168 ); |
171 continue; |
169 continue; |
172 } |
170 } |
173 if (Modifier.isAbstract(method.getModifiers())) { |
171 if (Modifier.isAbstract(method.getModifiers())) { |
174 LOG.warn("{} is annotated with {} but is abstract", |
172 LOG.warn("{} is annotated with {} but is abstract", |
175 method.getName(), RequestMapping.class.getSimpleName() |
|
176 ); |
|
177 continue; |
|
178 } |
|
179 if (!ResponseType.class.isAssignableFrom(method.getReturnType())) { |
|
180 LOG.warn("{} is annotated with {} but has the wrong return type - 'ResponseType' required", |
|
181 method.getName(), RequestMapping.class.getSimpleName() |
173 method.getName(), RequestMapping.class.getSimpleName() |
182 ); |
174 ); |
183 continue; |
175 continue; |
184 } |
176 } |
185 |
177 |
386 kv -> kv.getKey().matches(sanitizeRequestPath(req)) |
378 kv -> kv.getKey().matches(sanitizeRequestPath(req)) |
387 ).findAny() |
379 ).findAny() |
388 ); |
380 ); |
389 } |
381 } |
390 |
382 |
391 private void forwardAsSpecified(ResponseType type, HttpServletRequest req, HttpServletResponse resp) |
383 protected void renderSite(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { |
392 throws ServletException, IOException { |
384 req.getRequestDispatcher(SITE_JSP).forward(req, resp); |
393 switch (type) { |
|
394 case NONE: |
|
395 return; |
|
396 case HTML: |
|
397 req.getRequestDispatcher(SITE_JSP).forward(req, resp); |
|
398 return; |
|
399 // TODO: implement remaining response types |
|
400 default: |
|
401 throw new AssertionError("ResponseType switch is not exhaustive - this is a bug!"); |
|
402 } |
|
403 } |
385 } |
404 |
386 |
405 private void doProcess(HttpMethod method, HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { |
387 private void doProcess(HttpMethod method, HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { |
406 |
388 |
407 // choose the requested language as session language (if available) or fall back to english, otherwise |
389 // choose the requested language as session language (if available) or fall back to english, otherwise |
426 |
408 |
427 // if this is an error path, bypass the normal flow |
409 // if this is an error path, bypass the normal flow |
428 if (fullPath.startsWith("/error/")) { |
410 if (fullPath.startsWith("/error/")) { |
429 final var mapping = findMapping(method, req); |
411 final var mapping = findMapping(method, req); |
430 if (mapping.isPresent()) { |
412 if (mapping.isPresent()) { |
431 forwardAsSpecified(invokeMapping(mapping.get(), req, resp, null), req, resp); |
413 invokeMapping(mapping.get(), req, resp, null); |
432 } |
414 } |
433 return; |
415 return; |
434 } |
416 } |
435 |
417 |
436 // obtain a connection and create the data access objects |
418 // obtain a connection and create the data access objects |
445 try { |
427 try { |
446 connection.setAutoCommit(false); |
428 connection.setAutoCommit(false); |
447 // call the handler, if available, or send an HTTP 404 error |
429 // call the handler, if available, or send an HTTP 404 error |
448 final var mapping = findMapping(method, req); |
430 final var mapping = findMapping(method, req); |
449 if (mapping.isPresent()) { |
431 if (mapping.isPresent()) { |
450 forwardAsSpecified(invokeMapping(mapping.get(), req, resp, dao), req, resp); |
432 invokeMapping(mapping.get(), req, resp, dao); |
451 } else { |
433 } else { |
452 resp.sendError(HttpServletResponse.SC_NOT_FOUND); |
434 resp.sendError(HttpServletResponse.SC_NOT_FOUND); |
453 } |
435 } |
454 connection.commit(); |
436 connection.commit(); |
455 } catch (SQLException ex) { |
437 } catch (SQLException ex) { |