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