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}