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.IOUtils.*;
018
019import java.io.*;
020import java.lang.reflect.*;
021import java.net.*;
022import java.util.regex.*;
023
024import org.apache.http.*;
025import org.apache.http.client.*;
026import org.apache.http.util.*;
027
028/**
029 * Exception representing a <code>400+</code> HTTP response code against a remote resource.
030 */
031public final class RestCallException extends IOException {
032
033   private static final long serialVersionUID = 1L;
034
035   private int responseCode;
036   private String response, responseStatusMessage;
037   HttpResponseException e;
038   private HttpResponse httpResponse;
039
040   @SuppressWarnings("unused")
041   private String serverExceptionName, serverExceptionMessage, serverExceptionTrace;
042
043
044   /**
045    * Constructor.
046    * 
047    * @param msg The exception message.
048    */
049   public RestCallException(String msg) {
050      super(msg);
051   }
052
053   /**
054    * Constructor.
055    * 
056    * @param e The inner cause of the exception.
057    */
058   public RestCallException(Exception e) {
059      super(e.getLocalizedMessage(), e);
060      if (e instanceof FileNotFoundException) {
061         responseCode = 404;
062      } else if (e.getMessage() != null) {
063         Pattern p = Pattern.compile("[^\\d](\\d{3})[^\\d]");
064         Matcher m = p.matcher(e.getMessage());
065         if (m.find())
066            responseCode = Integer.parseInt(m.group(1));
067      }
068      setStackTrace(e.getStackTrace());
069   }
070
071   /**
072    * Create an exception with a simple message and the status code and body of the specified response.
073    * 
074    * @param msg The exception message.
075    * @param response The HTTP response object.
076    * @throws ParseException
077    * @throws IOException
078    */
079   public RestCallException(String msg, HttpResponse response) throws ParseException, IOException {
080      super(format("%s%nstatus='%s'%nResponse: %n%s%n", msg, response.getStatusLine().getStatusCode(), EntityUtils.toString(response.getEntity(), UTF8)));
081   }
082
083   /**
084    * Constructor.
085    * 
086    * @param responseCode The response code.
087    * @param responseMsg The response message.
088    * @param method The HTTP method (for message purposes).
089    * @param url The HTTP URL (for message purposes).
090    * @param response The response from the server.
091    */
092   public RestCallException(int responseCode, String responseMsg, String method, URI url, String response) {
093      super(format("HTTP method '%s' call to '%s' caused response code '%s,%s'.%nResponse: %n%s%n", method, url, responseCode, responseMsg, response));
094      this.responseCode = responseCode;
095      this.responseStatusMessage = responseMsg;
096      this.response = response;
097   }
098
099   /**
100    * Sets the server-side exception details.
101    * 
102    * @param exceptionName The <code>Exception-Name:</code> header specifying the full name of the exception.
103    * @param exceptionMessage
104    *    The <code>Exception-Message:</code> header specifying the message returned by {@link Throwable#getMessage()}.
105    * @param exceptionTrace The stack trace of the exception returned by {@link Throwable#printStackTrace()}.
106    * @return This object (for method chaining).
107    */
108   protected RestCallException setServerException(Header exceptionName, Header exceptionMessage, Header exceptionTrace) {
109      if (exceptionName != null)
110         serverExceptionName = exceptionName.getValue();
111      if (exceptionMessage != null)
112         serverExceptionMessage = exceptionMessage.getValue();
113      if (exceptionTrace != null)
114         serverExceptionTrace = exceptionTrace.getValue();
115      return this;
116   }
117
118   /**
119    * Tries to reconstruct and re-throw the server-side exception.
120    * 
121    * <p>
122    * The exception is based on the following HTTP response headers:
123    * <ul>
124    *    <li><code>Exception-Name:</code> - The full class name of the exception.
125    *    <li><code>Exception-Message:</code> - The message returned by {@link Throwable#getMessage()}.
126    *    <li><code>Exception-Trace:</code> - The stack trace of the exception returned by {@link Throwable#printStackTrace()}.
127    * </ul>
128    * 
129    * <p>
130    * Does nothing if the server-side exception could not be reconstructed.
131    * 
132    * <p>
133    * Currently only supports <code>Throwables</code> with either a public no-arg constructor
134    * or a public constructor that takes in a simple string message.
135    * 
136    * @param cl The classloader to use to resolve the throwable class name.
137    * @throws Throwable If the throwable could be reconstructed.
138    */
139   protected void throwServerException(ClassLoader cl) throws Throwable {
140      if (serverExceptionName != null) {
141         Throwable t = null;
142         try {
143            Class<?> exceptionClass = cl.loadClass(serverExceptionName);
144            Constructor<?> c = findPublicConstructor(exceptionClass, String.class);
145            if (c != null)
146               t = (Throwable)c.newInstance(serverExceptionMessage);
147            if (t == null) {
148               c = findPublicConstructor(exceptionClass);
149               if (c != null)
150                  t = (Throwable)c.newInstance();
151            }
152         } catch (Exception e2) {
153            e2.printStackTrace();
154         }
155         if (t != null)
156            throw t;
157      }
158   }
159
160   /**
161    * Sets the HTTP response object that caused this exception.
162    * 
163    * @param httpResponse The HTTP response object.
164    * @return This object (for method chaining).
165    */
166   protected RestCallException setHttpResponse(HttpResponse httpResponse) {
167      this.httpResponse = httpResponse;
168      return this;
169   }
170
171   /**
172    * Returns the HTTP response object that caused this exception.
173    * 
174    * @return
175    *    The HTTP response object that caused this exception, or <jk>null</jk> if no response was created yet when the
176    *    exception was thrown.
177    */
178   public HttpResponse getHttpResponse() {
179      return this.httpResponse;
180   }
181
182   /**
183    * Returns the HTTP response status code.
184    * 
185    * @return The response status code.  If a connection could not be made at all, returns <code>0</code>.
186    */
187   public int getResponseCode() {
188      return responseCode;
189   }
190
191   /**
192    * Returns the HTTP response message body text.
193    * 
194    * @return The response message body text.
195    */
196   public String getResponseMessage() {
197      return response;
198   }
199
200   /**
201    * Returns the response status message as a plain string.
202    * 
203    * @return The response status message.
204    */
205   public String getResponseStatusMessage() {
206      return responseStatusMessage;
207   }
208
209   /**
210    * Sets the inner cause for this exception.
211    * 
212    * @param cause The inner cause.
213    * @return This object (for method chaining).
214    */
215   @Override /* Throwable */
216   public synchronized RestCallException initCause(Throwable cause) {
217      super.initCause(cause);
218      return this;
219   }
220}