001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.http.entity;
018
019
020import java.io.*;
021import java.nio.charset.*;
022
023import org.apache.juneau.http.*;
024import static org.apache.juneau.commons.utils.Shorts.*;
025
026/**
027 * A non-repeatable {@link HttpBody} that streams character content from a {@link Reader}, encoded as UTF-8.
028 *
029 * <p>
030 * The character analog of {@link StreamBody}.  Because the reader is one-shot, {@link #isRepeatable()} returns
031 * {@code false} and {@link #getContentLength()} returns {@code -1} (the transport must use chunked transfer encoding).
032 * Content is streamed directly through {@link #writeTo(OutputStream)} rather than being drained into a {@link String}
033 * first.
034 *
035 * <p>
036 * The caller is responsible for closing the wrapped reader after the response has been consumed.
037 *
038 * @since 9.2.1
039 */
040@SuppressWarnings({
041   "resource" // reader is caller-owned; the OutputStreamWriter wraps (but does not close) the caller-owned out
042})
043public final class ReaderBody implements HttpBody {
044
045   private final Reader reader;
046   private final String contentType;
047
048   private ReaderBody(Reader reader, String contentType) {
049      this.reader = reqnn("reader", reader);
050      this.contentType = contentType;
051   }
052
053   /**
054    * Creates a {@link ReaderBody} with {@code text/plain} content type.
055    *
056    * @param reader The reader. Must not be <jk>null</jk>.
057    * @return A new instance. Never <jk>null</jk>.
058    */
059   public static ReaderBody of(Reader reader) {
060      return new ReaderBody(reader, "text/plain");
061   }
062
063   /**
064    * Creates a {@link ReaderBody} with the given content type.
065    *
066    * @param reader The reader. Must not be <jk>null</jk>.
067    * @param contentType The MIME content type. May be <jk>null</jk>.
068    * @return A new instance. Never <jk>null</jk>.
069    */
070   public static ReaderBody of(Reader reader, String contentType) {
071      return new ReaderBody(reader, contentType);
072   }
073
074   @Override /* HttpBody */
075   public String getContentType() {
076      return contentType;
077   }
078
079   @Override /* HttpBody */
080   public void writeTo(OutputStream out) throws IOException {
081      var w = new OutputStreamWriter(out, StandardCharsets.UTF_8);
082      reader.transferTo(w);
083      w.flush();
084   }
085
086   @Override /* HttpBody */
087   public boolean isRepeatable() {
088      return false;
089   }
090}