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.header;
018
019import static org.apache.juneau.commons.utils.CollectionUtils.*;
020import static org.apache.juneau.commons.utils.Shorts.*;
021import static org.apache.juneau.commons.utils.StringUtils.*;
022
023import java.util.*;
024
025import org.apache.juneau.commons.utils.*;
026import org.apache.juneau.http.*;
027
028/**
029 * Represents a parsed <l>Thrown</l> HTTP response header.
030 *
031 * <p>
032 * Contains exception information including name and optionally a message.
033 *
034 * <h5 class='figure'>Example</h5>
035 * <p class='bcode'>
036 *    Thrown: org.apache.juneau.http.classic.response.NotFound;Resource was not found
037 * </p>
038 *
039 * <p>
040 * This header isn't part of the RFC2616 specification, but is provided to allow for Java exception information
041 * to be delivered to remote REST Java interfaces.
042 *
043 * <p>
044 * This header supports comma-delimited values for multiple thrown values.
045 * <p class='bcode'>
046 *    Thrown: org.apache.juneau.http.classic.response.NotFound;Resource was not found,RuntimeException;foo
047 * </p>
048 *
049 * <p>
050 * Note that this is equivalent to specifying multiple header values.
051 * <p class='bcode'>
052 *    Thrown: org.apache.juneau.http.classic.response.NotFound;Resource was not found
053 *    Thrown: RuntimeException;foo
054 * </p>
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 * @serial exclude
061 */
062@Header("Thrown")
063@SuppressWarnings({
064   "java:S2160", // equals() inherited from BasicHeader compares name+value; no additional equality-relevant fields
065   "java:S9149" // Public Juneau DSL factory; hiding parent of()/create() is intentional.
066})
067public class Thrown extends BasicCsvHeader {
068
069   /**
070    * Represents a single entry in this header.
071    */
072   public static class Part {
073
074      String className;
075      String message;
076      String value;
077
078      /**
079       * Constructor.
080       *
081       * @param value The header part value.
082      */
083      public Part(String value) {
084         this.value = value;
085         var i = value.indexOf(';');
086         this.className = urlDecode(i == -1 ? value.trim() : value.substring(0, i).trim());
087         this.message = urlDecode(i == -1 ? null : value.substring(i + 1).trim());
088      }
089
090      /**
091       * Constructor.
092       *
093       * @param value The throwable to create the header part value from.
094       */
095      public Part(Throwable value) {
096         this.className = UnmodifiableBean.logicalType(value).getName();
097         this.message = value.getMessage();
098         this.value = urlEncode(className) + ';' + urlEncode(value.getMessage());
099      }
100
101      /**
102       * Returns the message portion of the header.
103       *
104       * @return The message portion of the header, or <jk>null</jk> if not there.
105       */
106      public String getClassName() { return className; }
107
108      /**
109       * Returns the message portion of the header.
110       *
111       * @return The message portion of the header, or <jk>null</jk> if not there.
112       */
113      public String getMessage() { return message; }
114
115      @Override /* Overridden from Object */
116      public String toString() {
117         return value;
118      }
119   }
120
121   private static final long serialVersionUID = 1L;
122
123   private static final String NAME = "Thrown";
124
125   /**
126    * An empty unmodifiable Thrown header.
127    */
128   public static final Thrown EMPTY = new Thrown((String)null);
129
130   /**
131    * Static creator.
132    *
133    * @param value
134    *    The header value.
135    *    <br>Can be <jk>null</jk>.
136    * @return A new header bean, or <jk>null</jk> if the value is <jk>null</jk>.
137    */
138   public static Thrown of(String value) {
139      return value == null ? null : new Thrown(value);
140   }
141
142   /**
143    * Static creator.
144    *
145    * @param values
146    *    The header value.
147    *    <br>Can be <jk>null</jk> or empty (treated as no thrown values).
148    * @return A new header bean, or <jk>null</jk> if the value is <jk>null</jk>.
149    */
150   public static Thrown of(Throwable...values) {
151      return new Thrown(l(values).stream().map(Part::new).toList());
152   }
153
154   /**
155    * Static creator.
156    *
157    * @param value
158    *    The header value.
159    *    <br>Can be <jk>null</jk> or empty to create a header with no value.
160    * @return A new header bean, or <jk>null</jk> if the value is <jk>null</jk>.
161    */
162   public static Thrown of(String...value) {
163      return value == null ? null : new Thrown(value);
164   }
165
166   /**
167    * Static creator for two CSV tokens.
168    *
169    * <p>
170    * This distinct fixed-arity overload ensures two literal {@link String} arguments resolve to this class's own
171    * factory (returning a {@link Thrown}) rather than the inherited fixed-arity {@link BasicCsvHeader#of(String,String)},
172    * which Java overload resolution would otherwise prefer.
173    *
174    * @param value1 The first CSV token.
175    * @param value2 The second CSV token.
176    * @return A new header bean.
177    */
178   public static Thrown of(String value1, String value2) {
179      return new Thrown(value1, value2);
180   }
181
182   private final transient List<Part> value;
183
184   /**
185    * Constructor.
186    *
187    * @param value
188    *    The header value.
189    */
190   public Thrown(List<Part> value) {
191      super(NAME, StringUtils.join(value, ", "));
192      this.value = u(value);
193   }
194
195   /**
196    * Constructor.
197    *
198    * @param value
199    *    The header value.
200    */
201   public Thrown(String value) {
202      super(NAME, value);
203      List<Part> l = list();
204      StringUtils.split(value, x -> l.add(new Part(x)));
205      this.value = value == null ? null : u(l);
206   }
207
208   /**
209    * Constructor.
210    *
211    * @param value
212    *    The header value.
213    *    <br>Can be <jk>null</jk> or empty to create a header with no value.
214    */
215   public Thrown(String...value) {
216      this(value == null ? null : join(value, ", "));
217   }
218
219   /**
220    * Returns the parts in this header.
221    *
222    * @return The parts in this header, or an empty {@link Optional} if the header value was <jk>null</jk>.
223    */
224   public Optional<List<Part>> asParts() {
225      return o(value);
226   }
227}