001/*
002 * ====================================================================
003 * Licensed to the Apache Software Foundation (ASF) under one
004 * or more contributor license agreements.  See the NOTICE file
005 * distributed with this work for additional information
006 * regarding copyright ownership.  The ASF licenses this file
007 * to you under the Apache License, Version 2.0 (the
008 * "License"); you may not use this file except in compliance
009 * with the License.  You may obtain a copy of the License at
010 *
011 *   http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing,
014 * software distributed under the License is distributed on an
015 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
016 * KIND, either express or implied.  See the License for the
017 * specific language governing permissions and limitations
018 * under the License.
019 * ====================================================================
020 *
021 * This software consists of voluntary contributions made by many
022 * individuals on behalf of the Apache Software Foundation.  For more
023 * information on the Apache Software Foundation, please see
024 * <http://www.apache.org/>.
025 *
026 */
027
028package org.apache.http.impl.nio.reactor;
029
030import java.io.IOException;
031import java.nio.ByteBuffer;
032import java.nio.CharBuffer;
033import java.nio.channels.ReadableByteChannel;
034import java.nio.channels.WritableByteChannel;
035import java.nio.charset.CharacterCodingException;
036import java.nio.charset.Charset;
037import java.nio.charset.CharsetEncoder;
038import java.nio.charset.CoderResult;
039import java.nio.charset.CodingErrorAction;
040
041import org.apache.http.nio.reactor.SessionOutputBuffer;
042import org.apache.http.nio.util.ByteBufferAllocator;
043import org.apache.http.nio.util.ExpandableBuffer;
044import org.apache.http.nio.util.HeapByteBufferAllocator;
045import org.apache.http.params.CoreProtocolPNames;
046import org.apache.http.params.HttpParams;
047import org.apache.http.protocol.HTTP;
048import org.apache.http.util.Args;
049import org.apache.http.util.CharArrayBuffer;
050import org.apache.http.util.CharsetUtils;
051
052/**
053 * Default implementation of {@link SessionOutputBuffer} based on
054 * the {@link ExpandableBuffer} class.
055 *
056 * @since 4.0
057 */
058@SuppressWarnings("deprecation")
059public class SessionOutputBufferImpl extends ExpandableBuffer implements SessionOutputBuffer {
060
061    private static final byte[] CRLF = new byte[] {HTTP.CR, HTTP.LF};
062
063    private final CharsetEncoder charencoder;
064    private final int lineBuffersize;
065
066    private CharBuffer charbuffer;
067
068    /**
069     *  Creates SessionOutputBufferImpl instance.
070     *
071     * @param buffersize input buffer size
072     * @param lineBuffersize buffer size for line operations. Has effect only if
073     *   {@code charencoder} is not {@code null}.
074     * @param charencoder charencoder to be used for encoding HTTP protocol elements.
075     *   If {@code null} simple type cast will be used for char to byte conversion.
076     * @param allocator memory allocator.
077     *   If {@code null} {@link HeapByteBufferAllocator#INSTANCE} will be used.
078     *
079     * @since 4.3
080     */
081    public SessionOutputBufferImpl(
082            final int buffersize,
083            final int lineBuffersize,
084            final CharsetEncoder charencoder,
085            final ByteBufferAllocator allocator) {
086        super(buffersize, allocator != null ? allocator : HeapByteBufferAllocator.INSTANCE);
087        this.lineBuffersize = Args.positive(lineBuffersize, "Line buffer size");
088        this.charencoder = charencoder;
089    }
090
091    /**
092     * @deprecated (4.3) use
093     *   {@link SessionOutputBufferImpl#SessionOutputBufferImpl(int, int, CharsetEncoder,
094     *     ByteBufferAllocator)}
095     */
096    @Deprecated
097    public SessionOutputBufferImpl(
098            final int buffersize,
099            final int lineBuffersize,
100            final ByteBufferAllocator allocator,
101            final HttpParams params) {
102        super(buffersize, allocator);
103        this.lineBuffersize = Args.positive(lineBuffersize, "Line buffer size");
104        final String charsetName = (String) params.getParameter(CoreProtocolPNames.HTTP_ELEMENT_CHARSET);
105        final Charset charset = CharsetUtils.lookup(charsetName);
106        if (charset != null) {
107            this.charencoder = charset.newEncoder();
108            final CodingErrorAction a1 = (CodingErrorAction) params.getParameter(
109                    CoreProtocolPNames.HTTP_MALFORMED_INPUT_ACTION);
110            this.charencoder.onMalformedInput(a1 != null ? a1 : CodingErrorAction.REPORT);
111            final CodingErrorAction a2 = (CodingErrorAction) params.getParameter(
112                    CoreProtocolPNames.HTTP_UNMAPPABLE_INPUT_ACTION);
113            this.charencoder.onUnmappableCharacter(a2 != null? a2 : CodingErrorAction.REPORT);
114        } else {
115            this.charencoder = null;
116        }
117    }
118
119    /**
120     * @deprecated (4.3) use
121     *   {@link SessionOutputBufferImpl#SessionOutputBufferImpl(int, int, Charset)}
122     */
123    @Deprecated
124    public SessionOutputBufferImpl(
125            final int buffersize,
126            final int linebuffersize,
127            final HttpParams params) {
128        this(buffersize, linebuffersize, HeapByteBufferAllocator.INSTANCE, params);
129    }
130
131    /**
132     * @since 4.3
133     */
134    public SessionOutputBufferImpl(final int buffersize) {
135        this(buffersize, 256, null, HeapByteBufferAllocator.INSTANCE);
136    }
137
138    /**
139     * @since 4.3
140     */
141    public SessionOutputBufferImpl(
142            final int buffersize,
143            final int linebuffersize,
144            final Charset charset) {
145        this(buffersize, linebuffersize,
146                charset != null ? charset.newEncoder() : null, HeapByteBufferAllocator.INSTANCE);
147    }
148
149    /**
150     * @since 4.3
151     */
152    public SessionOutputBufferImpl(
153            final int buffersize,
154            final int linebuffersize) {
155        this(buffersize, linebuffersize, null, HeapByteBufferAllocator.INSTANCE);
156    }
157
158    public void reset(final HttpParams params) {
159        clear();
160    }
161
162    @Override
163    public int flush(final WritableByteChannel channel) throws IOException {
164        Args.notNull(channel, "Channel");
165        setOutputMode();
166        return channel.write(this.buffer);
167    }
168
169    @Override
170    public void write(final ByteBuffer src) {
171        if (src == null) {
172            return;
173        }
174        setInputMode();
175        final int requiredCapacity = this.buffer.position() + src.remaining();
176        ensureCapacity(requiredCapacity);
177        this.buffer.put(src);
178    }
179
180    @Override
181    public void write(final ReadableByteChannel src) throws IOException {
182        if (src == null) {
183            return;
184        }
185        setInputMode();
186        src.read(this.buffer);
187    }
188
189    private void write(final byte[] b) {
190        if (b == null) {
191            return;
192        }
193        setInputMode();
194        final int off = 0;
195        final int len = b.length;
196        final int requiredCapacity = this.buffer.position() + len;
197        ensureCapacity(requiredCapacity);
198        this.buffer.put(b, off, len);
199    }
200
201    private void writeCRLF() {
202        write(CRLF);
203    }
204
205    @Override
206    public void writeLine(final CharArrayBuffer linebuffer) throws CharacterCodingException {
207        if (linebuffer == null) {
208            return;
209        }
210        setInputMode();
211        // Do not bother if the buffer is empty
212        if (linebuffer.length() > 0 ) {
213            if (this.charencoder == null) {
214                final int requiredCapacity = this.buffer.position() + linebuffer.length();
215                ensureCapacity(requiredCapacity);
216                if (this.buffer.hasArray()) {
217                    final byte[] b = this.buffer.array();
218                    final int len = linebuffer.length();
219                    final int off = this.buffer.position();
220                    for (int i = 0; i < len; i++) {
221                        b[off + i]  = (byte) linebuffer.charAt(i);
222                    }
223                    this.buffer.position(off + len);
224                } else {
225                    for (int i = 0; i < linebuffer.length(); i++) {
226                        this.buffer.put((byte) linebuffer.charAt(i));
227                    }
228                }
229            } else {
230                if (this.charbuffer == null) {
231                    this.charbuffer = CharBuffer.allocate(this.lineBuffersize);
232                }
233                this.charencoder.reset();
234                // transfer the string in small chunks
235                int remaining = linebuffer.length();
236                int offset = 0;
237                while (remaining > 0) {
238                    int l = this.charbuffer.remaining();
239                    boolean eol = false;
240                    if (remaining <= l) {
241                        l = remaining;
242                        // terminate the encoding process
243                        eol = true;
244                    }
245                    this.charbuffer.put(linebuffer.buffer(), offset, l);
246                    this.charbuffer.flip();
247
248                    boolean retry = true;
249                    while (retry) {
250                        final CoderResult result = this.charencoder.encode(this.charbuffer, this.buffer, eol);
251                        if (result.isError()) {
252                            result.throwException();
253                        }
254                        if (result.isOverflow()) {
255                            expand();
256                        }
257                        retry = !result.isUnderflow();
258                    }
259                    this.charbuffer.compact();
260                    offset += l;
261                    remaining -= l;
262                }
263                // flush the encoder
264                boolean retry = true;
265                while (retry) {
266                    final CoderResult result = this.charencoder.flush(this.buffer);
267                    if (result.isError()) {
268                        result.throwException();
269                    }
270                    if (result.isOverflow()) {
271                        expand();
272                    }
273                    retry = !result.isUnderflow();
274                }
275            }
276        }
277        writeCRLF();
278    }
279
280    @Override
281    public void writeLine(final String s) throws IOException {
282        if (s == null) {
283            return;
284        }
285        if (s.length() > 0) {
286            final CharArrayBuffer tmp = new CharArrayBuffer(s.length());
287            tmp.append(s);
288            writeLine(tmp);
289        } else {
290            write(CRLF);
291        }
292    }
293
294}