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}