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.classic.response; 018 019import static org.apache.juneau.commons.utils.Shorts.*; 020import static org.apache.juneau.http.classic.HttpEntities.*; 021 022import java.net.*; 023import java.util.*; 024 025import org.apache.http.*; 026import org.apache.http.Header; 027import org.apache.http.impl.*; 028import org.apache.http.params.*; 029import org.apache.juneau.http.*; 030import org.apache.juneau.http.classic.*; 031import org.apache.juneau.http.classic.header.*; 032import org.apache.juneau.marshall.*; 033 034/** 035 * Basic implementation of the {@link HttpResponse} interface. 036 * 037 * <p> 038 * Although this class implements the various setters defined on the {@link HttpResponse} interface, it's in general 039 * going to be more efficient to set the status/headers/content of this bean through the builder. 040 * 041 * <p> 042 * Immutability is expressed with the "funnel + nested {@code Unmodifiable} snapshot" paradigm: every mutator (fluent 043 * setter and {@code void} interface mutator) routes through the single protected {@link #modify(Runnable)} choke-point, 044 * and each concrete leaf gets a nested {@code X.Unmodifiable} whose only behavioral override is a throwing 045 * {@code modify(...)}. A leaf's {@link #unmodifiable()} factory returns a point-in-time snapshot of type 046 * {@code X.Unmodifiable}; any attempt to set a property on it throws an {@link UnsupportedOperationException}. 047 * 048 * <p> 049 * This is a self-typed (CRTP) root: a leaf declares <c>class Ok <jk>extends</jk> BasicHttpResponse<Ok></c> and 050 * inherits leaf-typed fluent setters with no covariant overrides. 051 * 052 * <h5 class='section'>Notes:</h5><ul> 053 * <li class='warn'>Beans are not thread safe unless they're marked as unmodifiable. 054 * </ul> 055 * 056 * <h5 class='section'>See Also:</h5><ul> 057 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauRestCommon">juneau-rest-common</a> 058 * </ul> 059 * 060 * @param <SELF> The self type for fluent setters. 061 */ 062@Marshalled(as=MarshalledAs.STRING) 063@SuppressWarnings({ 064 "deprecation", // Uses deprecated HttpMessage API 065 "java:S119", // 'SELF' (CRTP self-type) is intentional and clearer than a single-letter name. 066 "java:S1192" // Duplicated literals (argument/property names) read more clearly inline than as constants 067}) 068public abstract class BasicHttpResponse<SELF extends BasicHttpResponse<SELF>> implements HttpResponse { 069 070 BasicStatusLine statusLine = new BasicStatusLine(); 071 HeaderList headers = HeaderList.create(); 072 HttpEntity content; 073 074 /** 075 * Copy constructor. 076 * 077 * @param copyFrom The bean to copy from. Must not be <jk>null</jk>. 078 */ 079 protected BasicHttpResponse(BasicHttpResponse<?> copyFrom) { 080 statusLine = copyFrom.statusLine.copy(); 081 headers = copyFrom.headers.copy(); 082 content = copyFrom.content; 083 } 084 085 /** 086 * Constructor. 087 * 088 * @param statusLine The HTTP status line. Must not be <jk>null</jk>. 089 */ 090 protected BasicHttpResponse(BasicStatusLine statusLine) { 091 setStatusLine(statusLine.copy()); 092 } 093 094 /** 095 * Constructor. 096 * 097 * <p> 098 * This is the constructor used when parsing an HTTP response. 099 * 100 * @param response The HTTP response to copy from. Must not be <jk>null</jk>. 101 */ 102 protected BasicHttpResponse(HttpResponse response) { 103 reqnn("response", response); 104 setHeaders(response.getAllHeaders()); 105 setContent(response.getEntity()); 106 setStatusLine(response.getStatusLine()); 107 } 108 109 @Override /* Overridden from HttpMessage */ 110 public void addHeader(Header value) { 111 modify(() -> headers.append(value)); 112 } 113 114 @Override /* Overridden from HttpMessage */ 115 public void addHeader(String name, String value) { 116 modify(() -> headers.append(name, value)); 117 } 118 119 @Override /* Overridden from HttpMessage */ 120 public boolean containsHeader(String name) { 121 return headers.contains(name); 122 } 123 124 @Override /* Overridden from HttpMessage */ 125 public Header[] getAllHeaders() { return headers.getAll(); } 126 127 @Override /* Overridden from HttpMessage */ 128 public HttpEntity getEntity() { 129 // Constructing a StringEntity is somewhat expensive, so don't create it unless it's needed. 130 if (content == null) 131 content = stringEntity(getStatusLine().getReasonPhrase()); 132 return content; 133 } 134 135 @Override /* Overridden from HttpMessage */ 136 public Header getFirstHeader(String name) { 137 return headers.getFirst(name).orElse(null); 138 } 139 140 /** 141 * Returns access to the underlying builder for the headers. 142 * 143 * @return The underlying builder for the headers. 144 */ 145 public HeaderList getHeaders() { return headers; } 146 147 @Override /* Overridden from HttpMessage */ 148 public Header[] getHeaders(String name) { 149 return headers.getAll(name); 150 } 151 152 @Override /* Overridden from HttpMessage */ 153 public Header getLastHeader(String name) { 154 return headers.getLast(name).orElse(null); 155 } 156 157 @Override /* Overridden from HttpMessage */ 158 public Locale getLocale() { return statusLine.getLocale(); } 159 160 @Override /* Overridden from HttpMessage */ 161 public HttpParams getParams() { return null; } 162 163 @Override /* Overridden from HttpMessage */ 164 public ProtocolVersion getProtocolVersion() { return statusLine.getProtocolVersion(); } 165 166 @Override /* Overridden from HttpMessage */ 167 public StatusLine getStatusLine() { return statusLine; } 168 169 @Override /* Overridden from HttpMessage */ 170 public HeaderIterator headerIterator() { 171 return headers.headerIterator(); 172 } 173 174 @Override /* Overridden from HttpMessage */ 175 public HeaderIterator headerIterator(String name) { 176 return headers.headerIterator(name); 177 } 178 179 /** 180 * Returns <jk>true</jk> if this bean is unmodifiable. 181 * 182 * @return <jk>true</jk> if this bean is an {@link UnmodifiableBean} snapshot. 183 */ 184 public boolean isUnmodifiable() { return this instanceof UnmodifiableBean; } 185 186 @Override /* Overridden from HttpMessage */ 187 public void removeHeader(Header value) { 188 modify(() -> headers.remove(value)); 189 } 190 191 @Override /* Overridden from HttpMessage */ 192 public void removeHeaders(String name) { 193 modify(() -> headers.remove(name)); 194 } 195 196 /** 197 * Sets the body on this response. 198 * 199 * @param value The body on this response. Can be <jk>null</jk> to unset the property. 200 * @return This object. 201 */ 202 public SELF setContent(HttpEntity value) { 203 return modify(() -> content = value); 204 } 205 206 /** 207 * Sets the body on this response. 208 * 209 * @param value The body on this response. Can be <jk>null</jk> (treated as an empty string). 210 * @return This object. 211 */ 212 public SELF setContent(String value) { 213 return modify(() -> content = stringEntity(value)); 214 } 215 216 @Override /* Overridden from HttpMessage */ 217 public void setEntity(HttpEntity entity) { 218 modify(() -> content = entity); 219 } 220 221 @Override /* Overridden from HttpMessage */ 222 public void setHeader(Header value) { 223 modify(() -> headers.set(value)); 224 } 225 226 @Override /* Overridden from HttpMessage */ 227 public void setHeader(String name, String value) { 228 modify(() -> headers.set(name, value)); 229 } 230 231 /** 232 * Sets the specified header to the end of the headers in this builder. 233 * 234 * @param value The header to add. <jk>null</jk> values are ignored. 235 * @return This object. 236 */ 237 public SELF setHeader2(Header value) { 238 return modify(() -> headers.set(value)); 239 } 240 241 /** 242 * Sets the specified header to the end of the headers in this builder. 243 * 244 * @param name The header name. 245 * @param value The header value. 246 * @return This object. 247 */ 248 public SELF setHeader2(String name, String value) { 249 return modify(() -> headers.set(name, value)); 250 } 251 252 @Override /* Overridden from HttpMessage */ 253 public void setHeaders(Header[] values) { 254 modify(() -> headers.removeAll().append(values)); 255 } 256 257 /** 258 * Sets the specified headers on this response. 259 * 260 * @param value The new value. Must not be <jk>null</jk>. 261 * @return This object. 262 */ 263 public SELF setHeaders(HeaderList value) { 264 return modify(() -> headers = value.copy()); 265 } 266 267 /** 268 * Sets the specified headers to the end of the headers in this builder. 269 * 270 * @param values The headers to add. <jk>null</jk> values are ignored. 271 * @return This object. 272 */ 273 public SELF setHeaders(List<Header> values) { 274 return modify(() -> headers.set(values)); 275 } 276 277 /** 278 * Sets the specified headers to the end of the headers in this builder. 279 * 280 * @param values The headers to add. <jk>null</jk> values are ignored. 281 * @return This object. 282 */ 283 public SELF setHeaders2(Header...values) { 284 return modify(() -> headers.set(values)); 285 } 286 287 @Override /* Overridden from HttpMessage */ 288 public void setLocale(Locale loc) { 289 modify(() -> statusLine.setLocale(loc)); 290 } 291 292 /** 293 * Sets the locale used to retrieve reason phrases. 294 * 295 * <p> 296 * If not specified, uses {@link Locale#getDefault()}. 297 * 298 * @param value The new value. Can be <jk>null</jk>. The effect is implementation-dependent, depending on the configured {@link ReasonPhraseCatalog} (e.g. ignored by {@link EnglishReasonPhraseCatalog}). 299 * @return This object. 300 */ 301 public SELF setLocale2(Locale value) { 302 return modify(() -> statusLine.setLocale(value)); 303 } 304 305 /** 306 * Specifies the value for the <c>Location</c> header. 307 * 308 * @param value The new header location. Can be <jk>null</jk> (no-op; the header is left unchanged). 309 * @return This object. 310 */ 311 public SELF setLocation(String value) { 312 return modify(() -> headers.set(Location.of(value))); 313 } 314 315 /** 316 * Specifies the value for the <c>Location</c> header. 317 * 318 * @param value The new header location. Can be <jk>null</jk> (no-op; the header is left unchanged). 319 * @return This object. 320 */ 321 public SELF setLocation(URI value) { 322 return modify(() -> headers.set(Location.of(value))); 323 } 324 325 @Override /* Overridden from HttpMessage */ 326 public void setParams(HttpParams params) { 327 // Deprecated optional interface method; routed through the funnel so it is frozen on unmodifiable snapshots. 328 modify(() -> { /* No-op */ }); 329 } 330 331 /** 332 * Sets the protocol version on the status line. 333 * 334 * <p> 335 * If not specified, <js>"HTTP/1.1"</js> will be used. 336 * 337 * @param value The new value. Can be <jk>null</jk>, in which case the default protocol version is cleared and {@link BasicStatusLine#getProtocolVersion()} returns <jk>null</jk>. 338 * @return This object. 339 */ 340 public SELF setProtocolVersion(ProtocolVersion value) { 341 return modify(() -> statusLine.setProtocolVersion(value)); 342 } 343 344 @Override /* Overridden from HttpMessage */ 345 public void setReasonPhrase(String reason) throws IllegalStateException { 346 modify(() -> statusLine.setReasonPhrase(reason)); 347 } 348 349 /** 350 * Sets the reason phrase on the status line. 351 * 352 * <p> 353 * If not specified, the reason phrase will be retrieved from the reason phrase catalog 354 * using the locale on this builder. 355 * 356 * @param value The new value. Can be <jk>null</jk> to fall back to the reason phrase catalog. 357 * @return This object. 358 */ 359 public SELF setReasonPhrase2(String value) { 360 return modify(() -> statusLine.setReasonPhrase(value)); 361 } 362 363 /** 364 * Sets the reason phrase catalog used to retrieve reason phrases. 365 * 366 * <p> 367 * If not specified, uses {@link EnglishReasonPhraseCatalog}. 368 * 369 * @param value The new value. Can be <jk>null</jk> to fall back to {@link EnglishReasonPhraseCatalog#INSTANCE}. 370 * @return This object. 371 */ 372 public SELF setReasonPhraseCatalog(ReasonPhraseCatalog value) { 373 return modify(() -> statusLine.setReasonPhraseCatalog(value)); 374 } 375 376 @Override /* Overridden from HttpMessage */ 377 public void setStatusCode(int code) throws IllegalStateException { 378 modify(() -> statusLine.setStatusCode(code)); 379 } 380 381 /** 382 * Sets the status code on the status line. 383 * 384 * <p> 385 * If not specified, <c>0</c> will be used. 386 * 387 * @param value The new value. 388 * @return This object. 389 */ 390 public SELF setStatusCode2(int value) { 391 return modify(() -> statusLine.setStatusCode(value)); 392 } 393 394 /** 395 * Sets the protocol version on the status line. 396 * 397 * <p> 398 * If not specified, <js>"HTTP/1.1"</js> will be used. 399 * 400 * @param value The new value. Must not be <jk>null</jk>. 401 * @return This object. 402 */ 403 public SELF setStatusLine(BasicStatusLine value) { 404 return modify(() -> statusLine = value.copy()); 405 } 406 407 @Override /* Overridden from HttpMessage */ 408 public void setStatusLine(ProtocolVersion ver, int code) { 409 modify(() -> statusLine.setProtocolVersion(ver).setStatusCode(code)); 410 } 411 412 @Override /* Overridden from HttpMessage */ 413 public void setStatusLine(ProtocolVersion ver, int code, String reason) { 414 modify(() -> statusLine.setProtocolVersion(ver).setReasonPhrase(reason).setStatusCode(code)); 415 } 416 417 @Override /* Overridden from HttpMessage */ 418 public void setStatusLine(StatusLine value) { 419 modify(() -> statusLine.setProtocolVersion(value.getProtocolVersion()).setReasonPhrase(value.getReasonPhrase()).setStatusCode(value.getStatusCode())); 420 } 421 422 /** 423 * Returns an unmodifiable snapshot of this bean. 424 * 425 * <p> 426 * The returned instance is a point-in-time copy of the leaf's nested {@code Unmodifiable} type; any attempt to set a 427 * property on it throws an {@link UnsupportedOperationException}. This method is idempotent: if this bean is already 428 * unmodifiable it returns itself rather than taking another snapshot. The receiver is left unchanged (and still 429 * modifiable unless it was already unmodifiable). 430 * 431 * @return An unmodifiable snapshot of this bean, or this bean if it is already unmodifiable. 432 */ 433 public abstract SELF unmodifiable(); 434 435 @Override /* Overridden from Object */ 436 public String toString() { 437 var sb = new StringBuilder().append(statusLine).append(' ').append(headers); 438 if (nn(content)) 439 sb.append(' ').append(content); 440 return sb.toString(); 441 } 442 443 @Override /* Overridden from Object */ 444 public boolean equals(Object o) { 445 return o instanceof BasicHttpResponse<?> other && eq(this, other, (x, y) -> 446 eq(x.statusLine, y.statusLine) && eq(x.headers, y.headers) && eq(x.content, y.content)); 447 } 448 449 @Override /* Overridden from Object */ 450 public int hashCode() { 451 return h(statusLine, headers, content); 452 } 453 454 /** 455 * Returns this object cast to the self type. 456 * 457 * @return This object. 458 */ 459 @SuppressWarnings({ 460 "unchecked" // CRTP self-type cast is safe: SELF is bound to the concrete leaf type. 461 }) 462 protected final SELF self() { return (SELF) this; } 463 464 /** 465 * Single mutation funnel — the only choke-point through which all state changes on this bean flow. 466 * 467 * <p> 468 * Every mutator (fluent setter and {@code void} interface mutator) routes through this method. The nested 469 * {@code Unmodifiable} snapshot overrides only this method to throw, which freezes the entire mutation surface with 470 * a single override. 471 * 472 * @param mutation The state change to apply. 473 * @return This object. 474 */ 475 protected SELF modify(Runnable mutation) { 476 mutation.run(); 477 return self(); 478 } 479 480 /** 481 * Deep-freeze hook invoked from the nested {@code Unmodifiable} constructor after the snapshot copy completes. 482 * 483 * <p> 484 * The mutable sub-beans are frozen here by <b>direct field assignment</b> (never via a setter, which would route 485 * through the throwing {@link #modify(Runnable)} and blow up during construction). A leaf that adds its own mutable 486 * sub-bean overrides this method (calling {@code super.freeze()} first). 487 */ 488 protected void freeze() { 489 statusLine = statusLine.unmodifiable(); // DIRECT field write — must NOT use setStatusLine(...) 490 headers = headers.unmodifiable(); // DIRECT field write — snapshot the copied HeaderList. 491 } 492 493 /** 494 * Verifies that the specified HTTP response has the expected status code and returns it. 495 * 496 * @param response The HTTP response to check. Must not be <jk>null</jk>. 497 * @param expected The status code the response is required to have. 498 * @return The same response. 499 * @throws IllegalArgumentException If the response status code is not what was expected. 500 */ 501 static HttpResponse requireStatus(HttpResponse response, int expected) throws IllegalArgumentException { 502 return BasicHttpException.requireStatus(response, expected); 503 } 504 505 /** 506 * Asserts that the specified HTTP response has the same status code as the one on the status line of this bean. 507 * 508 * @param response The HTTP response to check. Must not be <jk>null</jk>. 509 * @throws IllegalArgumentException If status code is not what was expected. 510 */ 511 protected void assertStatusCode(HttpResponse response) throws IllegalArgumentException { 512 requireStatus(response, getStatusLine().getStatusCode()); 513 } 514}