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}