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