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