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.codecs;
029
030import java.io.IOException;
031import java.nio.ByteBuffer;
032import java.nio.channels.FileChannel;
033import java.nio.channels.WritableByteChannel;
034
035import org.apache.http.impl.io.HttpTransportMetricsImpl;
036import org.apache.http.nio.FileContentEncoder;
037import org.apache.http.nio.reactor.SessionOutputBuffer;
038
039/**
040 * Content encoder that writes data without any transformation. The end of
041 * the content entity is demarcated by closing the underlying connection
042 * (EOF condition). Entities transferred using this input stream can be of
043 * unlimited length.
044 * <p>
045 * This decoder is optimized to transfer data directly from
046 * a {@link FileChannel} to the underlying I/O session's channel whenever
047 * possible avoiding intermediate buffering in the session buffer.
048 *
049 * @since 4.0
050 */
051public class IdentityEncoder extends AbstractContentEncoder
052        implements FileContentEncoder {
053
054    private final int fragHint;
055
056    /**
057     * @since 4.3
058     *
059     * @param channel underlying channel.
060     * @param buffer  session buffer.
061     * @param metrics transport metrics.
062     * @param fragementSizeHint fragment size hint defining an minimal size of a fragment
063     *   that should be written out directly to the channel bypassing the session buffer.
064     *   Value {@code 0} disables fragment buffering.
065     */
066    public IdentityEncoder(
067            final WritableByteChannel channel,
068            final SessionOutputBuffer buffer,
069            final HttpTransportMetricsImpl metrics,
070            final int fragementSizeHint) {
071        super(channel, buffer, metrics);
072        this.fragHint = fragementSizeHint > 0 ? fragementSizeHint : 0;
073    }
074
075    public IdentityEncoder(
076            final WritableByteChannel channel,
077            final SessionOutputBuffer buffer,
078            final HttpTransportMetricsImpl metrics) {
079        this(channel, buffer, metrics, 0);
080    }
081
082    @Override
083    public int write(final ByteBuffer src) throws IOException {
084        if (src == null) {
085            return 0;
086        }
087        assertNotCompleted();
088
089        int total = 0;
090        while (src.hasRemaining()) {
091            if (this.buffer.hasData() || this.fragHint > 0) {
092                if (src.remaining() <= this.fragHint) {
093                    final int capacity = this.fragHint - this.buffer.length();
094                    if (capacity > 0) {
095                        final int limit = Math.min(capacity, src.remaining());
096                        final int bytesWritten = writeToBuffer(src, limit);
097                        total += bytesWritten;
098                    }
099                }
100            }
101            if (this.buffer.hasData()) {
102                if (this.buffer.length() >= this.fragHint || src.hasRemaining()) {
103                    final int bytesWritten = flushToChannel();
104                    if (bytesWritten == 0) {
105                        break;
106                    }
107                }
108            }
109            if (!this.buffer.hasData() && src.remaining() > this.fragHint) {
110                final int bytesWritten = writeToChannel(src);
111                total += bytesWritten;
112                if (bytesWritten == 0) {
113                    break;
114                }
115            }
116        }
117        return total;
118    }
119
120    @Override
121    public long transfer(
122            final FileChannel src,
123            final long position,
124            final long count) throws IOException {
125
126        if (src == null) {
127            return 0;
128        }
129        assertNotCompleted();
130
131        flushToChannel();
132        if (this.buffer.hasData()) {
133            return 0;
134        }
135
136        final long bytesWritten = src.transferTo(position, count, this.channel);
137        if (bytesWritten > 0) {
138            this.metrics.incrementBytesTransferred(bytesWritten);
139        }
140        return bytesWritten;
141    }
142
143    @Override
144    public String toString() {
145        final StringBuilder sb = new StringBuilder();
146        sb.append("[identity; completed: ");
147        sb.append(isCompleted());
148        sb.append("]");
149        return sb.toString();
150    }
151
152}