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    * Constructor.
049    *
050    * @param msg The message.
051    */
052   // TODO - Remove in 8.0
053   public RestCallException(String msg) {
054      this(msg, new Object[0]);
055   }
056
057   /**
058    * Constructor.
059    *
060    * @param message The {@link MessageFormat}-style message.
061    * @param args Optional {@link MessageFormat}-style arguments.
062    */
063   public RestCallException(String message, Object...args) {
064      super(format(message, args));
065   }
066
067   /**
068    * Constructor.
069    *
070    * @param cause The cause of this exception.
071    * @param message The {@link MessageFormat}-style message.
072    * @param args Optional {@link MessageFormat}-style arguments.
073    */
074   public RestCallException(Throwable cause, String message, Object...args) {
075      this(getMessage(cause, message, null), args);
076      initCause(cause);
077   }
078
079   /**
080    * Constructor.
081    *
082    * @param e The inner cause of the exception.
083    */
084   public RestCallException(Exception e) {
085      super(e.getLocalizedMessage(), e);
086      if (e instanceof FileNotFoundException) {
087         responseCode = 404;
088      } else if (e.getMessage() != null) {
089         Pattern p = Pattern.compile("[^\\d](\\d{3})[^\\d]");
090         Matcher m = p.matcher(e.getMessage());
091         if (m.find())
092            responseCode = Integer.parseInt(m.group(1));
093      }
094      setStackTrace(e.getStackTrace());
095   }
096
097   /**
098    * Create an exception with a simple message and the status code and body of the specified response.
099    *
100    * @param msg The exception message.
101    * @param response The HTTP response object.
102    * @throws ParseException
103    * @throws IOException
104    */
105   public RestCallException(String msg, HttpResponse response) throws ParseException, IOException {
106      super(format("{0}\n{1}\nstatus=''{2}''\nResponse: \n{3}", msg, response.getStatusLine().getStatusCode(), EntityUtils.toString(response.getEntity(), UTF8)));
107   }
108
109   /**
110    * Constructor.
111    *
112    * @param responseCode The response code.
113    * @param responseMsg The response message.
114    * @param method The HTTP method (for message purposes).
115    * @param url The HTTP URL (for message purposes).
116    * @param response The response from the server.
117    */
118   public RestCallException(int responseCode, String responseMsg, String method, URI url, String response) {
119      super(format("HTTP method ''{0}'' call to ''{1}'' caused response code ''{2}, {3}''.\nResponse: \n{4}", method, url, responseCode, responseMsg, response));
120      this.responseCode = responseCode;
121      this.responseStatusMessage = responseMsg;
122      this.response = response;
123   }
124
125   /**
126    * Sets the server-side exception details.
127    *
128    * @param exceptionName The <code>Exception-Name:</code> header specifying the full name of the exception.
129    * @param exceptionMessage
130    *    The <code>Exception-Message:</code> header specifying the message returned by {@link Throwable#getMessage()}.
131    * @param exceptionTrace The stack trace of the exception returned by {@link Throwable#printStackTrace()}.
132    * @return This object (for method chaining).
133    */
134   protected RestCallException setServerException(Header exceptionName, Header exceptionMessage, Header exceptionTrace) {
135      if (exceptionName != null)
136         serverExceptionName = exceptionName.getValue();
137      if (exceptionMessage != null)
138         serverExceptionMessage = exceptionMessage.getValue();
139      if (exceptionTrace != null)
140         serverExceptionTrace = exceptionTrace.getValue();
141      return this;
142   }
143
144   /**
145    * Tries to reconstruct and re-throw the server-side exception.
146    *
147    * <p>
148    * The exception is based on the following HTTP response headers:
149    * <ul>
150    *    <li><code>Exception-Name:</code> - The full class name of the exception.
151    *    <li><code>Exception-Message:</code> - The message returned by {@link Throwable#getMessage()}.
152    *    <li><code>Exception-Trace:</code> - The stack trace of the exception returned by {@link Throwable#printStackTrace()}.
153    * </ul>
154    *
155    * <p>
156    * Does nothing if the server-side exception could not be reconstructed.
157    *
158    * <p>
159    * Currently only supports <code>Throwables</code> with either a public no-arg constructor
160    * or a public constructor that takes in a simple string message.
161    *
162    * @param cl The classloader to use to resolve the throwable class name.
163    * @param throwables The possible throwables.
164    * @throws Throwable If the throwable could be reconstructed.
165    */
166   protected void throwServerException(ClassLoader cl, Class<?>...throwables) throws Throwable {
167      if (serverExceptionName != null) {
168         for (Class<?> t : throwables)
169            if (t.getName().endsWith(serverExceptionName))
170               doThrow(t, serverExceptionMessage);
171         try {
172            Class<?> t = cl.loadClass(serverExceptionName);
173            if (isParentClass(RuntimeException.class, t) || isParentClass(Error.class, t))
174               doThrow(t, serverExceptionMessage);
175         } catch (ClassNotFoundException e2) { /* Ignore */ }
176      }
177   }
178
179   private void doThrow(Class<?> t, String msg) throws Throwable {
180      Constructor<?> c = null;
181      if (msg != null) {
182         c = findPublicConstructor(t, String.class);
183         if (c != null)
184            throw (Throwable)c.newInstance(msg);
185         c = findPublicConstructor(t, Object.class);
186         if (c != null)
187            throw (Throwable)c.newInstance(msg);
188      }
189      c = findPublicConstructor(t);
190      if (c != null)
191         throw (Throwable)c.newInstance();
192   }
193
194   /**
195    * Sets the HTTP response object that caused this exception.
196    *
197    * @param httpResponse The HTTP response object.
198    * @return This object (for method chaining).
199    */
200   protected RestCallException setHttpResponse(HttpResponse httpResponse) {
201      this.httpResponse = httpResponse;
202      return this;
203   }
204
205   /**
206    * Returns the HTTP response object that caused this exception.
207    *
208    * @return
209    *    The HTTP response object that caused this exception, or <jk>null</jk> if no response was created yet when the
210    *    exception was thrown.
211    */
212   public HttpResponse getHttpResponse() {
213      return this.httpResponse;
214   }
215
216   /**
217    * Returns the HTTP response status code.
218    *
219    * @return The response status code.  If a connection could not be made at all, returns <code>0</code>.
220    */
221   public int getResponseCode() {
222      return responseCode;
223   }
224
225   /**
226    * Returns the HTTP response message body text.
227    *
228    * @return The response message body text.
229    */
230   public String getResponseMessage() {
231      return response;
232   }
233
234   /**
235    * Returns the response status message as a plain string.
236    *
237    * @return The response status message.
238    */
239   public String getResponseStatusMessage() {
240      return responseStatusMessage;
241   }
242
243   /**
244    * Finds the message.
245    *
246    * @param cause The cause.
247    * @param msg The message.
248    * @param def The default value if both above are <jk>null</jk>.
249    * @return The resolved message.
250    */
251   protected static final String getMessage(Throwable cause, String msg, String def) {
252      if (msg != null)
253         return msg;
254      if (cause != null)
255         return cause.getMessage();
256      return def;
257   }
258
259   /**
260    * @deprecated Unused.
261    */
262   @Override
263   @Deprecated
264   public synchronized RestCallException initCause(Throwable cause) {
265      super.initCause(cause);
266      return this;
267   }
268}