001/*
002 * This file is part of the JDrupes non-blocking HTTP Codec
003 * Copyright (C) 2016, 2017  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 Lesser General Public License as published
007 * by 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 Lesser General Public 
013 * License for more details.
014 *
015 * You should have received a copy of the GNU Lesser General Public License along 
016 * with this program; if not, see <http://www.gnu.org/licenses/>.
017 */
018
019package org.jdrupes.httpcodec;
020
021import java.nio.Buffer;
022import java.nio.ByteBuffer;
023import java.util.Optional;
024
025import org.jdrupes.httpcodec.Codec.ProtocolSwitchResult;
026import org.jdrupes.httpcodec.protocols.http.client.HttpResponseDecoder;
027
028/**
029 * An engine that can be used as a client. It has an associated
030 * request encoder and a response decoder. Using a {@link ClientEngine}
031 * has two main advantages over using an encoder and decoder
032 * directly. It links encoder and decoder (see 
033 * {@link Encoder#setPeerDecoder(Decoder)} and
034 * {@link Decoder#setPeerEncoder(Encoder)}) and it replaces the encoder 
035 * and decoder if the decoded result indicates a switch. The change takes 
036 * place upon the next `encode` or `decode` invocation. The "old" encoders
037 * and decoders are thus still available when the result of
038 * a decode invocation, that indicates a switch, is processed.
039 * 
040 * @param <Q> the message header type handled be the encoder (the request)
041 * @param <R> the message header type handled by the decoder (the response)
042 */
043public class ClientEngine<Q extends MessageHeader, 
044        R extends MessageHeader> extends Engine {
045
046        private Encoder<?, ?> requestEncoder;
047        private Decoder<?, ?> responseDecoder;
048        private Encoder<?, ?> newRequestEncoder;
049        private Decoder<?, ?> newResponseDecoder;
050        
051        /**
052         * Creates a new instance.
053         * 
054         * @param requestEncoder the encoder for the request
055         * @param responseDecoder the decoder for the response
056         */
057        public ClientEngine(Encoder<Q, R> requestEncoder,
058                        Decoder<R, Q> responseDecoder) {
059                this.requestEncoder = requestEncoder;
060                this.responseDecoder = responseDecoder;
061                requestEncoder.setPeerDecoder(responseDecoder);
062                responseDecoder.setPeerEncoder(requestEncoder);
063        }
064        
065        /**
066         * @return the requestEncoder
067         */
068        @SuppressWarnings("unchecked")
069        public Encoder<Q, R> requestEncoder() {
070                return (Encoder<Q, R>)requestEncoder;
071        }
072        
073        /**
074         * @return the responseDecoder
075         */
076        @SuppressWarnings("unchecked")
077        public Decoder<R,Q> responseDecoder() {
078                return (Decoder<R,Q>)responseDecoder;
079        }
080
081        /**
082         * Convenience method to invoke the encoder's encode method.
083         * 
084         * @param out the buffer to use for the result
085         * @return the result
086         */
087        public Codec.Result encode(ByteBuffer out) {
088                return requestEncoder.encode(out);
089        }
090
091        /**
092         * Convenience method to invoke the encoder's encode method.
093         * 
094         * @param in the buffer with the data to encode
095         * @param out the buffer to use for the result
096         * @param endOfInput {@code true} if end of input
097         * @return the result
098         */
099        public Codec.Result encode(Buffer in, ByteBuffer out, boolean endOfInput) {
100                return requestEncoder.encode(in, out, endOfInput);
101        }
102
103        /**
104         * Convenience method to invoke the encoder's encode method.
105         * 
106         * @param messageHeader the message header
107         */
108        @SuppressWarnings("unchecked")
109        public void encode(Q messageHeader) {
110                if (newRequestEncoder != null) {
111                        requestEncoder = newRequestEncoder;
112                        newRequestEncoder = null;
113                }
114                ((Encoder<Q, R>)requestEncoder).encode(messageHeader);
115        }
116
117        /**
118         * Convenience method to invoke the decoder's decode method.
119         * 
120         * @param in the buffer with the data to decode
121         * @param out the buffer to use for the result
122         * @param endOfInput {@code true} if end of input
123         * @return the result
124         * @throws ProtocolException if the input violates the protocol
125         * @see HttpResponseDecoder#decode(java.nio.ByteBuffer, java.nio.Buffer, boolean)
126         */
127        @SuppressWarnings("unchecked")
128        public Decoder.Result<Q> decode(
129                ByteBuffer in, Buffer out, boolean endOfInput)
130                throws ProtocolException {
131                if (newResponseDecoder != null) {
132                        responseDecoder = newResponseDecoder;
133                        newResponseDecoder = null;
134                }
135                Decoder.Result<Q> result 
136                        = (Decoder.Result<Q>)responseDecoder.decode(in, out, endOfInput);
137                if (result instanceof ProtocolSwitchResult) {
138                        ProtocolSwitchResult res = (ProtocolSwitchResult)result;
139                        if (res.newProtocol() != null) {
140                                setSwitchedTo(res.newProtocol());
141                                newResponseDecoder = res.newDecoder();
142                                newRequestEncoder = res.newEncoder();
143                                ((Decoder<MessageHeader, MessageHeader>)newResponseDecoder)
144                                        .setPeerEncoder((Encoder<MessageHeader, MessageHeader>)
145                                                        newRequestEncoder);
146                                ((Encoder<MessageHeader, MessageHeader>)newRequestEncoder)
147                                                .setPeerDecoder((Decoder<MessageHeader, MessageHeader>)
148                                                                newResponseDecoder);
149                        }
150                }
151                return result;
152        }
153        
154        /**
155         * Returns the last encoded request.
156         * 
157         * @return the request
158         */
159        @SuppressWarnings("unchecked")
160        public Optional<Q> currentRequest() {
161                return (Optional<Q>)requestEncoder.header();
162        }
163        
164}