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&lt;Ok&gt;</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}