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, 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 @SuppressWarnings("PMD.UnnecessaryBoxing") 165 public static void add(ComponentType component, String method, 166 String pattern, int priority) { 167 add(component, method, pattern, Integer.valueOf(priority)); 168 } 169 170 @SuppressWarnings("PMD.AvoidBranchingStatementAsLastInLoop") 171 private static void add(ComponentType component, String method, 172 String pattern, Integer priority) { 173 try { 174 for (Method m : component.getClass().getMethods()) { 175 if (!m.getName().equals(method)) { 176 continue; 177 } 178 for (Annotation annotation : m.getDeclaredAnnotations()) { 179 Class<?> annoType = annotation.annotationType(); 180 HandlerDefinition hda 181 = annoType.getAnnotation(HandlerDefinition.class); 182 if (hda == null 183 || !RequestHandler.class.isAssignableFrom(annoType) 184 || !((RequestHandler) annotation).dynamic()) { 185 continue; 186 } 187 @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") 188 Scope scope = new Scope(component, m, 189 (RequestHandler) annotation, 190 Collections.emptyMap(), pattern); 191 Components.manager(component) 192 .addHandler(m, scope, priority == null 193 ? ((RequestHandler) annotation).priority() 194 : priority); 195 return; 196 } 197 } 198 throw new IllegalArgumentException( 199 "No method named \"" + method + "\" with DynamicHandler" 200 + " annotation and correct parameter list."); 201 } catch (SecurityException e) { 202 throw (RuntimeException) new IllegalArgumentException() 203 .initCause(e); 204 } 205 } 206 207 /** 208 * The scope implementation. 209 */ 210 public static class Scope implements HandlerScope, InvocationFilter { 211 212 private final Set<Object> handledEventTypes = new HashSet<>(); 213 private final Set<Object> handledChannels = new HashSet<>(); 214 private final Set<ResourcePattern> handledPatterns 215 = new HashSet<>(); 216 217 /** 218 * Instantiates a new scope. 219 * 220 * @param component the component 221 * @param method the method 222 * @param annotation the annotation 223 * @param channelReplacements the channel replacements 224 * @param pattern the pattern 225 */ 226 @SuppressWarnings({ "PMD.CyclomaticComplexity", 227 "PMD.NPathComplexity", "PMD.AvoidDeeplyNestedIfStmts", 228 "PMD.CollapsibleIfStatements", "PMD.CognitiveComplexity", 229 "PMD.AvoidInstantiatingObjectsInLoops", "PMD.NcssCount" }) 230 public Scope(ComponentType component, 231 Method method, RequestHandler annotation, 232 Map<Class<? extends Channel>, Object[]> channelReplacements, 233 String pattern) { 234 if (!HandlerDefinition.Evaluator.checkMethodSignature(method)) { 235 throw new IllegalArgumentException("Method " 236 + method.toString() + " cannot be used as" 237 + " handler (wrong signature)."); 238 } 239 // Get all event keys from the handler annotation. 240 if (annotation.events()[0] != NoEvent.class) { 241 handledEventTypes 242 .addAll(Arrays.asList(annotation.events())); 243 } 244 // If no event types are given, try first parameter. 245 if (handledEventTypes.isEmpty()) { 246 Class<?>[] paramTypes = method.getParameterTypes(); 247 if (paramTypes.length > 0) { 248 if (Event.class.isAssignableFrom(paramTypes[0])) { 249 handledEventTypes.add(paramTypes[0]); 250 } 251 } 252 } 253 254 // Get channel keys from the annotation. 255 boolean addDefaultChannel = false; 256 if (annotation.channels()[0] != NoChannel.class) { 257 for (Class<?> c : annotation.channels()) { 258 if (c == Self.class) { 259 if (!(component instanceof Channel)) { 260 throw new IllegalArgumentException( 261 "Canot use channel This.class in " 262 + "annotation of " + method 263 + " because " + getClass().getName() 264 + " does not implement Channel."); 265 } 266 // Will be added anyway, see below 267 } else if (c == Channel.Default.class) { 268 addDefaultChannel = true; 269 } else { 270 if (channelReplacements != null 271 && channelReplacements.containsKey(c)) { 272 handledChannels.addAll( 273 Arrays.asList(channelReplacements.get(c))); 274 } else { 275 handledChannels.add(c); 276 } 277 } 278 } 279 } 280 if (handledChannels.isEmpty() || addDefaultChannel) { 281 handledChannels.add(Components.manager(component) 282 .channel().defaultCriterion()); 283 } 284 // Finally, a component always handles events 285 // directed at it directly. 286 if (component instanceof Channel) { 287 handledChannels.add( 288 ((Channel) component).defaultCriterion()); 289 } 290 291 try { 292 // Get all paths from the annotation. 293 if (!annotation.patterns()[0].isEmpty()) { 294 for (String p : annotation.patterns()) { 295 handledPatterns.add(new ResourcePattern(p)); 296 } 297 } 298 299 // Add additionally provided path 300 if (pattern != null) { 301 handledPatterns.add(new ResourcePattern(pattern)); 302 } 303 } catch (ParseException e) { 304 throw new IllegalArgumentException(e.getMessage(), e); 305 } 306 } 307 308 @Override 309 @SuppressWarnings({ "PMD.DataflowAnomalyAnalysis", 310 "PMD.NPathComplexity", "PMD.CognitiveComplexity" }) 311 public boolean includes(Eligible event, Eligible[] channels) { 312 boolean match = false; 313 for (Object eventType : handledEventTypes) { 314 if (Class.class.isInstance(eventType) 315 && ((Class<?>) eventType) 316 .isAssignableFrom(event.getClass())) { 317 match = true; 318 break; 319 } 320 321 } 322 if (!match) { 323 return false; 324 } 325 match = false; 326 // Try channels 327 for (Eligible channel : channels) { 328 for (Object channelValue : handledChannels) { 329 if (channel.isEligibleFor(channelValue)) { 330 match = true; 331 break; 332 } 333 } 334 } 335 if (!match) { 336 return false; 337 } 338 if (!(event instanceof Request.In)) { 339 return false; 340 } 341 for (ResourcePattern rp : handledPatterns) { 342 if (((Request.In) event).isEligibleFor( 343 Request.In.createMatchValue(Request.In.class, rp))) { 344 return true; 345 } 346 } 347 return false; 348 } 349 350 @Override 351 public boolean includes(EventBase<?> event) { 352 for (ResourcePattern rp : handledPatterns) { 353 if (rp.matches(((Request.In) event).requestUri()) >= 0) { 354 return true; 355 } 356 } 357 return false; 358 } 359 360 /* 361 * (non-Javadoc) 362 * 363 * @see java.lang.Object#toString() 364 */ 365 @Override 366 public String toString() { 367 StringBuilder builder = new StringBuilder(100); 368 builder.append("Scope ["); 369 if (handledEventTypes != null) { 370 builder.append("handledEventTypes=") 371 .append(handledEventTypes.stream().map(value -> { 372 if (value instanceof Class) { 373 return Components.className((Class<?>) value); 374 } 375 return value.toString(); 376 }).collect(Collectors.toSet())).append(", "); 377 } 378 if (handledChannels != null) { 379 builder.append("handledChannels=").append(handledChannels) 380 .append(", "); 381 } 382 if (handledPatterns != null) { 383 builder.append("handledPatterns=").append(handledPatterns); 384 } 385 builder.append(']'); 386 return builder.toString(); 387 } 388 389 } 390 } 391}