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