src/main/java/de/uapcore/lightpit/AbstractLightPITServlet.java

changeset 130
7ef369744fd1
parent 109
2e0669e814ff
child 131
67df332e3146
equal deleted inserted replaced
129:a09d5c59351a 130:7ef369744fd1
82 * the specification in the annotation must not start with a leading slash. 82 * the specification in the annotation must not start with a leading slash.
83 * <p> 83 * <p>
84 * The reason for this is the different handling of empty paths in 84 * The reason for this is the different handling of empty paths in
85 * {@link HttpServletRequest#getPathInfo()}. 85 * {@link HttpServletRequest#getPathInfo()}.
86 */ 86 */
87 private final Map<HttpMethod, Map<String, Method>> mappings = new HashMap<>(); 87 private final Map<HttpMethod, Map<PathPattern, Method>> mappings = new HashMap<>();
88 88
89 /** 89 /**
90 * Returns the name of the resource bundle associated with this servlet. 90 * Returns the name of the resource bundle associated with this servlet.
91 * 91 *
92 * @return the resource bundle base name 92 * @return the resource bundle base name
106 return new PGDataAccessObjects(connection); 106 return new PGDataAccessObjects(connection);
107 } 107 }
108 throw new AssertionError("Non-exhaustive if-else - this is a bug."); 108 throw new AssertionError("Non-exhaustive if-else - this is a bug.");
109 } 109 }
110 110
111 private ResponseType invokeMapping(Method method, HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws IOException { 111 private ResponseType invokeMapping(Map.Entry<PathPattern, Method> mapping, HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws IOException {
112 final var pathPattern = mapping.getKey();
113 final var method = mapping.getValue();
112 try { 114 try {
113 LOG.trace("invoke {}#{}", method.getDeclaringClass().getName(), method.getName()); 115 LOG.trace("invoke {}#{}", method.getDeclaringClass().getName(), method.getName());
114 final var paramTypes = method.getParameterTypes(); 116 final var paramTypes = method.getParameterTypes();
115 final var paramValues = new Object[paramTypes.length]; 117 final var paramValues = new Object[paramTypes.length];
116 for (int i = 0; i < paramTypes.length; i++) { 118 for (int i = 0; i < paramTypes.length; i++) {
119 } else if (paramTypes[i].isAssignableFrom(HttpServletResponse.class)) { 121 } else if (paramTypes[i].isAssignableFrom(HttpServletResponse.class)) {
120 paramValues[i] = resp; 122 paramValues[i] = resp;
121 } 123 }
122 if (paramTypes[i].isAssignableFrom(DataAccessObjects.class)) { 124 if (paramTypes[i].isAssignableFrom(DataAccessObjects.class)) {
123 paramValues[i] = dao; 125 paramValues[i] = dao;
126 }
127 if (paramTypes[i].isAssignableFrom(PathParameters.class)) {
128 paramValues[i] = pathPattern.obtainPathParameters(sanitizeRequestPath(req));
124 } 129 }
125 } 130 }
126 return (ResponseType) method.invoke(this, paramValues); 131 return (ResponseType) method.invoke(this, paramValues);
127 } catch (InvocationTargetException ex) { 132 } catch (InvocationTargetException ex) {
128 LOG.error("invocation of method {}::{} failed: {}", 133 LOG.error("invocation of method {}::{} failed: {}",
150 try { 155 try {
151 Method[] methods = getClass().getDeclaredMethods(); 156 Method[] methods = getClass().getDeclaredMethods();
152 for (Method method : methods) { 157 for (Method method : methods) {
153 Optional<RequestMapping> mapping = Optional.ofNullable(method.getAnnotation(RequestMapping.class)); 158 Optional<RequestMapping> mapping = Optional.ofNullable(method.getAnnotation(RequestMapping.class));
154 if (mapping.isPresent()) { 159 if (mapping.isPresent()) {
160 if (mapping.get().requestPath().isBlank()) {
161 LOG.warn("{} is annotated with {} but request path is empty",
162 method.getName(), RequestMapping.class.getSimpleName()
163 );
164 continue;
165 }
166
155 if (!Modifier.isPublic(method.getModifiers())) { 167 if (!Modifier.isPublic(method.getModifiers())) {
156 LOG.warn("{} is annotated with {} but is not public", 168 LOG.warn("{} is annotated with {} but is not public",
157 method.getName(), RequestMapping.class.getSimpleName() 169 method.getName(), RequestMapping.class.getSimpleName()
158 ); 170 );
159 continue; 171 continue;
173 185
174 boolean paramsInjectible = true; 186 boolean paramsInjectible = true;
175 for (var param : method.getParameterTypes()) { 187 for (var param : method.getParameterTypes()) {
176 paramsInjectible &= HttpServletRequest.class.isAssignableFrom(param) 188 paramsInjectible &= HttpServletRequest.class.isAssignableFrom(param)
177 || HttpServletResponse.class.isAssignableFrom(param) 189 || HttpServletResponse.class.isAssignableFrom(param)
190 || PathParameters.class.isAssignableFrom(param)
178 || DataAccessObjects.class.isAssignableFrom(param); 191 || DataAccessObjects.class.isAssignableFrom(param);
179 } 192 }
180 if (paramsInjectible) { 193 if (paramsInjectible) {
181 String requestPath = "/" + mapping.get().requestPath(); 194 try {
182 195 PathPattern pathPattern = new PathPattern(mapping.get().requestPath());
183 if (mappings 196
184 .computeIfAbsent(mapping.get().method(), k -> new HashMap<>()) 197 if (mappings
185 .putIfAbsent(requestPath, method) != null) { 198 .computeIfAbsent(mapping.get().method(), k -> new HashMap<>())
186 LOG.warn("{} {} has multiple mappings", 199 .putIfAbsent(pathPattern, method) != null) {
200 LOG.warn("{} {} has multiple mappings",
201 mapping.get().method(),
202 mapping.get().requestPath()
203 );
204 }
205
206 LOG.debug("{} {} maps to {}::{}",
187 mapping.get().method(), 207 mapping.get().method(),
188 mapping.get().requestPath() 208 mapping.get().requestPath(),
209 getClass().getSimpleName(),
210 method.getName()
211 );
212 } catch (IllegalArgumentException ex) {
213 LOG.warn("Request mapping for {} failed: path pattern '{}' is syntactically invalid",
214 method.getName(), mapping.get().requestPath()
189 ); 215 );
190 } 216 }
191
192 LOG.debug("{} {} maps to {}::{}",
193 mapping.get().method(),
194 requestPath,
195 getClass().getSimpleName(),
196 method.getName()
197 );
198 } else { 217 } else {
199 LOG.warn("{} is annotated with {} but has the wrong parameters - only HttpServletRequest. HttpServletResponse, and DataAccessObjects are allowed", 218 LOG.warn("{} is annotated with {} but has the wrong parameters - only HttpServletRequest, HttpServletResponse, PathParameters, and DataAccessObjects are allowed",
200 method.getName(), RequestMapping.class.getSimpleName() 219 method.getName(), RequestMapping.class.getSimpleName()
201 ); 220 );
202 } 221 }
203 } 222 }
204 } 223 }
371 390
372 private String sanitizeRequestPath(HttpServletRequest req) { 391 private String sanitizeRequestPath(HttpServletRequest req) {
373 return Optional.ofNullable(req.getPathInfo()).orElse("/"); 392 return Optional.ofNullable(req.getPathInfo()).orElse("/");
374 } 393 }
375 394
376 private Optional<Method> findMapping(HttpMethod method, HttpServletRequest req) { 395 private Optional<Map.Entry<PathPattern, Method>> findMapping(HttpMethod method, HttpServletRequest req) {
377 return Optional.ofNullable(mappings.get(method)).map(rm -> rm.get(sanitizeRequestPath(req))); 396 return Optional.ofNullable(mappings.get(method)).flatMap(rm ->
397 rm.entrySet().stream().filter(
398 kv -> kv.getKey().matches(sanitizeRequestPath(req))
399 ).findAny()
400 );
378 } 401 }
379 402
380 private void forwardAsSpecified(ResponseType type, HttpServletRequest req, HttpServletResponse resp) 403 private void forwardAsSpecified(ResponseType type, HttpServletRequest req, HttpServletResponse resp)
381 throws ServletException, IOException { 404 throws ServletException, IOException {
382 switch (type) { 405 switch (type) {

mercurial