001/* 002 * JGrapes Event Driven Framework 003 * Copyright (C) 2016-2018 Michael N. Lipp 004 * 005 * This program is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU Affero General Public License as published by 007 * the Free Software Foundation; either version 3 of the License, or 008 * (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, but 011 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 012 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License 013 * for more details. 014 * 015 * You should have received a copy of the GNU Affero General Public License along 016 * with this program; if not, see <http://www.gnu.org/licenses/>. 017 */ 018 019package org.jgrapes.http.annotation; 020 021import java.lang.annotation.Annotation; 022import java.lang.annotation.Documented; 023import java.lang.annotation.ElementType; 024import java.lang.annotation.Retention; 025import java.lang.annotation.RetentionPolicy; 026import java.lang.annotation.Target; 027import java.lang.reflect.Method; 028import java.text.ParseException; 029import java.util.Arrays; 030import java.util.Collections; 031import java.util.HashSet; 032import java.util.Map; 033import java.util.Set; 034import java.util.stream.Collectors; 035import org.jgrapes.core.Channel; 036import org.jgrapes.core.ComponentType; 037import org.jgrapes.core.Components; 038import org.jgrapes.core.Eligible; 039import org.jgrapes.core.Event; 040import org.jgrapes.core.HandlerScope; 041import org.jgrapes.core.InvocationFilter; 042import org.jgrapes.core.Self; 043import org.jgrapes.core.annotation.Handler.NoChannel; 044import org.jgrapes.core.annotation.Handler.NoEvent; 045import org.jgrapes.core.annotation.HandlerDefinition; 046import org.jgrapes.core.annotation.HandlerDefinition.ChannelReplacements; 047import org.jgrapes.core.internal.EventBase; 048import org.jgrapes.http.ResourcePattern; 049import org.jgrapes.http.events.Request; 050import org.jgrapes.http.events.Request.In; 051 052/** 053 * This annotation marks a method as handler for events. The method is 054 * invoked for events that have a type derived from {@link Request} and 055 * are matched by one of the specified {@link ResourcePattern}s. 056 * 057 * Note that matching uses a shortened request URI for reasons outlined 058 * in {@link In#defaultCriterion()}. Specifying patterns with 059 * more path components than are used by the event's default criterion 060 * may therefore result in unexpected events. If the default criterion 061 * of an event shortens a requested URI "/foo/bar" to "/foo/**" and 062 * the {@link RequestHandler} annotation specifies "/foo/baz" as pattern, 063 * the handler will be invoked. 064 */ 065@Documented 066@Retention(RetentionPolicy.RUNTIME) 067@Target(ElementType.METHOD) 068@HandlerDefinition(evaluator = RequestHandler.Evaluator.class) 069public @interface RequestHandler { 070 071 /** 072 * Specifies classes of events that the handler is to receive. 073 * 074 * @return the event classes 075 */ 076 @SuppressWarnings("rawtypes") 077 Class<? extends Event>[] events() default NoEvent.class; 078 079 /** 080 * Specifies classes of channels that the handler listens on. 081 * 082 * @return the channel classes 083 */ 084 Class<? extends Channel>[] channels() default NoChannel.class; 085 086 /** 087 * Specifies the patterns that the handler is supposed to handle 088 * (see {@link ResourcePattern}). 089 * 090 * @return the patterns 091 */ 092 String[] patterns() default ""; 093 094 /** 095 * Specifies a priority. The value is used to sort handlers. 096 * Handlers with higher priority are invoked first. 097 * 098 * @return the priority 099 */ 100 int priority() default 0; 101 102 /** 103 * Returns {@code true} if the annotated annotation defines a 104 * dynamic handler. A dynamic handler must be added to the set of 105 * handlers of a component explicitly. 106 * 107 * @return the result 108 */ 109 boolean dynamic() default false; 110 111 /** 112 * This class provides the {@link Evaluator} for the {@link RequestHandler} 113 * annotation. It implements the behavior as described for the annotation. 114 */ 115 class Evaluator implements HandlerDefinition.Evaluator { 116 117 /* 118 * (non-Javadoc) 119 * 120 * @see 121 * org.jgrapes.core.annotation.HandlerDefinition.Evaluator#getPriority() 122 */ 123 @Override 124 public int priority(Annotation annotation) { 125 return ((RequestHandler) annotation).priority(); 126 } 127 128 @Override 129 public HandlerScope scope(ComponentType component, Method method, 130 ChannelReplacements channelReplacements) { 131 RequestHandler annotation 132 = method.getAnnotation(RequestHandler.class); 133 if (annotation.dynamic()) { 134 return null; 135 } 136 return new Scope(component, method, (RequestHandler) annotation, 137 channelReplacements, null); 138 } 139 140 /** 141 * Adds the given method of the given component as a 142 * dynamic handler for a specific pattern. Other informations 143 * are taken from the annotation. 144 * 145 * @param component the component 146 * @param method the name of the method that implements the handler 147 * @param pattern the pattern 148 */ 149 public static void add(ComponentType component, String method, 150 String pattern) { 151 add(component, method, pattern, null); 152 } 153 154 /** 155 * Adds the given method of the given component as a 156 * dynamic handler for a specific pattern with the specified 157 * priority. Other informations are taken from the annotation. 158 * 159 * @param component the component 160 * @param method the name of the method that implements the handler 161 * @param pattern the pattern 162 * @param priority the priority 163 */ 164 public static void add(ComponentType component, String method, 165 String pattern, int priority) { 166 add(component, method, pattern, Integer.valueOf(priority)); 167 } 168 169 @SuppressWarnings("PMD.AvoidBranchingStatementAsLastInLoop") 170 private static void add(ComponentType component, String method, 171 String pattern, Integer priority) { 172 try { 173 for (Method m : component.getClass().getMethods()) { 174 if (!m.getName().equals(method)) { 175 continue; 176 } 177 for (Annotation annotation : m.getDeclaredAnnotations()) { 178 Class<?> annoType = annotation.annotationType(); 179 HandlerDefinition hda 180 = annoType.getAnnotation(HandlerDefinition.class); 181 if (hda == null 182 || !RequestHandler.class.isAssignableFrom(annoType) 183 || !((RequestHandler) annotation).dynamic()) { 184 continue; 185 } 186 @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") 187 Scope scope = new Scope(component, m, 188 (RequestHandler) annotation, 189 Collections.emptyMap(), pattern); 190 Components.manager(component) 191 .addHandler(m, scope, priority == null 192 ? ((RequestHandler) annotation).priority() 193 : priority); 194 return; 195 } 196 } 197 throw new IllegalArgumentException( 198 "No method named \"" + method + "\" with DynamicHandler" 199 + " annotation and correct parameter list."); 200 } catch (SecurityException e) { 201 throw (RuntimeException) (new IllegalArgumentException() 202 .initCause(e)); 203 } 204 } 205 206 /** 207 * The scope implementation. 208 */ 209 public static class Scope implements HandlerScope, InvocationFilter { 210 211 private final Set<Object> handledEventTypes = new HashSet<>(); 212 private final Set<Object> handledChannels = new HashSet<>(); 213 private final Set<ResourcePattern> handledPatterns 214 = new HashSet<>(); 215 216 /** 217 * Instantiates a new scope. 218 * 219 * @param component the component 220 * @param method the method 221 * @param annotation the annotation 222 * @param channelReplacements the channel replacements 223 * @param pattern the pattern 224 */ 225 @SuppressWarnings({ "PMD.CyclomaticComplexity", 226 "PMD.NPathComplexity", "PMD.AvoidDeeplyNestedIfStmts", 227 "PMD.CollapsibleIfStatements", "PMD.CognitiveComplexity", 228 "PMD.AvoidInstantiatingObjectsInLoops" }) 229 public Scope(ComponentType component, 230 Method method, RequestHandler annotation, 231 Map<Class<? extends Channel>, Object[]> channelReplacements, 232 String pattern) { 233 if (!HandlerDefinition.Evaluator.checkMethodSignature(method)) { 234 throw new IllegalArgumentException("Method " 235 + method.toString() + " cannot be used as" 236 + " handler (wrong signature)."); 237 } 238 // Get all event keys from the handler annotation. 239 if (annotation.events()[0] != NoEvent.class) { 240 handledEventTypes 241 .addAll(Arrays.asList(annotation.events())); 242 } 243 // If no event types are given, try first parameter. 244 if (handledEventTypes.isEmpty()) { 245 Class<?>[] paramTypes = method.getParameterTypes(); 246 if (paramTypes.length > 0) { 247 if (Event.class.isAssignableFrom(paramTypes[0])) { 248 handledEventTypes.add(paramTypes[0]); 249 } 250 } 251 } 252 253 // Get channel keys from the annotation. 254 boolean addDefaultChannel = false; 255 if (annotation.channels()[0] != NoChannel.class) { 256 for (Class<?> c : annotation.channels()) { 257 if (c == Self.class) { 258 if (!(component instanceof Channel)) { 259 throw new IllegalArgumentException( 260 "Canot use channel This.class in " 261 + "annotation of " + method 262 + " because " + getClass().getName() 263 + " does not implement Channel."); 264 } 265 // Will be added anyway, see below 266 } else if (c == Channel.Default.class) { 267 addDefaultChannel = true; 268 } else { 269 if (channelReplacements != null 270 && channelReplacements.containsKey(c)) { 271 handledChannels.addAll( 272 Arrays.asList(channelReplacements.get(c))); 273 } else { 274 handledChannels.add(c); 275 } 276 } 277 } 278 } 279 if (handledChannels.isEmpty() || addDefaultChannel) { 280 handledChannels.add(Components.manager(component) 281 .channel().defaultCriterion()); 282 } 283 // Finally, a component always handles events 284 // directed at it directly. 285 if (component instanceof Channel) { 286 handledChannels.add( 287 ((Channel) component).defaultCriterion()); 288 } 289 290 try { 291 // Get all paths from the annotation. 292 if (!annotation.patterns()[0].isEmpty()) { 293 for (String p : annotation.patterns()) { 294 handledPatterns.add(new ResourcePattern(p)); 295 } 296 } 297 298 // Add additionally provided path 299 if (pattern != null) { 300 handledPatterns.add(new ResourcePattern(pattern)); 301 } 302 } catch (ParseException e) { 303 throw new IllegalArgumentException(e.getMessage(), e); 304 } 305 } 306 307 @Override 308 @SuppressWarnings({ "PMD.DataflowAnomalyAnalysis", 309 "PMD.NPathComplexity", "PMD.CognitiveComplexity" }) 310 public boolean includes(Eligible event, Eligible[] channels) { 311 boolean match = false; 312 for (Object eventType : handledEventTypes) { 313 if (Class.class.isInstance(eventType) 314 && ((Class<?>) eventType) 315 .isAssignableFrom(event.getClass())) { 316 match = true; 317 break; 318 } 319 320 } 321 if (!match) { 322 return false; 323 } 324 match = false; 325 // Try channels 326 for (Eligible channel : channels) { 327 for (Object channelValue : handledChannels) { 328 if (channel.isEligibleFor(channelValue)) { 329 match = true; 330 break; 331 } 332 } 333 } 334 if (!match) { 335 return false; 336 } 337 if (!(event instanceof Request.In)) { 338 return false; 339 } 340 for (ResourcePattern rp : handledPatterns) { 341 if (((Request.In) event).isEligibleFor( 342 Request.In.createMatchValue(Request.In.class, rp))) { 343 return true; 344 } 345 } 346 return false; 347 } 348 349 @Override 350 public boolean includes(EventBase<?> event) { 351 for (ResourcePattern rp : handledPatterns) { 352 if (rp.matches(((Request.In) event).requestUri()) >= 0) { 353 return true; 354 } 355 } 356 return false; 357 } 358 359 /* 360 * (non-Javadoc) 361 * 362 * @see java.lang.Object#toString() 363 */ 364 @Override 365 public String toString() { 366 StringBuilder builder = new StringBuilder(); 367 builder.append("Scope ["); 368 if (handledEventTypes != null) { 369 builder.append("handledEventTypes=") 370 .append(handledEventTypes.stream().map(value -> { 371 if (value instanceof Class) { 372 return Components.className((Class<?>) value); 373 } 374 return value.toString(); 375 }).collect(Collectors.toSet())); 376 builder.append(", "); 377 } 378 if (handledChannels != null) { 379 builder.append("handledChannels="); 380 builder.append(handledChannels); 381 builder.append(", "); 382 } 383 if (handledPatterns != null) { 384 builder.append("handledPatterns="); 385 builder.append(handledPatterns); 386 } 387 builder.append(']'); 388 return builder.toString(); 389 } 390 391 } 392 } 393}