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.events;
020
021import java.net.URI;
022import java.net.URISyntaxException;
023import java.net.URL;
024import java.util.Iterator;
025import java.util.Optional;
026import java.util.function.BiConsumer;
027
028import org.jdrupes.httpcodec.protocols.http.HttpConstants.HttpProtocol;
029import org.jdrupes.httpcodec.protocols.http.HttpRequest;
030import org.jgrapes.core.Channel;
031import org.jgrapes.core.CompletionEvent;
032import org.jgrapes.core.Components;
033import org.jgrapes.http.ResourcePattern;
034import org.jgrapes.http.ResourcePattern.PathSpliterator;
035import org.jgrapes.net.TcpChannel;
036
037/**
038 * The base class for all HTTP requests such as {@link Request.In.Get},
039 * {@link Request.In.Post} etc.
040 *
041 * @param <R> the generic type
042 */
043public class Request<R> extends MessageReceived<R> {
044
045    /**
046     * @param channels
047     */
048    protected Request(Channel... channels) {
049        super(channels);
050    }
051
052    /**
053     * The base class for all incoming HTTP requests. Incoming
054     * request flow downstream and are served by the framework. 
055     * 
056     * A result of `true` indicates that the request has been processed, 
057     * i.e. a response has been sent or will sent.
058     */
059    @SuppressWarnings("PMD.ShortClassName")
060    public static class In extends Request<Boolean> {
061
062        @SuppressWarnings("PMD.AvoidFieldNameMatchingTypeName")
063        private final HttpRequest request;
064        private MatchValue matchValue;
065        private URI matchUri;
066        private URI uri;
067
068        /**
069         * Creates a new request event with the associated {@link Completed}
070         * event.
071         * 
072         * @param protocol the protocol as reported by {@link #requestUri()}
073         * @param request the request data
074         * @param matchLevels the number of elements from the request path
075         * to use in the match value (see {@link #matchValue})
076         * @param channels the channels associated with this event
077         */
078        @SuppressWarnings("PMD.UselessParentheses")
079        public In(String protocol, HttpRequest request,
080                int matchLevels, Channel... channels) {
081            super(channels);
082            new Completed(this);
083            this.request = request;
084            try {
085                URI headerInfo = new URI(protocol, null,
086                    request.host(), request.port(), null, null, null);
087                setRequestUri(headerInfo.resolve(request.requestUri()));
088                Iterator<String> segs = PathSpliterator.stream(
089                    requestUri().getPath()).skip(1).iterator();
090                StringBuilder pattern = new StringBuilder(20);
091                for (int i = 0; i < matchLevels && segs.hasNext(); i++) {
092                    pattern.append('/')
093                        .append(segs.next());
094                }
095                if (segs.hasNext()) {
096                    pattern.append("/**");
097                }
098                String matchPath = pattern.toString();
099                URI uri = requestUri();
100                matchUri = new URI(uri.getScheme(), null, uri.getHost(),
101                    uri.getPort(), uri.getPath(), null, null);
102                matchValue = new MatchValue(getClass(),
103                    (uri.getScheme() == null ? ""
104                        : (uri.getScheme() + "://"))
105                        + (uri.getHost() == null ? ""
106                            : (uri.getHost() + (uri.getPort() == -1 ? ""
107                                : (":" + uri.getPort()))))
108                        + matchPath);
109            } catch (URISyntaxException e) {
110                throw new IllegalArgumentException(e);
111            }
112        }
113
114        /**
115         * Creates the appropriate derived request event type from
116         * a given {@link HttpRequest}.
117         * 
118         * @param request the HTTP request
119         * @param secure whether the request was received over a secure channel
120         * @param matchLevels the match levels
121         * @return the request event
122         */
123        @SuppressWarnings("PMD.AvoidDuplicateLiterals")
124        public static In fromHttpRequest(
125                HttpRequest request, boolean secure, int matchLevels) {
126            switch (request.method()) {
127            case "OPTIONS":
128                return new Options(request, secure, matchLevels);
129            case "GET":
130                return new Get(request, secure, matchLevels);
131            case "HEAD":
132                return new Head(request, secure, matchLevels);
133            case "POST":
134                return new Post(request, secure, matchLevels);
135            case "PUT":
136                return new Put(request, secure, matchLevels);
137            case "DELETE":
138                return new Delete(request, secure, matchLevels);
139            case "TRACE":
140                return new Trace(request, secure, matchLevels);
141            case "CONNECT":
142                return new Connect(request, secure, matchLevels);
143            default:
144                return new In(secure ? "https" : "http", request, matchLevels);
145            }
146        }
147
148        /**
149         * Sets the request URI.
150         *
151         * @param uri the new request URI
152         */
153        protected final void setRequestUri(URI uri) {
154            this.uri = uri;
155        }
156
157        /**
158         * Returns an absolute URI of the request. For incoming requests, the 
159         * URI is built from the information provided by the decoder.
160         * 
161         * @return the URI
162         */
163        public final URI requestUri() {
164            return uri;
165        }
166
167        /**
168         * Returns the "raw" request as provided by the HTTP decoder.
169         * 
170         * @return the request
171         */
172        public HttpRequest httpRequest() {
173            return request;
174        }
175
176        /**
177         * The match value consists of the event class and a URI.
178         * The URI is similar to the request URI but its path elements
179         * are shortened as specified in the constructor.
180         * 
181         * As the match value is used as key in a map that speeds up
182         * the lookup of handlers, having the complete URI in the match
183         * value would inflate this map.
184         *
185         * @return the object
186         * @see org.jgrapes.core.Event#defaultCriterion()
187         */
188        @Override
189        public Object defaultCriterion() {
190            return matchValue;
191        }
192
193        /*
194         * (non-Javadoc)
195         * 
196         * @see org.jgrapes.core.Event#isMatchedBy(java.lang.Object)
197         */
198        @Override
199        public boolean isEligibleFor(Object value) {
200            if (!(value instanceof MatchValue)) {
201                return super.isEligibleFor(value);
202            }
203            MatchValue mval = (MatchValue) value;
204            if (!mval.type.isAssignableFrom(matchValue.type)) {
205                return false;
206            }
207            if (mval.resource instanceof ResourcePattern) {
208                return ((ResourcePattern) mval.resource).matches(matchUri) >= 0;
209            }
210            return mval.resource.equals(matchValue.resource);
211        }
212
213        /*
214         * (non-Javadoc)
215         * 
216         * @see java.lang.Object#toString()
217         */
218        @Override
219        @SuppressWarnings("PMD.AvoidLiteralsInIfCondition")
220        public String toString() {
221            StringBuilder builder = new StringBuilder();
222            builder.append(Components.objectName(this))
223                .append(" [\"");
224            String path = request.requestUri().getPath();
225            if (path.length() > 15) {
226                builder.append("...")
227                    .append(path.substring(path.length() - 12));
228            } else {
229                builder.append(path);
230            }
231            builder.append('\"');
232            if (channels().length > 0) {
233                builder.append(", channels=");
234                builder.append(Channel.toString(channels()));
235            }
236            builder.append(']');
237            return builder.toString();
238        }
239
240        /**
241         * Creates the match value.
242         *
243         * @param type the type
244         * @param resource the resource
245         * @return the object
246         */
247        public static Object createMatchValue(
248                Class<?> type, ResourcePattern resource) {
249            return new MatchValue(type, resource);
250        }
251
252        /**
253         * Represents a match value.
254         */
255        private static class MatchValue {
256            private final Class<?> type;
257            private final Object resource;
258
259            /**
260             * @param type
261             * @param resource
262             */
263            public MatchValue(Class<?> type, Object resource) {
264                super();
265                this.type = type;
266                this.resource = resource;
267            }
268
269            /*
270             * (non-Javadoc)
271             * 
272             * @see java.lang.Object#hashCode()
273             */
274            @Override
275            @SuppressWarnings("PMD.DataflowAnomalyAnalysis")
276            public int hashCode() {
277                @SuppressWarnings("PMD.AvoidFinalLocalVariable")
278                final int prime = 31;
279                int result = 1;
280                result = prime * result
281                    + ((resource == null) ? 0 : resource.hashCode());
282                result
283                    = prime * result + ((type == null) ? 0 : type.hashCode());
284                return result;
285            }
286
287            /*
288             * (non-Javadoc)
289             * 
290             * @see java.lang.Object#equals(java.lang.Object)
291             */
292            @Override
293            public boolean equals(Object obj) {
294                if (this == obj) {
295                    return true;
296                }
297                if (obj == null) {
298                    return false;
299                }
300                if (getClass() != obj.getClass()) {
301                    return false;
302                }
303                MatchValue other = (MatchValue) obj;
304                if (resource == null) {
305                    if (other.resource != null) {
306                        return false;
307                    }
308                } else if (!resource.equals(other.resource)) {
309                    return false;
310                }
311                if (type == null) {
312                    if (other.type != null) {
313                        return false;
314                    }
315                } else if (!type.equals(other.type)) {
316                    return false;
317                }
318                return true;
319            }
320
321        }
322
323        /**
324         * The associated completion event.
325         */
326        public static class Completed extends CompletionEvent<In> {
327
328            /**
329             * Instantiates a new event.
330             *
331             * @param monitoredEvent the monitored event
332             * @param channels the channels
333             */
334            public Completed(In monitoredEvent, Channel... channels) {
335                super(monitoredEvent, channels);
336            }
337        }
338
339        /**
340         * Represents a HTTP CONNECT request.
341         */
342        public static class Connect extends In {
343
344            /**
345             * Create a new event.
346             * 
347             * @param request the request data
348             * @param secure indicates whether the request was received on a
349             * secure channel
350             * @param matchLevels the number of elements from the request path
351             * to use in the match value
352             * @param channels the channels on which the event is to be 
353             * fired (optional)
354             */
355            public Connect(HttpRequest request, boolean secure,
356                    int matchLevels, Channel... channels) {
357                super(secure ? "https" : "http", request, matchLevels,
358                    channels);
359            }
360
361        }
362
363        /**
364         * The Class Delete.
365         */
366        public static class Delete extends In {
367
368            /**
369            * Create a new event.
370            * 
371            * @param request the request data
372            * @param secure indicates whether the request was received on a
373            * secure channel
374            * @param matchLevels the number of elements from the request path
375            * to use in the match value
376            * @param channels the channels on which the event is to be 
377            * fired (optional)
378            */
379            public Delete(HttpRequest request, boolean secure,
380                    int matchLevels, Channel... channels) {
381                super(secure ? "https" : "http", request, matchLevels,
382                    channels);
383            }
384
385        }
386
387        /**
388         * Represents a HTTP GET request.
389         */
390        public static class Get extends In {
391
392            /**
393             * Create a new event.
394             * 
395             * @param request the request data
396             * @param secure indicates whether the request was received on a
397             * secure channel
398             * @param matchLevels the number of elements from the request path
399             * to use in the match value
400             * @param channels the channels on which the event is to be 
401             * fired (optional)
402             */
403            public Get(HttpRequest request, boolean secure,
404                    int matchLevels, Channel... channels) {
405                super(secure ? "https" : "http", request, matchLevels,
406                    channels);
407            }
408        }
409
410        /**
411         * Represents a HTTP HEAD request.
412         */
413        public static class Head extends In {
414
415            /**
416             * Create a new event.
417             * 
418             * @param request the request data
419             * @param secure indicates whether the request was received on a
420             * secure channel
421             * @param matchLevels the number of elements from the request path
422             * to use in the match value
423             * @param channels the channels on which the event is to be 
424             * fired (optional)
425             */
426            public Head(HttpRequest request, boolean secure,
427                    int matchLevels, Channel... channels) {
428                super(secure ? "https" : "http", request, matchLevels,
429                    channels);
430            }
431        }
432
433        /**
434         * Represents a HTTP OPTIONS request.
435         */
436        public static class Options extends In {
437
438            /**
439             * Create a new event.
440             * 
441             * @param request the request data
442             * @param secure indicates whether the request was received on a
443             * secure channel
444             * @param matchLevels the number of elements from the request path
445             * to use in the match value
446             * @param channels the channels on which the event is to be 
447             * fired (optional)
448             */
449            public Options(HttpRequest request, boolean secure,
450                    int matchLevels, Channel... channels) {
451                super(secure ? "https" : "http", request, matchLevels,
452                    channels);
453            }
454        }
455
456        /**
457         * Represents a HTTP POST request.
458         */
459        public static class Post extends In {
460
461            /**
462             * Create a new event.
463             * 
464             * @param request the request data
465             * @param secure indicates whether the request was received on a
466             * secure channel
467             * @param matchLevels the number of elements from the request path
468             * to use in the match value
469             * @param channels the channels on which the event is to be 
470             * fired (optional)
471             */
472            public Post(HttpRequest request, boolean secure,
473                    int matchLevels, Channel... channels) {
474                super(secure ? "https" : "http", request, matchLevels,
475                    channels);
476            }
477
478        }
479
480        /**
481         * Represents a HTTP PUT request.
482         */
483        public static class Put extends In {
484
485            /**
486             * Create a new event.
487             * 
488             * @param request the request data
489             * @param secure indicates whether the request was received on a
490             * secure channel
491             * @param matchLevels the number of elements from the request path
492             * to use in the match value
493             * @param channels the channels on which the event is to be 
494             * fired (optional)
495             */
496            public Put(HttpRequest request, boolean secure,
497                    int matchLevels, Channel... channels) {
498                super(secure ? "https" : "http", request, matchLevels,
499                    channels);
500            }
501        }
502
503        /**
504         * Represents a HTTP TRACE request.
505         */
506        public static class Trace extends In {
507
508            /**
509             * Create a new event.
510             * 
511             * @param request the request data
512             * @param secure indicates whether the request was received on a
513             * secure channel
514             * @param matchLevels the number of elements from the request path
515             * to use in the match value
516             * @param channels the channels on which the event is to be 
517             * fired (optional)
518             */
519            public Trace(HttpRequest request, boolean secure,
520                    int matchLevels, Channel... channels) {
521                super(secure ? "https" : "http", request, matchLevels,
522                    channels);
523            }
524        }
525    }
526
527    /**
528     * The base class for all outgoing HTTP requests. Outgoing
529     * request flow upstream and are served externally. 
530     * 
531     * A result of `true` indicates that the request has been processed, 
532     * i.e. a response has been sent or will sent.
533     */
534    @SuppressWarnings("PMD.ShortClassName")
535    public static class Out extends Request<Void> {
536
537        private HttpRequest request;
538        private BiConsumer<Request.Out, TcpChannel> connectedCallback;
539
540        /**
541         * Instantiates a new request.
542         *
543         * @param method the method
544         * @param url the url
545         */
546        public Out(String method, URL url) {
547            try {
548                request = new HttpRequest(method, url.toURI(),
549                    HttpProtocol.HTTP_1_1, false);
550            } catch (URISyntaxException e) {
551                // This should not happen because every valid URL can be
552                // converted to a URI.
553                throw new IllegalArgumentException(e);
554            }
555        }
556
557        /**
558         * Sets a "connected callback". When the {@link Out} event is
559         * created, the network connection is not yet known. Some
560         * header fields' values, however, need e.g. the port information
561         * from the connection. Therefore a callback may be set which is
562         * invoked when the connection has been obtained that will be used
563         * to send the request.
564         *
565         * @param connectedCallback the connected callback
566         * @return the out
567         */
568        public Out setConnectedCallback(
569                BiConsumer<Request.Out, TcpChannel> connectedCallback) {
570            this.connectedCallback = connectedCallback;
571            return this;
572        }
573
574        /**
575         * Returns the connected callback.
576         *
577         * @return the connected callback, if set
578         */
579        public Optional<BiConsumer<Request.Out, TcpChannel>>
580                connectedCallback() {
581            return Optional.ofNullable(connectedCallback);
582        }
583
584        /**
585         * The HTTP request that will be sent by the event.
586         *
587         * @return the http request
588         */
589        public HttpRequest httpRequest() {
590            return request;
591        }
592
593        /**
594         * Returns an absolute URI of the request.
595         * 
596         * @return the URI
597         */
598        public URI requestUri() {
599            return request.requestUri();
600        }
601
602        /*
603         * (non-Javadoc)
604         * 
605         * @see java.lang.Object#toString()
606         */
607        @Override
608        public String toString() {
609            StringBuilder builder = new StringBuilder();
610            builder.append(Components.objectName(this))
611                .append(" [").append(request.toString());
612            if (channels().length > 0) {
613                builder.append(", channels=");
614                builder.append(Channel.toString(channels()));
615            }
616            builder.append(']');
617            return builder.toString();
618        }
619
620        /**
621         * Represents a HTTP CONNECT request.
622         */
623        public static class Connect extends Out {
624
625            /**
626             * Instantiates a new request.
627             *
628             * @param uri the uri
629             */
630            public Connect(URL uri) {
631                super("CONNECT", uri);
632            }
633        }
634
635        /**
636         * Represents a HTTP DELETE request.
637         */
638        public static class Delete extends Out {
639
640            /**
641             * Instantiates a new request.
642             *
643             * @param uri the uri
644             */
645            public Delete(URL uri) {
646                super("DELETE", uri);
647            }
648        }
649
650        /**
651         * Represents a HTTP GET request.
652         */
653        public static class Get extends Out {
654
655            /**
656             * Instantiates a new request.
657             *
658             * @param uri the uri
659             */
660            public Get(URL uri) {
661                super("GET", uri);
662            }
663        }
664
665        /**
666         * Represents a HTTP HEAD request.
667         */
668        public static class Head extends Out {
669
670            /**
671             * Instantiates a new request.
672             *
673             * @param uri the uri
674             */
675            public Head(URL uri) {
676                super("HEAD", uri);
677            }
678        }
679
680        /**
681         * Represents a HTTP OPTIONS request.
682         */
683        public static class Options extends Out {
684
685            /**
686             * Instantiates a new request.
687             *
688             * @param uri the uri
689             */
690            public Options(URL uri) {
691                super("OPTIONS", uri);
692            }
693        }
694
695        /**
696         * Represents a HTTP POST request.
697         */
698        public static class Post extends Out {
699
700            /**
701             * Instantiates a new request.
702             *
703             * @param uri the uri
704             */
705            public Post(URL uri) {
706                super("POST", uri);
707            }
708        }
709
710        /**
711         * Represents a HTTP PUT request.
712         */
713        public static class Put extends Out {
714
715            /**
716             * Instantiates a new request.
717             *
718             * @param uri the uri
719             */
720            public Put(URL uri) {
721                super("PUT", uri);
722            }
723        }
724
725        /**
726         * Represents a HTTP TRACE request.
727         */
728        public static class Trace extends Out {
729
730            /**
731             * Instantiates a new request.
732             *
733             * @param uri the uri
734             */
735            public Trace(URL uri) {
736                super("TRACE", uri);
737            }
738        }
739    }
740}