Sun, 17 Dec 2017 01:45:28 +0100
implements simple request mapper
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 java.util.function.BiConsumer;
38 import javax.servlet.ServletException;
39 import javax.servlet.http.HttpServlet;
40 import javax.servlet.http.HttpServletRequest;
41 import javax.servlet.http.HttpServletResponse;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
45 /**
46 * A special implementation of a HTTPServlet which is focused on implementing
47 * the necessary functionality for {@link LightPITModule}s.
48 */
49 public abstract class AbstractLightPITServlet extends HttpServlet {
51 private static final Logger LOG = LoggerFactory.getLogger(AbstractLightPITServlet.class);
53 /**
54 * Store a reference to the annotation for quicker access.
55 */
56 private Optional<LightPITModule> moduleInfo = Optional.empty();
58 /**
59 * The EL proxy is necessary, because the EL resolver cannot handle annotation properties.
60 */
61 private Optional<LightPITModule.ELProxy> moduleInfoELProxy = Optional.empty();
63 /**
64 * Invocation mapping gathered from the {@link RequestMapping} annotations.
65 */
66 private final Map<HttpMethod, Map<String, BiConsumer<HttpServletRequest, HttpServletResponse>>> mappings = new HashMap<>();
68 /**
69 * Gives implementing modules access to the {@link ModuleManager}.
70 * @return the module manager
71 */
72 protected final ModuleManager getModuleManager() {
73 return (ModuleManager) getServletContext().getAttribute(ModuleManager.SC_ATTR_NAME);
74 }
76 private void invokeMapping(Method method, HttpServletRequest req, HttpServletResponse resp) {
77 try {
78 LOG.debug("invoke {}", method.getName());
79 method.invoke(this, req, resp);
80 } catch (ReflectiveOperationException ex) {
81 LOG.error(String.format("invocation of method %s failed", method.getName()), ex);
82 }
83 }
85 @Override
86 public void init() throws ServletException {
87 moduleInfo = Optional.ofNullable(this.getClass().getAnnotation(LightPITModule.class));
88 moduleInfoELProxy = moduleInfo.map(LightPITModule.ELProxy::convert);
90 if (moduleInfo.isPresent()) {
91 Method[] methods = getClass().getDeclaredMethods();
92 for (Method method : methods) {
93 Optional<RequestMapping> mapping = Optional.ofNullable(method.getAnnotation(RequestMapping.class));
94 if (mapping.isPresent()) {
95 if (!Modifier.isPublic(method.getModifiers())) {
96 LOG.warn("{} is annotated with {} but is not public",
97 method.getName(), RequestMapping.class.getSimpleName()
98 );
99 continue;
100 }
101 if (Modifier.isAbstract(method.getModifiers())) {
102 LOG.warn("{} is annotated with {} but is abstract",
103 method.getName(), RequestMapping.class.getSimpleName()
104 );
105 continue;
106 }
108 Class<?>[] params = method.getParameterTypes();
109 if (params.length == 2
110 && HttpServletRequest.class.isAssignableFrom(params[0])
111 && HttpServletResponse.class.isAssignableFrom(params[1])) {
113 if (mappings.computeIfAbsent(mapping.get().method(), k -> new HashMap<>()).
114 putIfAbsent(mapping.get().requestPath(),
115 (req, resp) -> invokeMapping(method, req, resp)) != null) {
116 LOG.warn("{} {} has multiple mappings",
117 mapping.get().method(),
118 mapping.get().requestPath()
119 );
120 }
122 LOG.info("{} {} maps to {}",
123 mapping.get().method(),
124 mapping.get().requestPath(),
125 method.getName()
126 );
127 } else {
128 LOG.warn("{} is annotated with {} but has the wrong signature - (HttpServletRequest,HttpServletResponse) required",
129 method.getName(), RequestMapping.class.getSimpleName()
130 );
131 }
132 }
133 }
134 }
136 LOG.trace("{} initialized", getServletName());
137 }
139 @Override
140 public void destroy() {
141 mappings.clear();
142 LOG.trace("{} destroyed", getServletName());
143 }
146 /**
147 * Sets several requests attributes, that can be used by the JSP.
148 *
149 * @param req the servlet request object
150 * @see Constants#REQ_ATTR_PATH
151 * @see Constants#REQ_ATTR_MODULE_CLASSNAME
152 * @see Constants#REQ_ATTR_MODULE_INFO
153 */
154 private void setGenericRequestAttributes(HttpServletRequest req) {
155 req.setAttribute(Constants.REQ_ATTR_PATH, Functions.fullPath(req));
157 req.setAttribute(Constants.REQ_ATTR_MODULE_CLASSNAME, this.getClass().getName());
159 moduleInfoELProxy.ifPresent((proxy) -> req.setAttribute(Constants.REQ_ATTR_MODULE_INFO, proxy));
160 }
162 private void forwardToFullView(HttpServletRequest req, HttpServletResponse resp)
163 throws IOException, ServletException {
165 setGenericRequestAttributes(req);
166 req.setAttribute(Constants.REQ_ATTR_MENU, getModuleManager().getMainMenu());
167 req.getRequestDispatcher(Functions.jspPath("full.jsp")).forward(req, resp);
168 }
170 private Optional<BiConsumer<HttpServletRequest, HttpServletResponse>> findMapping(HttpMethod method, HttpServletRequest req) {
171 return Optional.ofNullable(mappings.get(method)).map(
172 (rm) -> rm.get(Optional.ofNullable(req.getPathInfo()).orElse(""))
173 );
174 }
176 @Override
177 protected final void doGet(HttpServletRequest req, HttpServletResponse resp)
178 throws ServletException, IOException {
180 findMapping(HttpMethod.GET, req).ifPresent((consumer) -> consumer.accept(req, resp));
182 // TODO: let the invoked handler decide (signature must be changed from a BiConsumer to a BiFunction)
183 // TODO: we should call a default handler, if no specific mapping could be found
184 forwardToFullView(req, resp);
185 }
187 @Override
188 protected final void doPost(HttpServletRequest req, HttpServletResponse resp)
189 throws ServletException, IOException {
191 findMapping(HttpMethod.POST, req).ifPresent((consumer) -> consumer.accept(req, resp));
193 forwardToFullView(req, resp);
194 }
195 }