Sat, 23 Dec 2017 17:28:19 +0100
implements ResponseTypes
1 /*
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3 *
4 * Copyright 2017 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 java.io.IOException;
32 import java.lang.reflect.Method;
33 import java.lang.reflect.Modifier;
34 import java.util.HashMap;
35 import java.util.Map;
36 import java.util.Optional;
37 import javax.servlet.ServletException;
38 import javax.servlet.http.HttpServlet;
39 import javax.servlet.http.HttpServletRequest;
40 import javax.servlet.http.HttpServletResponse;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
44 /**
45 * A special implementation of a HTTPServlet which is focused on implementing
46 * the necessary functionality for {@link LightPITModule}s.
47 */
48 public abstract class AbstractLightPITServlet extends HttpServlet {
50 private static final Logger LOG = LoggerFactory.getLogger(AbstractLightPITServlet.class);
52 /**
53 * Store a reference to the annotation for quicker access.
54 */
55 private Optional<LightPITModule> moduleInfo = Optional.empty();
57 /**
58 * The EL proxy is necessary, because the EL resolver cannot handle annotation properties.
59 */
60 private Optional<LightPITModule.ELProxy> moduleInfoELProxy = Optional.empty();
63 @FunctionalInterface
64 private static interface HandlerMethod {
65 ResponseType apply(HttpServletRequest t, HttpServletResponse u) throws IOException, ServletException;
66 }
68 /**
69 * Invocation mapping gathered from the {@link RequestMapping} annotations.
70 */
71 private final Map<HttpMethod, Map<String, HandlerMethod>> mappings = new HashMap<>();
73 /**
74 * Gives implementing modules access to the {@link ModuleManager}.
75 * @return the module manager
76 */
77 protected final ModuleManager getModuleManager() {
78 return (ModuleManager) getServletContext().getAttribute(ModuleManager.SC_ATTR_NAME);
79 }
81 private ResponseType invokeMapping(Method method, HttpServletRequest req, HttpServletResponse resp)
82 throws IOException, ServletException {
83 try {
84 LOG.debug("invoke {}", method.getName());
85 return (ResponseType) method.invoke(this, req, resp);
86 } catch (ReflectiveOperationException | ClassCastException ex) {
87 LOG.error(String.format("invocation of method %s failed", method.getName()), ex);
88 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
89 return ResponseType.NONE;
90 }
91 }
93 @Override
94 public void init() throws ServletException {
95 moduleInfo = Optional.ofNullable(this.getClass().getAnnotation(LightPITModule.class));
96 moduleInfoELProxy = moduleInfo.map(LightPITModule.ELProxy::convert);
98 if (moduleInfo.isPresent()) {
99 scanForRequestMappings();
100 }
102 LOG.trace("{} initialized", getServletName());
103 }
105 private void scanForRequestMappings() {
106 try {
107 Method[] methods = getClass().getDeclaredMethods();
108 for (Method method : methods) {
109 Optional<RequestMapping> mapping = Optional.ofNullable(method.getAnnotation(RequestMapping.class));
110 if (mapping.isPresent()) {
111 if (!Modifier.isPublic(method.getModifiers())) {
112 LOG.warn("{} is annotated with {} but is not public",
113 method.getName(), RequestMapping.class.getSimpleName()
114 );
115 continue;
116 }
117 if (Modifier.isAbstract(method.getModifiers())) {
118 LOG.warn("{} is annotated with {} but is abstract",
119 method.getName(), RequestMapping.class.getSimpleName()
120 );
121 continue;
122 }
123 if (!ResponseType.class.isAssignableFrom(method.getReturnType())) {
124 LOG.warn("{} is annotated with {} but has the wrong return type - 'ResponseType' required",
125 method.getName(), RequestMapping.class.getSimpleName()
126 );
127 continue;
128 }
130 Class<?>[] params = method.getParameterTypes();
131 if (params.length == 2
132 && HttpServletRequest.class.isAssignableFrom(params[0])
133 && HttpServletResponse.class.isAssignableFrom(params[1])) {
135 if (mappings.computeIfAbsent(mapping.get().method(), k -> new HashMap<>()).
136 putIfAbsent(mapping.get().requestPath(),
137 (req, resp) -> invokeMapping(method, req, resp)) != null) {
138 LOG.warn("{} {} has multiple mappings",
139 mapping.get().method(),
140 mapping.get().requestPath()
141 );
142 }
144 LOG.info("{} {} maps to {}",
145 mapping.get().method(),
146 mapping.get().requestPath(),
147 method.getName()
148 );
149 } else {
150 LOG.warn("{} is annotated with {} but has the wrong parameters - (HttpServletRequest,HttpServletResponse) required",
151 method.getName(), RequestMapping.class.getSimpleName()
152 );
153 }
154 }
155 }
156 } catch (SecurityException ex) {
157 LOG.error("Scan for request mappings on declared methods failed.", ex);
158 }
159 }
161 @Override
162 public void destroy() {
163 mappings.clear();
164 LOG.trace("{} destroyed", getServletName());
165 }
168 /**
169 * Sets several requests attributes, that can be used by the JSP.
170 *
171 * @param req the servlet request object
172 * @see Constants#REQ_ATTR_PATH
173 * @see Constants#REQ_ATTR_MODULE_CLASSNAME
174 * @see Constants#REQ_ATTR_MODULE_INFO
175 */
176 private void setGenericRequestAttributes(HttpServletRequest req) {
177 req.setAttribute(Constants.REQ_ATTR_PATH, Functions.fullPath(req));
179 req.setAttribute(Constants.REQ_ATTR_MODULE_CLASSNAME, this.getClass().getName());
181 moduleInfoELProxy.ifPresent((proxy) -> req.setAttribute(Constants.REQ_ATTR_MODULE_INFO, proxy));
182 }
184 private void forwardToFullView(HttpServletRequest req, HttpServletResponse resp)
185 throws IOException, ServletException {
187 setGenericRequestAttributes(req);
188 req.setAttribute(Constants.REQ_ATTR_MENU, getModuleManager().getMainMenu());
189 req.getRequestDispatcher(Functions.jspPath("full.jsp")).forward(req, resp);
190 }
192 private Optional<HandlerMethod> findMapping(HttpMethod method, HttpServletRequest req) {
193 return Optional.ofNullable(mappings.get(method)).map(
194 (rm) -> rm.get(Optional.ofNullable(req.getPathInfo()).orElse(""))
195 );
196 }
198 private void forwardAsSepcified(ResponseType type, HttpServletRequest req, HttpServletResponse resp)
199 throws ServletException, IOException {
200 switch (type) {
201 case NONE: return;
202 case HTML_FULL:
203 forwardToFullView(req, resp);
204 return;
205 // TODO: implement remaining response types
206 default:
207 // this code should be unreachable
208 LOG.error("ResponseType switch is not exhaustive - this is a bug!");
209 throw new UnsupportedOperationException();
210 }
211 }
213 private void doProcess(HttpMethod method, HttpServletRequest req, HttpServletResponse resp)
214 throws ServletException, IOException {
215 Optional<HandlerMethod> mapping = findMapping(method, req);
216 if (mapping.isPresent()) {
217 forwardAsSepcified(mapping.get().apply(req, resp), req, resp);
218 } else {
219 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
220 }
221 }
223 @Override
224 protected final void doGet(HttpServletRequest req, HttpServletResponse resp)
225 throws ServletException, IOException {
226 doProcess(HttpMethod.GET, req, resp);
227 }
229 @Override
230 protected final void doPost(HttpServletRequest req, HttpServletResponse resp)
231 throws ServletException, IOException {
232 doProcess(HttpMethod.POST, req, resp);
233 }
234 }