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}