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.response; 018 019import static org.apache.juneau.commons.utils.Shorts.*; 020 021import java.net.*; 022import java.util.*; 023 024import org.apache.juneau.http.*; 025import org.apache.juneau.http.entity.*; 026import org.apache.juneau.http.header.*; 027 028/** 029 * Base class for concrete HTTP response objects in the {@code org.apache.juneau.http} stack. 030 * 031 * <p> 032 * Holds a status line, an optional body, and an ordered list of headers. Provides classic-style 033 * fluent setters that mutate state and return {@code this}. 034 * 035 * <p> 036 * Immutability is expressed with the "funnel + nested {@code Unmodifiable} snapshot" paradigm: every mutator routes 037 * through the single protected {@link #modify(Runnable)} choke-point, and each concrete leaf gets a nested 038 * {@code X.Unmodifiable} whose only behavioral override is a throwing {@code modify(...)}. A leaf's 039 * {@link #unmodifiable()} factory returns a point-in-time snapshot of type {@code X.Unmodifiable}; any attempt to set a 040 * property on it throws an {@link UnsupportedOperationException}. 041 * 042 * <p> 043 * This is a self-typed (CRTP) root: a leaf declares <c>class Ok <jk>extends</jk> BasicHttpResponse<Ok></c> and 044 * inherits leaf-typed fluent setters with no covariant overrides. 045 * 046 * <h5 class='section'>See Also:</h5><ul> 047 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/NextGenRestClient">Next-Generation REST Client</a> 048 * </ul> 049 * 050 * @param <SELF> The self type for fluent setters. 051 * @since 9.2.1 052 */ 053@SuppressWarnings({ 054 "java:S119" // 'SELF' (CRTP self-type) is intentional and clearer than a single-letter name. 055}) 056public abstract class BasicHttpResponse<SELF extends BasicHttpResponse<SELF>> implements HttpResponseMessage { 057 058 private HttpStatusLine statusLine; 059 private HttpHeaderList headers; 060 private HttpBody body; 061 private Locale locale; 062 063 /** 064 * Constructor with no body. 065 * 066 * @param statusLine The status line. Must not be <jk>null</jk>. 067 */ 068 protected BasicHttpResponse(HttpStatusLine statusLine) { 069 // Reason phrase is documented-nullable; StringBody.of asserts non-null, so guard against a valid status line 070 // that carries a null reason phrase (would otherwise NPE on a legitimate input). 071 this(statusLine, l(), 072 statusLine != null && statusLine.getReasonPhrase() != null ? StringBody.of(statusLine.getReasonPhrase()) : null); 073 } 074 075 /** 076 * Constructor with a body. 077 * 078 * @param statusLine The status line. Must not be <jk>null</jk>. 079 * @param body The response body. May be <jk>null</jk>. 080 */ 081 protected BasicHttpResponse(HttpStatusLine statusLine, HttpBody body) { 082 this(statusLine, l(), body); 083 } 084 085 /** 086 * Constructor with a string body (UTF-8, {@code text/plain}). 087 * 088 * @param statusLine The status line. Must not be <jk>null</jk>. 089 * @param body The response body as a plain-text string. May be <jk>null</jk>. 090 */ 091 protected BasicHttpResponse(HttpStatusLine statusLine, String body) { 092 this(statusLine, l(), body != null ? StringBody.of(body) : null); 093 } 094 095 /** 096 * Constructor with headers and an optional body. 097 * 098 * @param statusLine The status line. Must not be <jk>null</jk>. 099 * @param headers The response headers. Can be <jk>null</jk> (treated as an empty list). 100 * @param body The response body. May be <jk>null</jk>. 101 */ 102 protected BasicHttpResponse(HttpStatusLine statusLine, List<HttpHeader> headers, HttpBody body) { 103 this.statusLine = reqnn("statusLine", statusLine); 104 this.headers = HttpHeaderList.of(headers); 105 this.body = body; 106 } 107 108 /** 109 * Copy constructor. 110 * 111 * @param copyFrom The instance to copy. Must not be <jk>null</jk>. 112 */ 113 protected BasicHttpResponse(BasicHttpResponse<?> copyFrom) { 114 reqnn("copyFrom", copyFrom); 115 this.statusLine = copyFrom.statusLine; 116 this.headers = copyFrom.headers.copy(); 117 this.body = copyFrom.body; 118 this.locale = copyFrom.locale; 119 } 120 121 // ------------------------------------------------------------------------------------------------------------------ 122 // Read accessors 123 // ------------------------------------------------------------------------------------------------------------------ 124 125 @Override /* HttpResponseMessage */ 126 public HttpStatusLine getStatusLine() { 127 return statusLine; 128 } 129 130 @Override /* HttpResponseMessage */ 131 public List<HttpHeader> getHeaders() { 132 return u(headers); 133 } 134 135 @Override /* HttpResponseMessage */ 136 public HttpBody getBody() { 137 return body; 138 } 139 140 /** 141 * Returns the HTTP status code from the status line. 142 * 143 * @return The status code. 144 */ 145 public int getStatusCode() { 146 return statusLine.getStatusCode(); 147 } 148 149 /** 150 * Returns the locale associated with this response, if any. 151 * 152 * @return The locale, possibly <jk>null</jk>. 153 */ 154 public Locale getLocale() { 155 return locale; 156 } 157 158 // ------------------------------------------------------------------------------------------------------------------ 159 // Fluent setters (mutate this instance) 160 // ------------------------------------------------------------------------------------------------------------------ 161 162 /** 163 * Sets the status line. 164 * 165 * @param value New status line. Must not be <jk>null</jk>. 166 * @return This object. 167 */ 168 public SELF setStatusLine(HttpStatusLine value) { 169 return modify(() -> statusLine = reqnn("value", value)); 170 } 171 172 /** 173 * Sets the status code, preserving the existing protocol version and reason phrase. 174 * 175 * @param value New status code. 176 * @return This object. 177 */ 178 public SELF setStatusCode(int value) { 179 return modify(() -> statusLine = HttpStatusLineBean.of(statusLine.getProtocolVersion(), value, statusLine.getReasonPhrase())); 180 } 181 182 /** 183 * Sets the reason phrase, preserving the existing protocol version and status code. 184 * 185 * @param value New reason phrase. May be <jk>null</jk>. 186 * @return This object. 187 */ 188 public SELF setReasonPhrase(String value) { 189 return modify(() -> statusLine = HttpStatusLineBean.of(statusLine.getProtocolVersion(), statusLine.getStatusCode(), value)); 190 } 191 192 /** 193 * Sets the protocol version, preserving the existing status code and reason phrase. 194 * 195 * @param value New protocol version. Must not be <jk>null</jk>. 196 * @return This object. 197 */ 198 public SELF setProtocolVersion(HttpProtocolVersion value) { 199 return modify(() -> { 200 reqnn("value", value); 201 statusLine = HttpStatusLineBean.of(value, statusLine.getStatusCode(), statusLine.getReasonPhrase()); 202 }); 203 } 204 205 /** 206 * Replaces all headers with the given list. 207 * 208 * @param values New headers. {@code null} is treated as an empty list. 209 * @return This object. 210 */ 211 public SELF setHeaders(List<HttpHeader> values) { 212 return modify(() -> { 213 headers.clear(); 214 if (values != null) 215 headers.append(values); 216 }); 217 } 218 219 /** 220 * Replaces all headers with the given array. 221 * 222 * @param values New headers. {@code null} is treated as an empty array. 223 * @return This object. 224 */ 225 public SELF setHeaders(HttpHeader...values) { 226 return modify(() -> { 227 headers.clear(); 228 if (values != null) 229 headers.append(values); 230 }); 231 } 232 233 /** 234 * Sets a single header, replacing any existing header with the same name. 235 * 236 * @param value The header to set. {@code null} is ignored. 237 * @return This object. 238 */ 239 public SELF setHeader(HttpHeader value) { 240 return modify(() -> headers.set(value)); 241 } 242 243 /** 244 * Sets a single header, replacing any existing header with the same name. 245 * 246 * @param name Header name. Must not be <jk>null</jk>. 247 * @param value Header value. May be <jk>null</jk>. 248 * @return This object. 249 */ 250 public SELF setHeader(String name, String value) { 251 return modify(() -> headers.set(name, value)); 252 } 253 254 /** 255 * Appends a header to the end of the header list. 256 * 257 * @param value The header to add. {@code null} is ignored. 258 * @return This object. 259 */ 260 public SELF addHeader(HttpHeader value) { 261 return modify(() -> headers.append(value)); 262 } 263 264 /** 265 * Appends a header to the end of the header list. 266 * 267 * @param name Header name. Must not be <jk>null</jk>. 268 * @param value Header value. May be <jk>null</jk>. 269 * @return This object. 270 */ 271 public SELF addHeader(String name, String value) { 272 return modify(() -> headers.append(name, value)); 273 } 274 275 /** 276 * Sets the response body. 277 * 278 * @param value New body. May be <jk>null</jk>. 279 * @return This object. 280 */ 281 public SELF setBody(HttpBody value) { 282 return modify(() -> body = value); 283 } 284 285 /** 286 * Sets the response body to a plain-text string body (UTF-8, {@code text/plain}). 287 * 288 * @param value Body text. May be <jk>null</jk>. 289 * @return This object. 290 */ 291 public SELF setContent(String value) { 292 return setBody(value != null ? StringBody.of(value) : null); 293 } 294 295 /** 296 * Sets the response body. 297 * 298 * @param value New body. May be <jk>null</jk>. 299 * @return This object. 300 */ 301 public SELF setContent(HttpBody value) { 302 return setBody(value); 303 } 304 305 /** 306 * Sets the locale associated with this response. 307 * 308 * @param value New locale. May be <jk>null</jk>. 309 * @return This object. 310 */ 311 public SELF setLocale(Locale value) { 312 return modify(() -> locale = value); 313 } 314 315 /** 316 * Sets the value of the {@code Location} header. 317 * 318 * @param value The new location value. May be <jk>null</jk>. 319 * @return This object. 320 */ 321 public SELF setLocation(String value) { 322 return setHeader(Location.NAME, value); 323 } 324 325 /** 326 * Sets the value of the {@code Location} header. 327 * 328 * @param value The new location value. May be <jk>null</jk>. 329 * @return This object. 330 */ 331 public SELF setLocation(URI value) { 332 return setHeader(Location.NAME, value != null ? value.toString() : null); 333 } 334 335 /** 336 * Returns whether this instance is an unmodifiable snapshot. 337 * 338 * @return <jk>true</jk> if this bean is an {@link UnmodifiableBean} snapshot. 339 */ 340 public boolean isUnmodifiable() { 341 return this instanceof UnmodifiableBean; 342 } 343 344 /** 345 * Returns an unmodifiable snapshot of this bean. 346 * 347 * <p> 348 * The returned instance is a point-in-time copy of the leaf's nested {@code Unmodifiable} type; any attempt to set a 349 * property on it throws an {@link UnsupportedOperationException}. This method is idempotent: if this bean is already 350 * unmodifiable it returns itself rather than taking another snapshot. The receiver is left unchanged (and still 351 * modifiable unless it was already unmodifiable). 352 * 353 * @return An unmodifiable snapshot of this bean, or this bean if it is already unmodifiable. 354 */ 355 public abstract SELF unmodifiable(); 356 357 // ------------------------------------------------------------------------------------------------------------------ 358 // Fluent "with" helpers (mutate this instance and return the leaf type) 359 // ------------------------------------------------------------------------------------------------------------------ 360 361 /** 362 * Sets the body on this response. 363 * 364 * @param value The new body. May be <jk>null</jk>. 365 * @return This object. 366 */ 367 public SELF withBody(HttpBody value) { 368 return modify(() -> body = value); 369 } 370 371 /** 372 * Sets the body on this response as a plain-text string. 373 * 374 * @param value The new body as a plain-text string. May be <jk>null</jk>. 375 * @return This object. 376 */ 377 public SELF withBody(String value) { 378 return withBody(value != null ? StringBody.of(value) : null); 379 } 380 381 /** 382 * Appends a header to this response. 383 * 384 * @param header The header to add. Must not be <jk>null</jk>. 385 * @return This object. 386 */ 387 public SELF withHeader(HttpHeader header) { 388 return modify(() -> headers.append(reqnn("header", header))); 389 } 390 391 /** 392 * Appends a header to this response. 393 * 394 * @param name The header name. Must not be <jk>null</jk>. 395 * @param value The header value. May be <jk>null</jk>. 396 * @return This object. 397 */ 398 public SELF withHeader(String name, String value) { 399 return withHeader(HttpHeaderBean.of(name, value)); 400 } 401 402 // ------------------------------------------------------------------------------------------------------------------ 403 // Immutability paradigm plumbing 404 // ------------------------------------------------------------------------------------------------------------------ 405 406 /** 407 * Returns this object cast to the self type. 408 * 409 * @return This object. 410 */ 411 @SuppressWarnings({ 412 "unchecked" // CRTP self-type cast is safe: SELF is bound to the concrete leaf type. 413 }) 414 protected final SELF self() { 415 return (SELF) this; 416 } 417 418 /** 419 * Single mutation funnel — the only choke-point through which all state changes on this bean flow. 420 * 421 * <p> 422 * Every fluent setter routes through this method. The nested {@code Unmodifiable} snapshot overrides only this 423 * method to throw, which freezes the entire mutation surface with a single override. 424 * 425 * @param mutation The state change to apply. 426 * @return This object. 427 */ 428 protected SELF modify(Runnable mutation) { 429 mutation.run(); 430 return self(); 431 } 432 433 /** 434 * Deep-freeze hook invoked from the nested {@code Unmodifiable} constructor after the snapshot copy completes. 435 * 436 * <p> 437 * The mutable sub-beans are frozen here by <b>direct field assignment</b> (never via a setter, which would route 438 * through the throwing {@link #modify(Runnable)} and blow up during construction). A leaf that adds its own mutable 439 * sub-bean overrides this method (calling {@code super.freeze()} first). 440 */ 441 protected void freeze() { 442 headers = headers.unmodifiable(); // DIRECT field write — snapshot the copied HttpHeaderList. 443 } 444 445 @Override /* Object */ 446 public boolean equals(Object o) { 447 return o instanceof BasicHttpResponse<?> other && eq(this, other, (x, y) -> 448 eq(x.statusLine, y.statusLine) && eq(x.headers, y.headers) && eq(x.body, y.body)); 449 } 450 451 @Override /* Object */ 452 public int hashCode() { 453 return h(statusLine, headers, body); 454 } 455 456 @Override /* Object */ 457 public String toString() { 458 return statusLine.toString(); 459 } 460}