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