001// ***************************************************************************************************************************
002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
003// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
005// * with the License.  You may obtain a copy of the License at                                                              *
006// *                                                                                                                         *
007// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
008// *                                                                                                                         *
009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
011// * specific language governing permissions and limitations under the License.                                              *
012// ***************************************************************************************************************************
013package org.apache.juneau.rest.client;
014
015import static java.lang.String.*;
016import static org.apache.juneau.internal.ClassUtils.*;
017import static org.apache.juneau.internal.StringUtils.*;
018import static org.apache.juneau.internal.IOUtils.*;
019import static org.apache.juneau.internal.StringUtils.format;
020
021import java.io.*;
022import java.lang.reflect.*;
023import java.net.*;
024import java.text.*;
025import java.util.regex.*;
026
027import org.apache.http.*;
028import org.apache.http.client.*;
029import org.apache.http.util.*;
030import org.apache.juneau.parser.ParseException;
031
032/**
033 * Exception representing a <code>400+</code> HTTP response code against a remote resource.
034 */
035public final class RestCallException extends IOException {
036
037   private static final long serialVersionUID = 1L;
038
039   private int responseCode;
040   private String response, responseStatusMessage;
041   HttpResponseException e;
042   private HttpResponse httpResponse;
043
044   @SuppressWarnings("unused")
045   private String serverExceptionName, serverExceptionMessage, serverExceptionTrace;
046
047
048   /**
049    * Constructor.
050    *
051    * @param message The {@link MessageFormat}-style message.
052    * @param args Optional {@link MessageFormat}-style arguments.
053    */
054   public RestCallException(String message, Object...args) {
055      super(format(message, args));
056   }
057
058   /**
059    * Constructor.
060    *
061    * @param cause The cause of this exception.
062    * @param message The {@link MessageFormat}-style message.
063    * @param args Optional {@link MessageFormat}-style arguments.
064    */
065   public RestCallException(Throwable cause, String message, Object...args) {
066      this(getMessage(cause, message, null), args);
067      initCause(cause);
068   }
069
070   /**
071    * Constructor.
072    *
073    * @param e The inner cause of the exception.
074    */
075   public RestCallException(Exception e) {
076      super(e.getLocalizedMessage(), e);
077      if (e instanceof FileNotFoundException) {
078         responseCode = 404;
079      } else if (e.getMessage() != null) {
080         Pattern p = Pattern.compile("[^\\d](\\d{3})[^\\d]");
081         Matcher m = p.matcher(e.getMessage());
082         if (m.find())
083            responseCode = Integer.parseInt(m.group(1));
084      }
085      setStackTrace(e.getStackTrace());
086   }
087
088   /**
089    * Create an exception with a simple message and the status code and body of the specified response.
090    *
091    * @param msg The exception message.
092    * @param response The HTTP response object.
093    * @throws ParseException
094    * @throws IOException
095    */
096   public RestCallException(String msg, HttpResponse response) throws ParseException, IOException {
097      super(format("{0}\n{1}\nstatus=''{2}''\nResponse: \n{3}", msg, response.getStatusLine().getStatusCode(), EntityUtils.toString(response.getEntity(), UTF8)));
098   }
099
100   /**
101    * Constructor.
102    *
103    * @param responseCode The response code.
104    * @param responseMsg The response message.
105    * @param method The HTTP method (for message purposes).
106    * @param url The HTTP URL (for message purposes).
107    * @param response The response from the server.
108    */
109   public RestCallException(int responseCode, String responseMsg, String method, URI url, String response) {
110      super(format("HTTP method ''{0}'' call to ''{1}'' caused response code ''{2}, {3}''.\nResponse: \n{4}", method, url, responseCode, responseMsg, response));
111      this.responseCode = responseCode;
112      this.responseStatusMessage = responseMsg;
113      this.response = response;
114   }
115
116   /**
117    * Sets the server-side exception details.
118    *
119    * @param exceptionName The <code>Exception-Name:</code> header specifying the full name of the exception.
120    * @param exceptionMessage
121    *    The <code>Exception-Message:</code> header specifying the message returned by {@link Throwable#getMessage()}.
122    * @param exceptionTrace The stack trace of the exception returned by {@link Throwable#printStackTrace()}.
123    * @return This object (for method chaining).
124    */
125   protected RestCallException setServerException(Header exceptionName, Header exceptionMessage, Header exceptionTrace) {
126      if (exceptionName != null)
127         serverExceptionName = exceptionName.getValue();
128      if (exceptionMessage != null)
129         serverExceptionMessage = exceptionMessage.getValue();
130      if (exceptionTrace != null)
131         serverExceptionTrace = exceptionTrace.getValue();
132      return this;
133   }
134
135   /**
136    * Tries to reconstruct and re-throw the server-side exception.
137    *
138    * <p>
139    * The exception is based on the following HTTP response headers:
140    * <ul>
141    *    <li><code>Exception-Name:</code> - The full class name of the exception.
142    *    <li><code>Exception-Message:</code> - The message returned by {@link Throwable#getMessage()}.
143    *    <li><code>Exception-Trace:</code> - The stack trace of the exception returned by {@link Throwable#printStackTrace()}.
144    * </ul>
145    *
146    * <p>
147    * Does nothing if the server-side exception could not be reconstructed.
148    *
149    * <p>
150    * Currently only supports <code>Throwables</code> with either a public no-arg constructor
151    * or a public constructor that takes in a simple string message.
152    *
153    * @param cl The classloader to use to resolve the throwable class name.
154    * @throws Throwable If the throwable could be reconstructed.
155    */
156   protected void throwServerException(ClassLoader cl) throws Throwable {
157      if (serverExceptionName != null) {
158         Throwable t = null;
159         try {
160            Class<?> exceptionClass = cl.loadClass(serverExceptionName);
161            Constructor<?> c = findPublicConstructor(exceptionClass, String.class);
162            if (c != null)
163               t = (Throwable)c.newInstance(serverExceptionMessage);
164            if (t == null) {
165               c = findPublicConstructor(exceptionClass);
166               if (c != null)
167                  t = (Throwable)c.newInstance();
168            }
169         } catch (Exception e2) {
170            e2.printStackTrace();
171         }
172         if (t != null)
173            throw t;
174      }
175   }
176
177   /**
178    * Sets the HTTP response object that caused this exception.
179    *
180    * @param httpResponse The HTTP response object.
181    * @return This object (for method chaining).
182    */
183   protected RestCallException setHttpResponse(HttpResponse httpResponse) {
184      this.httpResponse = httpResponse;
185      return this;
186   }
187
188   /**
189    * Returns the HTTP response object that caused this exception.
190    *
191    * @return
192    *    The HTTP response object that caused this exception, or <jk>null</jk> if no response was created yet when the
193    *    exception was thrown.
194    */
195   public HttpResponse getHttpResponse() {
196      return this.httpResponse;
197   }
198
199   /**
200    * Returns the HTTP response status code.
201    *
202    * @return The response status code.  If a connection could not be made at all, returns <code>0</code>.
203    */
204   public int getResponseCode() {
205      return responseCode;
206   }
207
208   /**
209    * Returns the HTTP response message body text.
210    *
211    * @return The response message body text.
212    */
213   public String getResponseMessage() {
214      return response;
215   }
216
217   /**
218    * Returns the response status message as a plain string.
219    *
220    * @return The response status message.
221    */
222   public String getResponseStatusMessage() {
223      return responseStatusMessage;
224   }
225
226   /**
227    * Finds the message.
228    *
229    * @param cause The cause.
230    * @param msg The message.
231    * @param def The default value if both above are <jk>null</jk>.
232    * @return The resolved message.
233    */
234   protected static final String getMessage(Throwable cause, String msg, String def) {
235      if (msg != null)
236         return msg;
237      if (cause != null)
238         return cause.getMessage();
239      return def;
240   }
241}