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