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.resource;
018
019import static org.apache.juneau.commons.utils.CollectionUtils.*;
020import static org.apache.juneau.commons.utils.Shorts.*;
021
022import java.io.*;
023import java.util.*;
024
025import org.apache.juneau.http.*;
026import org.apache.juneau.http.header.*;
027
028/**
029 * Default immutable implementation of {@link HttpResource}: an {@link HttpBody} bundled with a set of
030 * associated headers (such as {@code Content-Type}, {@code Content-Disposition}, or {@code Content-Language}).
031 *
032 * <p>
033 * Useful for static-file delivery, multipart form parts, and other scenarios where headers must travel
034 * with the body.
035 *
036 * <p>
037 * Create instances via the static factory methods:
038 * <p class='bjava'>
039 *    <jc>// Body with content type header</jc>
040 *    HttpResource <jv>resource</jv> = HttpResourceBean.<jsm>of</jsm>(FileBody.<jsm>of</jsm>(myFile))
041 *       .withHeader(ContentType.<jsm>of</jsm>(<js>"application/pdf"</js>))
042 *       .withHeader(ContentDisposition.<jsm>of</jsm>(<js>"attachment; filename=\"report.pdf\""</js>));
043 * </p>
044 *
045 * <h5 class='section'>See Also:</h5><ul>
046 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/NextGenRestClient">Next-Generation REST Client</a>
047 * </ul>
048 *
049 * @since 9.2.1
050 */
051public final class HttpResourceBean implements HttpResource {
052
053   private final HttpBody body;
054   private final List<HttpHeader> headers;
055
056   private HttpResourceBean(HttpBody body, List<HttpHeader> headers) {
057      this.body = body;
058      this.headers = List.copyOf(headers);
059   }
060
061   /**
062    * Creates an {@link HttpResourceBean} wrapping the given body with no additional headers.
063    *
064    * @param body The body. Must not be <jk>null</jk>.
065    * @return A new instance. Never <jk>null</jk>.
066    */
067   public static HttpResourceBean of(HttpBody body) {
068      reqnn("body", body);
069      return new HttpResourceBean(body, List.of());
070   }
071
072   /**
073    * Creates an {@link HttpResourceBean} wrapping the given body and headers.
074    *
075    * @param body The body. Must not be <jk>null</jk>.
076    * @param headers The associated headers. Must not be <jk>null</jk>.
077    * @return A new instance. Never <jk>null</jk>.
078    */
079   public static HttpResourceBean of(HttpBody body, List<HttpHeader> headers) {
080      reqnn("body", body);
081      reqnn("headers", headers);
082      return new HttpResourceBean(body, headers);
083   }
084
085   /**
086    * Returns a new instance with the given header added.
087    *
088    * @param header The header to add. Must not be <jk>null</jk>.
089    * @return A new instance. Never <jk>null</jk>.
090    */
091   public HttpResourceBean withHeader(HttpHeader header) {
092      reqnn("header", header);
093      var newHeaders = new ArrayList<>(headers);
094      newHeaders.add(header);
095      return new HttpResourceBean(body, newHeaders);
096   }
097
098   /**
099    * Returns a new instance with the given header name and value added.
100    *
101    * @param name The header name. Must not be <jk>null</jk>.
102    * @param value The header value. May be <jk>null</jk>.
103    * @return A new instance. Never <jk>null</jk>.
104    */
105   public HttpResourceBean withHeader(String name, String value) {
106      return withHeader(HttpHeaderBean.of(name, value));
107   }
108
109   /**
110    * Returns a new instance with the given headers added.
111    *
112    * @param toAdd The headers to add. {@code null} entries are ignored.
113    * @return A new instance. Never <jk>null</jk>.
114    */
115   public HttpResourceBean withHeaders(HttpHeader...toAdd) {
116      if (toAdd == null || toAdd.length == 0)
117         return this;
118      var newHeaders = new ArrayList<>(headers);
119      addAllNn(newHeaders, toAdd);
120      return new HttpResourceBean(body, newHeaders);
121   }
122
123   /**
124    * Returns a new instance with the given headers added.
125    *
126    * @param toAdd The headers to add. {@code null} entries are ignored.
127    * @return A new instance. Never <jk>null</jk>.
128    */
129   public HttpResourceBean withHeaders(List<HttpHeader> toAdd) {
130      if (ie(toAdd))
131         return this;
132      var newHeaders = new ArrayList<>(headers);
133      addAllNn(newHeaders, toAdd);
134      return new HttpResourceBean(body, newHeaders);
135   }
136
137   /**
138    * Returns the underlying body.
139    *
140    * @return The body. Never <jk>null</jk>.
141    */
142   public HttpBody getBody() {
143      return body;
144   }
145
146   @Override /* HttpResource */
147   public List<HttpHeader> getHeaders() {
148      return headers;
149   }
150
151   /**
152    * Returns the first header with the given name (case-insensitive), or <jk>null</jk> if absent.
153    *
154    * @param name The header name. Must not be <jk>null</jk>.
155    * @return The first matching header, or <jk>null</jk> if absent.
156    */
157   public HttpHeader getFirstHeader(String name) {
158      reqnn("name", name);
159      return headers.stream().filter(h -> eqic(h.getName(), name)).findFirst().orElse(null);
160   }
161
162   // ------------------------------------------------------------------------------------------------------------------
163   // HttpBody delegation
164   // ------------------------------------------------------------------------------------------------------------------
165
166   @Override /* HttpBody */
167   public String getContentType() {
168      var ct = getFirstHeader("Content-Type");
169      return ct != null ? ct.getValue() : body.getContentType();
170   }
171
172   @Override /* HttpBody */
173   public long getContentLength() {
174      return body.getContentLength();
175   }
176
177   @Override /* HttpBody */
178   public boolean isRepeatable() {
179      return body.isRepeatable();
180   }
181
182   @Override /* HttpBody */
183   public void writeTo(OutputStream out) throws IOException {
184      body.writeTo(out);
185   }
186
187   @Override /* Object */
188   public String toString() {
189      return body.toString();
190   }
191}