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.rest.client;
018
019import static org.apache.juneau.commons.utils.ThrowableUtils.*;
020import static org.apache.juneau.commons.utils.Utils.*;
021
022import java.text.*;
023
024import org.apache.http.*;
025import org.apache.juneau.http.header.*;
026
027/**
028 * Exception representing a <c>400+</c> HTTP response code against a remote resource or other exception.
029 *
030 * <h5 class='section'>See Also:</h5><ul>
031 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauRestClientBasics">juneau-rest-client Basics</a>
032 * </ul>
033 *
034 * @serial exclude
035 */
036public class RestCallException extends HttpException {
037
038   private static final long serialVersionUID = 1L;
039
040   // HttpException has a bug involving ASCII control characters so just replace them with spaces.
041   private static String clean(String message) {
042      message = emptyIfNull(message);
043
044      boolean needsCleaning = false;
045      for (var i = 0; i < message.length() && ! needsCleaning; i++)
046         if (message.charAt(i) < 32)
047            needsCleaning = true;
048
049      if (! needsCleaning)
050         return message;
051
052      var sb = new StringBuilder(message.length());
053      for (var i = 0; i < message.length(); i++) {
054         var c = message.charAt(i);
055         sb.append(c < 32 ? ' ' : c);
056      }
057
058      return sb.toString();
059   }
060
061   private static String format(String msg, Object...args) {
062      if (args.length == 0)
063         return clean(msg);
064      return clean(f(msg, args));
065   }
066
067   private final int statusCode;
068
069   private final Thrown thrown;
070
071   /**
072    * Constructor.
073    *
074    * @param statusCode The HTTP response status code.  Use <c>0</c> if no connection could be made.
075    * @param thrown The value of the <js>"Thrown"</js> header on the response.  Can be <jk>null</jk>.
076    * @param cause The cause of this exception.
077    * @param message The {@link MessageFormat}-style message.
078    * @param args Optional {@link MessageFormat}-style arguments.
079    */
080   public RestCallException(int statusCode, Thrown thrown, Throwable cause, String message, Object...args) {
081      super(format(message, args), cause);
082      this.statusCode = statusCode;
083      this.thrown = thrown;
084   }
085
086   /**
087    * Constructor.
088    *
089    * @param response The HTTP response.  Can be <jk>null</jk>.
090    * @param cause The cause of this exception.
091    * @param message The {@link MessageFormat}-style message.
092    * @param args Optional {@link MessageFormat}-style arguments.
093    */
094   public RestCallException(RestResponse response, Throwable cause, String message, Object...args) {
095      this((response == null ? 0 : response.getStatusCode()), (response == null ? Thrown.EMPTY : response.getHeader("Thrown").as(Thrown.class).orElse(null)), cause, message, args);
096   }
097
098   /**
099    * Similar to {@link #getCause()} but searches until it finds the throwable of the specified type.
100    *
101    * @param <T> The throwable type.
102    * @param c The throwable type.
103    * @return The cause of the specified type, or <jk>null</jk> of not found.
104    */
105   public <T extends Throwable> T getCause(Class<T> c) {
106      return getThrowableCause(c, this);
107   }
108
109   /**
110    * Returns the HTTP response status code.
111    *
112    * @return The response status code.  If a connection could not be made at all, returns <c>0</c>.
113    */
114   public int getResponseCode() { return statusCode; }
115
116   /**
117    * Returns the value of the <js>"Thrown"</js> header on the response.
118    *
119    * @return The value of the <js>"Thrown"</js> header on the response, never <jk>null</jk>.
120    */
121   public Thrown getThrown() { return thrown; }
122}