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.header;
018
019import static org.apache.juneau.commons.utils.Shorts.*;
020
021import java.util.*;
022import java.util.function.*;
023
024/**
025 * Represents an HTTP <c>Warning</c> header.
026 *
027 * <p>
028 * A general warning about possible problems with the entity body.
029 *
030 * <h5 class='section'>See Also:</h5><ul>
031 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/NextGenRestClient">Next-Generation REST Client</a>
032 * </ul>
033 *
034 * @since 9.2.1
035 */
036public class Warning extends HttpStringHeader {
037
038   /** The header name */
039   public static final String NAME = "Warning";
040
041   /**
042    * Constructor with an eager value.
043    *
044    * @param value The header value. May be <jk>null</jk>.
045    */
046   public Warning(String value) {
047      super(NAME, value);
048   }
049
050   /**
051    * Constructor with a lazy value supplier.
052    *
053    * @param valueSupplier Supplier for the header value. Must not be <jk>null</jk>.
054    */
055   public Warning(Supplier<String> valueSupplier) {
056      super(NAME, valueSupplier);
057   }
058
059   /**
060    * Static factory method with an eager value.
061    *
062    * @param value The header value. May be <jk>null</jk>.
063    * @return A new instance. Never <jk>null</jk>.
064    */
065   public static Warning of(String value) {
066      return new Warning(value);
067   }
068
069   /**
070    * Static factory method with a lazy value supplier.
071    *
072    * @param valueSupplier Supplier for the header value. Must not be <jk>null</jk>.
073    * @return A new instance. Never <jk>null</jk>.
074    */
075   public static Warning of(Supplier<String> valueSupplier) {
076      return new Warning(valueSupplier);
077   }
078
079   /**
080    * Creates a new empty {@link Builder}.
081    *
082    * @return A new builder. Never <jk>null</jk>.
083    */
084   public static Builder create() {
085      return new Builder();
086   }
087
088   //------------------------------------------------------------------------------------------------------------------
089   // Inner types
090   //------------------------------------------------------------------------------------------------------------------
091
092   /**
093    * Fluent builder for assembling an HTTP <c>Warning</c> header value.
094    *
095    * <p>
096    * Composes one or more warning-values defined by
097    * <a class='doclink' href='https://www.rfc-editor.org/rfc/rfc7234#section-5.5'>RFC 7234 ยง5.5</a> into the
098    * <c>warn-code SP warn-agent SP warn-text [SP warn-date]</c> form, with multiple warnings <c>,</c>-separated.  The
099    * 3-digit warn-code is zero-padded, the warn-text and optional warn-date are rendered as quoted-strings, and
100    * {@link #add(String)} is a generic escape hatch for a fully pre-rendered warning-value.
101    *
102    * <h5 class='section'>Example:</h5>
103    * <p class='bjava'>
104    *    String <jv>value</jv> = Warning.<jsm>create</jsm>()
105    *       .warning(110, <js>"anderson/1.3.37"</js>, <js>"Response is stale"</js>)
106    *       .build();
107    *    <jc>// =&gt; "110 anderson/1.3.37 \"Response is stale\""</jc>
108    * </p>
109    *
110    * <h5 class='section'>See Also:</h5><ul>
111    *    <li class='jc'>{@link Warning}
112    *    <li class='extlink'><a class='doclink' href='https://www.rfc-editor.org/rfc/rfc7234'>RFC 7234 - HTTP/1.1 Caching</a>
113    * </ul>
114    *
115    * @since 10.0.0
116    */
117   public static class Builder {
118
119      private final List<String> warnings = l();
120
121      /**
122       * Adds a warning-value (without a warn-date).
123       *
124       * @param code The 3-digit warn-code. Must be in the range 0-999.
125       * @param agent The warn-agent (host:port or pseudonym, e.g. <js>"-"</js>). Must not be <jk>null</jk> or blank.
126       * @param text The human-readable warn-text. Must not be <jk>null</jk>.
127       * @return This object.
128       * @throws IllegalArgumentException If {@code code} is out of range or {@code agent} is <jk>null</jk> or blank.
129       */
130      public Builder warning(int code, String agent, String text) {
131         return add(render(code, agent, text, null));
132      }
133
134      /**
135       * Adds a warning-value with a warn-date.
136       *
137       * @param code The 3-digit warn-code. Must be in the range 0-999.
138       * @param agent The warn-agent (host:port or pseudonym, e.g. <js>"-"</js>). Must not be <jk>null</jk> or blank.
139       * @param text The human-readable warn-text. Must not be <jk>null</jk>.
140       * @param date The warn-date (an HTTP-date). Must not be <jk>null</jk>.
141       * @return This object.
142       * @throws IllegalArgumentException If {@code code} is out of range or {@code agent}/{@code date} is invalid.
143       */
144      public Builder warning(int code, String agent, String text, String date) {
145         reqnn("date", date);
146         return add(render(code, agent, text, date));
147      }
148
149      /**
150       * Generic escape hatch for adding a fully pre-rendered warning-value verbatim.
151       *
152       * @param value The pre-rendered warning-value. Must not be <jk>null</jk> or blank.
153       * @return This object.
154       * @throws IllegalArgumentException If {@code value} is <jk>null</jk> or blank.
155       */
156      public Builder add(String value) {
157         reqnn("value", value);
158         var v = value.trim();
159         if (v.isEmpty())
160            throw iaex("warning value must not be blank");
161         warnings.add(v);
162         return this;
163      }
164
165      private static String render(int code, String agent, String text, String date) {
166         if (code < 0 || code > 999)
167            throw iaex("warn-code must be in the range 0-999: %s", code);
168         reqnn("agent", agent);
169         reqnn("text", text);
170         var a = agent.trim();
171         if (a.isEmpty())
172            throw iaex("warn-agent must not be blank");
173         var sb = new StringBuilder();
174         sb.append(String.format(Locale.ROOT, "%03d", code)).append(' ').append(a).append(' ').append(q(text));
175         if (date != null)
176            sb.append(' ').append(q(date));
177         return sb.toString();
178      }
179
180      private static String q(String value) {
181         var sb = new StringBuilder(value.length() + 2);
182         sb.append('"');
183         for (var i = 0; i < value.length(); i++) {
184            var c = value.charAt(i);
185            if (c == '\\' || c == '"')
186               sb.append('\\');
187            sb.append(c);
188         }
189         sb.append('"');
190         return sb.toString();
191      }
192
193      /**
194       * Builds the rendered <c>Warning</c> header value.
195       *
196       * <p>
197       * Returns an empty string when no warnings have been registered.
198       *
199       * @return The header value. Never <jk>null</jk>.
200       */
201      public String build() {
202         return String.join(", ", warnings);
203      }
204
205      /**
206       * Builds a {@link Warning} header bean directly from this builder.
207       *
208       * @return A {@link Warning} carrying {@link #build()}. Never <jk>null</jk>.
209       */
210      public Warning toHeader() {
211         return Warning.of(build());
212      }
213
214      @Override
215      public String toString() {
216         return build();
217      }
218   }
219}