/** * Copyright 2017-2019 The Feign Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package feign.error; import feign.Request; import feign.Response; import feign.Types; import feign.codec.DecodeException; import feign.codec.Decoder; import java.io.IOException; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Type; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Map; import static feign.Util.checkState; class ExceptionGenerator { private static final Response TEST_RESPONSE; static { Map<String, Collection<String>> testHeaders = new HashMap<String, Collection<String>>(); testHeaders.put("TestHeader", Arrays.asList("header-value")); TEST_RESPONSE = Response.builder() .status(500) .body((Response.Body) null) .headers(testHeaders) .request(Request.create(Request.HttpMethod.GET, "http://test", testHeaders, Request.Body.empty(), null)) .build(); } private final Integer bodyIndex; private final Integer requestIndex; private final Integer headerMapIndex; private final Integer numOfParams; private final Type bodyType; private final Class<? extends Exception> exceptionType; private final Decoder bodyDecoder; ExceptionGenerator(Integer bodyIndex, Integer requestIndex, Integer headerMapIndex, Integer numOfParams, Type bodyType, Class<? extends Exception> exceptionType, Decoder bodyDecoder) { this.bodyIndex = bodyIndex; this.requestIndex = requestIndex; this.headerMapIndex = headerMapIndex; this.numOfParams = numOfParams; this.bodyType = bodyType; this.exceptionType = exceptionType; this.bodyDecoder = bodyDecoder; } Exception createException(Response response) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException { Class<?>[] paramClasses = new Class[numOfParams]; Object[] paramValues = new Object[numOfParams]; if (bodyIndex >= 0) { paramClasses[bodyIndex] = Types.getRawType(bodyType); paramValues[bodyIndex] = resolveBody(response); } if (requestIndex >= 0) { paramClasses[requestIndex] = Request.class; paramValues[requestIndex] = response.request(); } if (headerMapIndex >= 0) { paramValues[headerMapIndex] = response.headers(); paramClasses[headerMapIndex] = Map.class; } return exceptionType.getConstructor(paramClasses) .newInstance(paramValues); } Class<? extends Exception> getExceptionType() { return exceptionType; } private Object resolveBody(Response response) { if (bodyType instanceof Class<?> && ((Class<?>) bodyType).isInstance(response)) { return response; } try { return bodyDecoder.decode(response, bodyType); } catch (IOException e) { // How do we log this? return null; } catch (DecodeException e) { // How do we log this? return null; } } static class Builder { private Class<? extends Exception> exceptionType; private Decoder responseBodyDecoder; public Builder withExceptionType(Class<? extends Exception> exceptionType) { this.exceptionType = exceptionType; return this; } public Builder withResponseBodyDecoder(Decoder bodyDecoder) { this.responseBodyDecoder = bodyDecoder; return this; } public ExceptionGenerator build() { Constructor<? extends Exception> constructor = getConstructor(exceptionType); Type[] parameterTypes = constructor.getGenericParameterTypes(); Annotation[][] parametersAnnotations = constructor.getParameterAnnotations(); Integer bodyIndex = -1; Integer requestIndex = -1; Integer headerMapIndex = -1; Integer numOfParams = parameterTypes.length; Type bodyType = null; for (int i = 0; i < parameterTypes.length; i++) { Annotation[] paramAnnotations = parametersAnnotations[i]; boolean foundAnnotation = false; for (Annotation annotation : paramAnnotations) { if (annotation.annotationType().equals(ResponseHeaders.class)) { checkState(headerMapIndex == -1, "Cannot have two parameters tagged with @ResponseHeaders"); checkState(Types.getRawType(parameterTypes[i]).equals(Map.class), "Response Header map must be of type Map, but was %s", parameterTypes[i]); headerMapIndex = i; foundAnnotation = true; break; } } if (!foundAnnotation) { if (parameterTypes[i].equals(Request.class)) { checkState(requestIndex == -1, "Cannot have two parameters either without annotations or with object of type feign.Request"); requestIndex = i; } else { checkState(bodyIndex == -1, "Cannot have two parameters either without annotations or with @ResponseBody annotation"); bodyIndex = i; bodyType = parameterTypes[i]; } } } ExceptionGenerator generator = new ExceptionGenerator( bodyIndex, requestIndex, headerMapIndex, numOfParams, bodyType, exceptionType, responseBodyDecoder); validateGeneratorCanBeUsedToGenerateExceptions(generator); return generator; } private void validateGeneratorCanBeUsedToGenerateExceptions(ExceptionGenerator generator) { try { generator.createException(TEST_RESPONSE); } catch (Exception e) { throw new IllegalStateException( "Cannot generate exception - check constructor parameter types (are headers Map<String,Collection<String>> or is something causing an exception on construction?)", e); } } private Constructor<? extends Exception> getConstructor(Class<? extends Exception> exceptionClass) { Constructor<? extends Exception> preferredConstructor = null; for (Constructor<?> constructor : exceptionClass.getConstructors()) { FeignExceptionConstructor exceptionConstructor = constructor.getAnnotation(FeignExceptionConstructor.class); if (exceptionConstructor == null) { continue; } Class<?>[] parameterTypes = constructor.getParameterTypes(); if (parameterTypes.length == 0) { continue; } if (preferredConstructor == null) { preferredConstructor = (Constructor<? extends Exception>) constructor; } else { throw new IllegalStateException( "Too many constructors marked with @FeignExceptionConstructor"); } } if (preferredConstructor == null) { try { return exceptionClass.getConstructor(); } catch (NoSuchMethodException e) { throw new IllegalStateException( "Cannot find any suitable constructor in class [" + exceptionClass.getName() + "] - did you forget to mark one with @FeignExceptionConstructor or at least have a public default constructor?", e); } } return preferredConstructor; } } }