@caseylucas, here is the error handler that I eventually implemented to deal with this:
Code:
import com.acme.api.ApiException;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.security.oauth2.client.http.OAuth2ErrorHandler;
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import java.io.IOException;
import java.util.Map;
public class ErrorResponseHandler extends OAuth2ErrorHandler {
private static final Log log = LogFactory.getLog(ErrorResponseHandler.class);
public ErrorResponseHandler(OAuth2ProtectedResourceDetails resource) {
super(resource);
}
@Override
public boolean hasError(ClientHttpResponse response) throws IOException {
switch (response.getStatusCode()) {
case OK:
return false;
case NOT_MODIFIED:
return false;
case SEE_OTHER:
return false;
default:
return true;
}
}
@Override
public void handleError(ClientHttpResponse response) throws IOException {
try {
super.handleError(response);
} catch ( OAuth2Exception e ) {
Map<String,String> additionalInformation = e.getAdditionalInformation();
if ( additionalInformation == null ) throw e;
String msg = additionalInformation.get("error_message");
String type = additionalInformation.get("error_type");
String code = additionalInformation.get("error_code");
RuntimeException error;
try {
if ( StringUtils.isBlank(type) ) {
if ( !StringUtils.isBlank(msg) ) {
throw new IOException(msg);
} else {
throw e;
}
} else {
error = extractException(type, msg, code, response.getStatusCode().value());
}
} catch ( RuntimeException ex ) {
throw ex;
} catch ( Throwable t ) {
throw new RuntimeException("Failed to extract exception from client response", t);
}
if ( error != null )
throw error;
else
throw e;
}
}
protected RuntimeException extractException(String type, String message, String code, int httpStatus) {
Class errorType;
try {
errorType = Class.forName(type);
} catch (ClassNotFoundException e) {
log.error("Failed to extract exception of type: " + type + " message: " + message , e);
throw new RuntimeException(message != null ? message : "Failed to extract exception of type: " + type + " message: " + message,e);
}
Throwable t;
if ( errorType.equals(ApiException.class)) {
t = new ApiException(message, code, httpStatus);
} else {
try {
t = (Throwable)errorType.getDeclaredConstructor(String.class).newInstance(message);
} catch (Throwable e) {
log.error("Failed to instantiate Throwable of type: " + type + " message: " + message, e );
throw new RuntimeException(message != null ? message : "Failed to instantiate Throwable of type: " + type + " message: " + message,e);
}
}
if ( !RuntimeException.class.isAssignableFrom(t.getClass()) ) {
throw new RuntimeException("Unhandled error type: " + type + " message: " + message, t );
}
return (RuntimeException)t;
}
}
It basically catches the OAuth2Exception and interrogates it for additionalInformation. If there is none, it throws the exception. If there is additional information, it uses it to create and throw a new exception. This requires the internal api to always return json objects with the correct properties (error_message, error_type, error_code), which I do with a global exception handler. There is also a pretty tight coupling between the client and the service, in that the exception class specified in the error_type should be some type that the client knows about.
Not ideal, but it is working for us.