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.common.utils.Utils.*;
020
021import java.text.*;
022
023import org.apache.http.*;
024import org.apache.juneau.common.utils.*;
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   private final int statusCode;
041   private final Thrown thrown;
042
043   /**
044    * Constructor.
045    *
046    * @param response The HTTP response.  Can be <jk>null</jk>.
047    * @param cause The cause of this exception.
048    * @param message The {@link MessageFormat}-style message.
049    * @param args Optional {@link MessageFormat}-style arguments.
050    */
051   public RestCallException(RestResponse response, Throwable cause, String message, Object...args) {
052      this(
053         (response == null ? 0 : response.getStatusCode()),
054         (response == null ? Thrown.EMPTY : response.getHeader("Thrown").as(Thrown.class).orElse(null)),
055         cause, message, args
056      );
057   }
058
059   /**
060    * Constructor.
061    *
062    * @param statusCode The HTTP response status code.  Use <c>0</c> if no connection could be made.
063    * @param thrown The value of the <js>"Thrown"</js> header on the response.  Can be <jk>null</jk>.
064    * @param cause The cause of this exception.
065    * @param message The {@link MessageFormat}-style message.
066    * @param args Optional {@link MessageFormat}-style arguments.
067    */
068   public RestCallException(int statusCode, Thrown thrown, Throwable cause, String message, Object...args) {
069      super(format(message,args),cause);
070      this.statusCode = statusCode;
071      this.thrown = thrown;
072   }
073
074   /**
075    * Returns the value of the <js>"Thrown"</js> header on the response.
076    *
077    * @return The value of the <js>"Thrown"</js> header on the response, never <jk>null</jk>.
078    */
079   public Thrown getThrown() {
080      return thrown;
081   }
082
083   /**
084    * Returns the HTTP response status code.
085    *
086    * @return The response status code.  If a connection could not be made at all, returns <c>0</c>.
087    */
088   public int getResponseCode() {
089      return statusCode;
090   }
091
092   /**
093    * Similar to {@link #getCause()} but searches until it finds the throwable of the specified type.
094    *
095    * @param <T> The throwable type.
096    * @param c The throwable type.
097    * @return The cause of the specified type, or <jk>null</jk> of not found.
098    */
099   public <T extends Throwable> T getCause(Class<T> c) {
100      return ThrowableUtils.getCause(c, this);
101   }
102
103   //------------------------------------------------------------------------------------------------------------------
104   // Helper methods
105   //------------------------------------------------------------------------------------------------------------------
106
107   private static String format(String msg, Object...args) {
108      if (args.length == 0)
109         return clean(msg);
110      return clean(StringUtils.format(msg, args));
111   }
112
113   // HttpException has a bug involving ASCII control characters so just replace them with spaces.
114   private static String clean(String message) {
115      message = emptyIfNull(message);
116
117      boolean needsCleaning = false;
118      for (int i = 0; i < message.length() && !needsCleaning; i++)
119         if (message.charAt(i) < 32)
120            needsCleaning = true;
121
122      if (!needsCleaning)
123         return message;
124
125      StringBuilder sb = new StringBuilder(message.length());
126      for (int i = 0; i < message.length(); i++) {
127         char c = message.charAt(i);
128         sb.append(c < 32 ? ' ' : c);
129      }
130
131      return sb.toString();
132   }
133}