32 import java.lang.reflect.Method; |
32 import java.lang.reflect.Method; |
33 import java.lang.reflect.Modifier; |
33 import java.lang.reflect.Modifier; |
34 import java.util.HashMap; |
34 import java.util.HashMap; |
35 import java.util.Map; |
35 import java.util.Map; |
36 import java.util.Optional; |
36 import java.util.Optional; |
37 import java.util.function.BiConsumer; |
|
38 import javax.servlet.ServletException; |
37 import javax.servlet.ServletException; |
39 import javax.servlet.http.HttpServlet; |
38 import javax.servlet.http.HttpServlet; |
40 import javax.servlet.http.HttpServletRequest; |
39 import javax.servlet.http.HttpServletRequest; |
41 import javax.servlet.http.HttpServletResponse; |
40 import javax.servlet.http.HttpServletResponse; |
42 import org.slf4j.Logger; |
41 import org.slf4j.Logger; |
58 /** |
57 /** |
59 * The EL proxy is necessary, because the EL resolver cannot handle annotation properties. |
58 * The EL proxy is necessary, because the EL resolver cannot handle annotation properties. |
60 */ |
59 */ |
61 private Optional<LightPITModule.ELProxy> moduleInfoELProxy = Optional.empty(); |
60 private Optional<LightPITModule.ELProxy> moduleInfoELProxy = Optional.empty(); |
62 |
61 |
|
62 |
|
63 @FunctionalInterface |
|
64 private static interface HandlerMethod { |
|
65 ResponseType apply(HttpServletRequest t, HttpServletResponse u) throws IOException, ServletException; |
|
66 } |
|
67 |
63 /** |
68 /** |
64 * Invocation mapping gathered from the {@link RequestMapping} annotations. |
69 * Invocation mapping gathered from the {@link RequestMapping} annotations. |
65 */ |
70 */ |
66 private final Map<HttpMethod, Map<String, BiConsumer<HttpServletRequest, HttpServletResponse>>> mappings = new HashMap<>(); |
71 private final Map<HttpMethod, Map<String, HandlerMethod>> mappings = new HashMap<>(); |
67 |
72 |
68 /** |
73 /** |
69 * Gives implementing modules access to the {@link ModuleManager}. |
74 * Gives implementing modules access to the {@link ModuleManager}. |
70 * @return the module manager |
75 * @return the module manager |
71 */ |
76 */ |
72 protected final ModuleManager getModuleManager() { |
77 protected final ModuleManager getModuleManager() { |
73 return (ModuleManager) getServletContext().getAttribute(ModuleManager.SC_ATTR_NAME); |
78 return (ModuleManager) getServletContext().getAttribute(ModuleManager.SC_ATTR_NAME); |
74 } |
79 } |
75 |
80 |
76 private void invokeMapping(Method method, HttpServletRequest req, HttpServletResponse resp) { |
81 private ResponseType invokeMapping(Method method, HttpServletRequest req, HttpServletResponse resp) |
|
82 throws IOException, ServletException { |
77 try { |
83 try { |
78 LOG.debug("invoke {}", method.getName()); |
84 LOG.debug("invoke {}", method.getName()); |
79 method.invoke(this, req, resp); |
85 return (ResponseType) method.invoke(this, req, resp); |
80 } catch (ReflectiveOperationException ex) { |
86 } catch (ReflectiveOperationException | ClassCastException ex) { |
81 LOG.error(String.format("invocation of method %s failed", method.getName()), 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; |
82 } |
90 } |
83 } |
91 } |
84 |
92 |
85 @Override |
93 @Override |
86 public void init() throws ServletException { |
94 public void init() throws ServletException { |
87 moduleInfo = Optional.ofNullable(this.getClass().getAnnotation(LightPITModule.class)); |
95 moduleInfo = Optional.ofNullable(this.getClass().getAnnotation(LightPITModule.class)); |
88 moduleInfoELProxy = moduleInfo.map(LightPITModule.ELProxy::convert); |
96 moduleInfoELProxy = moduleInfo.map(LightPITModule.ELProxy::convert); |
89 |
97 |
90 if (moduleInfo.isPresent()) { |
98 if (moduleInfo.isPresent()) { |
|
99 scanForRequestMappings(); |
|
100 } |
|
101 |
|
102 LOG.trace("{} initialized", getServletName()); |
|
103 } |
|
104 |
|
105 private void scanForRequestMappings() { |
|
106 try { |
91 Method[] methods = getClass().getDeclaredMethods(); |
107 Method[] methods = getClass().getDeclaredMethods(); |
92 for (Method method : methods) { |
108 for (Method method : methods) { |
93 Optional<RequestMapping> mapping = Optional.ofNullable(method.getAnnotation(RequestMapping.class)); |
109 Optional<RequestMapping> mapping = Optional.ofNullable(method.getAnnotation(RequestMapping.class)); |
94 if (mapping.isPresent()) { |
110 if (mapping.isPresent()) { |
95 if (!Modifier.isPublic(method.getModifiers())) { |
111 if (!Modifier.isPublic(method.getModifiers())) { |
102 LOG.warn("{} is annotated with {} but is abstract", |
118 LOG.warn("{} is annotated with {} but is abstract", |
103 method.getName(), RequestMapping.class.getSimpleName() |
119 method.getName(), RequestMapping.class.getSimpleName() |
104 ); |
120 ); |
105 continue; |
121 continue; |
106 } |
122 } |
107 |
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 } |
|
129 |
108 Class<?>[] params = method.getParameterTypes(); |
130 Class<?>[] params = method.getParameterTypes(); |
109 if (params.length == 2 |
131 if (params.length == 2 |
110 && HttpServletRequest.class.isAssignableFrom(params[0]) |
132 && HttpServletRequest.class.isAssignableFrom(params[0]) |
111 && HttpServletResponse.class.isAssignableFrom(params[1])) { |
133 && HttpServletResponse.class.isAssignableFrom(params[1])) { |
112 |
134 |
113 if (mappings.computeIfAbsent(mapping.get().method(), k -> new HashMap<>()). |
135 if (mappings.computeIfAbsent(mapping.get().method(), k -> new HashMap<>()). |
114 putIfAbsent(mapping.get().requestPath(), |
136 putIfAbsent(mapping.get().requestPath(), |
115 (req, resp) -> invokeMapping(method, req, resp)) != null) { |
137 (req, resp) -> invokeMapping(method, req, resp)) != null) { |
116 LOG.warn("{} {} has multiple mappings", |
138 LOG.warn("{} {} has multiple mappings", |
117 mapping.get().method(), |
139 mapping.get().method(), |
118 mapping.get().requestPath() |
140 mapping.get().requestPath() |
119 ); |
141 ); |
120 } |
142 } |
121 |
143 |
122 LOG.info("{} {} maps to {}", |
144 LOG.info("{} {} maps to {}", |
123 mapping.get().method(), |
145 mapping.get().method(), |
124 mapping.get().requestPath(), |
146 mapping.get().requestPath(), |
125 method.getName() |
147 method.getName() |
126 ); |
148 ); |
127 } else { |
149 } else { |
128 LOG.warn("{} is annotated with {} but has the wrong signature - (HttpServletRequest,HttpServletResponse) required", |
150 LOG.warn("{} is annotated with {} but has the wrong parameters - (HttpServletRequest,HttpServletResponse) required", |
129 method.getName(), RequestMapping.class.getSimpleName() |
151 method.getName(), RequestMapping.class.getSimpleName() |
130 ); |
152 ); |
131 } |
153 } |
132 } |
154 } |
133 } |
155 } |
134 } |
156 } catch (SecurityException ex) { |
135 |
157 LOG.error("Scan for request mappings on declared methods failed.", ex); |
136 LOG.trace("{} initialized", getServletName()); |
158 } |
137 } |
159 } |
138 |
160 |
139 @Override |
161 @Override |
140 public void destroy() { |
162 public void destroy() { |
141 mappings.clear(); |
163 mappings.clear(); |
165 setGenericRequestAttributes(req); |
187 setGenericRequestAttributes(req); |
166 req.setAttribute(Constants.REQ_ATTR_MENU, getModuleManager().getMainMenu()); |
188 req.setAttribute(Constants.REQ_ATTR_MENU, getModuleManager().getMainMenu()); |
167 req.getRequestDispatcher(Functions.jspPath("full.jsp")).forward(req, resp); |
189 req.getRequestDispatcher(Functions.jspPath("full.jsp")).forward(req, resp); |
168 } |
190 } |
169 |
191 |
170 private Optional<BiConsumer<HttpServletRequest, HttpServletResponse>> findMapping(HttpMethod method, HttpServletRequest req) { |
192 private Optional<HandlerMethod> findMapping(HttpMethod method, HttpServletRequest req) { |
171 return Optional.ofNullable(mappings.get(method)).map( |
193 return Optional.ofNullable(mappings.get(method)).map( |
172 (rm) -> rm.get(Optional.ofNullable(req.getPathInfo()).orElse("")) |
194 (rm) -> rm.get(Optional.ofNullable(req.getPathInfo()).orElse("")) |
173 ); |
195 ); |
174 } |
196 } |
175 |
197 |
|
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 } |
|
212 |
|
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 } |
|
222 |
176 @Override |
223 @Override |
177 protected final void doGet(HttpServletRequest req, HttpServletResponse resp) |
224 protected final void doGet(HttpServletRequest req, HttpServletResponse resp) |
178 throws ServletException, IOException { |
225 throws ServletException, IOException { |
179 |
226 doProcess(HttpMethod.GET, req, resp); |
180 findMapping(HttpMethod.GET, req).ifPresent((consumer) -> consumer.accept(req, resp)); |
|
181 |
|
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 } |
227 } |
186 |
228 |
187 @Override |
229 @Override |
188 protected final void doPost(HttpServletRequest req, HttpServletResponse resp) |
230 protected final void doPost(HttpServletRequest req, HttpServletResponse resp) |
189 throws ServletException, IOException { |
231 throws ServletException, IOException { |
190 |
232 doProcess(HttpMethod.POST, req, resp); |
191 findMapping(HttpMethod.POST, req).ifPresent((consumer) -> consumer.accept(req, resp)); |
|
192 |
|
193 forwardToFullView(req, resp); |
|
194 } |
233 } |
195 } |
234 } |