Skip to content

Commit

Permalink
Merge pull request #1341 from dmgcodevil/iss1328
Browse files Browse the repository at this point in the history
iss1328: fix
  • Loading branch information
mattrjacobs authored Sep 1, 2016
2 parents 12ad3e0 + 13b4cde commit b4d129e
Show file tree
Hide file tree
Showing 5 changed files with 599 additions and 35 deletions.
12 changes: 7 additions & 5 deletions hystrix-contrib/hystrix-javanica/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ check.dependsOn test, ajcTest

ext {
aspectjVersion = '1.8.6'
springframeworkVesion = '4.3.2.RELEASE'
}


Expand All @@ -104,12 +105,13 @@ dependencies {
compile 'org.apache.commons:commons-lang3:3.1'
compile 'com.google.code.findbugs:jsr305:2.0.0'
compile 'org.ow2.asm:asm:5.0.4'
testCompile 'junit:junit-dep:4.10'
testCompile group: 'junit', name: 'junit', version: '4.12'
testCompile 'pl.pragmatists:JUnitParams:1.0.5'
testCompile project(':hystrix-junit')
testCompile 'org.springframework:spring-core:4.0.0.RELEASE'
testCompile 'org.springframework:spring-context:4.0.0.RELEASE'
testCompile 'org.springframework:spring-aop:4.0.0.RELEASE'
testCompile 'org.springframework:spring-test:4.0.0.RELEASE'
testCompile "org.springframework:spring-core:$springframeworkVesion"
testCompile "org.springframework:spring-context:$springframeworkVesion"
testCompile "org.springframework:spring-aop:$springframeworkVesion"
testCompile "org.springframework:spring-test:$springframeworkVesion"
testCompile 'cglib:cglib:3.1'
testCompile 'org.mockito:mockito-all:1.9.5'
testCompile 'log4j:log4j:1.2.17'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
/**
* Copyright 2016 Netflix, Inc.
*
* <p>
* 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
*
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.
Expand All @@ -16,20 +16,34 @@
package com.netflix.hystrix.contrib.javanica.utils;


import com.google.common.base.Objects;
import com.google.common.base.Optional;
import com.google.common.base.Supplier;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.command.ExecutionType;
import com.netflix.hystrix.contrib.javanica.exception.FallbackDefinitionException;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import static com.netflix.hystrix.contrib.javanica.utils.TypeHelper.getAllParameterizedTypes;
import static com.netflix.hystrix.contrib.javanica.utils.TypeHelper.flattenTypeVariables;
import static com.netflix.hystrix.contrib.javanica.utils.TypeHelper.isGenericReturnType;
import static com.netflix.hystrix.contrib.javanica.utils.TypeHelper.isParametrizedType;
import static com.netflix.hystrix.contrib.javanica.utils.TypeHelper.isReturnTypeParametrized;
import static com.netflix.hystrix.contrib.javanica.utils.TypeHelper.isTypeVariable;
import static com.netflix.hystrix.contrib.javanica.utils.TypeHelper.isWildcardType;

public class FallbackMethod {

Expand Down Expand Up @@ -124,12 +138,26 @@ private Type getFirstParametrizedType(Method m) {
}

private void validateReturnType(Method commandMethod, Method fallbackMethod) {
if (isReturnTypeParametrized(commandMethod)) {
List<Type> commandParametrizedTypes = getParametrizedTypes(commandMethod);
List<Type> fallbackParametrizedTypes = getParametrizedTypes(fallbackMethod);
List<String> msg = equalsParametrizedTypes(commandParametrizedTypes, fallbackParametrizedTypes);
if (!msg.isEmpty()) {
throw new FallbackDefinitionException(createErrorMsg(commandMethod, method, StringUtils.join(msg, ", ")));
if (isGenericReturnType(commandMethod)) {
List<Type> commandParametrizedTypes = flattenTypeVariables(commandMethod.getGenericReturnType());
List<Type> fallbackParametrizedTypes = flattenTypeVariables(fallbackMethod.getGenericReturnType());
Result result = equalsParametrizedTypes(commandParametrizedTypes, fallbackParametrizedTypes);
if (!result.success) {
List<String> msg = new ArrayList<String>();
for (Error error : result.errors) {
Optional<Type> parentKindOpt = getParentKind(error.commandType, commandParametrizedTypes);
String extraHint = "";
if (parentKindOpt.isPresent()) {
Type parentKind = parentKindOpt.get();
if (isParametrizedType(parentKind)) {
extraHint = "--> " + ((ParameterizedType) parentKind).getRawType().toString() + "<Ooops!>\n";
}
}
msg.add(String.format(error.reason + "\n" + extraHint + "Command type literal pos: %s; Fallback type literal pos: %s",
positionAsString(error.commandType, commandParametrizedTypes),
positionAsString(error.fallbackType, fallbackParametrizedTypes)));
}
throw new FallbackDefinitionException(createErrorMsg(commandMethod, method, StringUtils.join(msg, "\n")));
}
}
validatePlainReturnType(commandMethod, fallbackMethod);
Expand All @@ -154,35 +182,227 @@ private void validateParametrizedType(Type commandReturnType, Type fallbackRetur
}

private String createErrorMsg(Method commandMethod, Method fallbackMethod, String hint) {
return "Incompatible return types. Command method: " + commandMethod + ", fallback method: " + fallbackMethod + ". "
+ (StringUtils.isNotBlank(hint) ? "Hint: " : "");
return "Incompatible return types. \nCommand method: " + commandMethod + ";\nFallback method: " + fallbackMethod + ";\n"
+ (StringUtils.isNotBlank(hint) ? "Hint: " + hint : "");
}

private List<Type> getParametrizedTypes(Method m) {
return getAllParameterizedTypes(m.getGenericReturnType());
}
private static final Result SUCCESS = Result.success();

private List<String> equalsParametrizedTypes(List<Type> commandParametrizedTypes, List<Type> fallbackParametrizedTypes) {
List<String> msg = Collections.emptyList();
private Result equalsParametrizedTypes(List<Type> commandParametrizedTypes, List<Type> fallbackParametrizedTypes) {
if (commandParametrizedTypes.size() != fallbackParametrizedTypes.size()) {
return Collections.singletonList("a different set of parametrized types, command: " + commandParametrizedTypes.size() +
" fallback: " + fallbackParametrizedTypes.size());
return Result.failure(Collections.singletonList(
new Error("Different size of types variables.\n" +
"Command type literals size = " + commandParametrizedTypes.size() + ": " + commandParametrizedTypes + "\n" +
"Fallback type literals size = " + fallbackParametrizedTypes.size() + ": " + fallbackParametrizedTypes + "\n"
)));
}

for (int i = 0; i < commandParametrizedTypes.size(); i++) {
Type commandParametrizedType = commandParametrizedTypes.get(i);
Type fallbackParametrizedType = fallbackParametrizedTypes.get(i);
if (!commandParametrizedType.equals(fallbackParametrizedType)) {
if (Collections.<String>emptyList() == msg) {
msg = new ArrayList<String>();
Result result = equals(commandParametrizedType, fallbackParametrizedType);
if (!result.success) return result;
}

return SUCCESS;
}

// Regular Type#equals method cannot be used to compare parametrized types and type variables
// because it compares generic declarations, see java.lang.reflect.GenericDeclaration.
// If generic declaration is an instance of java.lang.reflect.Method then command and fallback return types have with different generic declarations which aren't the same.
// In this case we need to compare only few type properties, such as bounds for type literal and row types for parametrized types.
private static Result equals(Type commandType, Type fallbackType) {
if (isParametrizedType(commandType) && isParametrizedType(fallbackType)) {
final ParameterizedType pt1 = (ParameterizedType) commandType;
final ParameterizedType pt2 = (ParameterizedType) fallbackType;
Result result = regularEquals(pt1.getRawType(), pt2.getRawType());
return result.andThen(new Supplier<Result>() {
@Override
public Result get() {
return FallbackMethod.equals(pt1.getActualTypeArguments(), pt2.getActualTypeArguments());
}
});
} else if (isTypeVariable(commandType) && isTypeVariable(fallbackType)) {
final TypeVariable tv1 = (TypeVariable) commandType;
final TypeVariable tv2 = (TypeVariable) fallbackType;
if (tv1.getGenericDeclaration() instanceof Method && tv2.getGenericDeclaration() instanceof Method) {
Result result = equals(tv1.getBounds(), tv2.getBounds());
return result.append(new Supplier<List<Error>>() {
@Override
public List<Error> get() {
return Collections.singletonList(boundsError(tv1, tv1.getBounds(), "", tv2, tv2.getBounds()));
}
});
}
return regularEquals(tv1, tv2);
} else if (isWildcardType(commandType) && isWildcardType(fallbackType)) {
final WildcardType wt1 = (WildcardType) commandType;
final WildcardType wt2 = (WildcardType) fallbackType;
Result result = equals(wt1.getLowerBounds(), wt2.getLowerBounds());
result = result.append(new Supplier<List<Error>>() {
@Override
public List<Error> get() {
return Collections.singletonList(boundsError(wt1, wt1.getLowerBounds(), "lower", wt2, wt2.getLowerBounds()));
}
});

if (result.isFailure()) return result;

result = equals(wt1.getUpperBounds(), wt2.getUpperBounds());
return result.append(new Supplier<List<Error>>() {
@Override
public List<Error> get() {
return Collections.singletonList(boundsError(wt1, wt1.getUpperBounds(), "upper", wt2, wt2.getUpperBounds()));
}
msg.add("wrong parametrized type. Expected: '" + commandParametrizedType + "' but in fallback '" +
fallbackParametrizedType + "', position: " + i);
return msg;
});
} else {
return regularEquals(commandType, fallbackType);
}
}

private static Result regularEquals(final Type commandType, final Type fallbackType) {
return Result.of(Objects.equal(commandType, fallbackType), new Supplier<List<Error>>() {
@Override
public List<Error> get() {
return Collections.singletonList(new Error(
commandType,
String.format("Different types. Command type: '%s'; fallback type: '%s'", commandType, fallbackType),
fallbackType));
}
});
}

private static Optional<Type> getParentKind(Type type, List<Type> types) {
int pos = position(type, types);
if (pos <= 0) return Optional.absent();
return Optional.of(types.get(pos - 1));
}

private static String positionAsString(Type type, List<Type> types) {
int pos = position(type, types);
if (pos < 0) {
return "unknown";
}
return String.valueOf(pos);
}

private static int position(Type type, List<Type> types) {
if (type == null) return -1;
if (CollectionUtils.isEmpty(types)) return -1;
return types.indexOf(type);
}

private static Error boundsError(Type t1, Type[] b1, String boundType, Type t2, Type[] b2) {
return new Error(t1,
String.format("Different %s bounds. Command bounds: '%s'; Fallback bounds: '%s'",
boundType,
StringUtils.join(b1, ", "),
StringUtils.join(b2, ", ")),
t2);
}

private static Result equals(Type[] t1, Type[] t2) {
if (t1 == null && t2 == null) return SUCCESS;
if (t1 == null) return Result.failure();
if (t2 == null) return Result.failure();
if (t1.length != t2.length)
return Result.failure(new Error(String.format("Different size of type literals. Command size = %d, fallback size = %d",
t1.length, t2.length)));
Result result = SUCCESS;
for (int i = 0; i < t1.length; i++) {
result = result.combine(equals(t1[i], t2[i]));
if (result.isFailure()) return result;
}
return result;
}

private static class Result {
boolean success;
List<Error> errors = Collections.emptyList();

boolean isSuccess() {
return success;
}

return msg;
boolean isFailure() {
return !success;
}

static Result of(boolean res, Supplier<List<Error>> errors) {
if (res) return success();
return failure(errors.get());
}

static Result success() {
return new Result(true);
}

static Result failure() {
return new Result(false);
}

static Result failure(Error... errors) {
return new Result(false, Arrays.asList(errors));
}

static Result failure(List<Error> errors) {
return new Result(false, errors);
}

Result combine(Result r) {
return new Result(this.success && r.success, merge(this.errors, r.errors));
}

Result andThen(Supplier<Result> resultSupplier) {
if (!success) return this;
return resultSupplier.get();
}

Result append(List<Error> errors) {
if (success) return this;
return failure(merge(this.errors, errors));
}

Result append(Supplier<List<Error>> errors) {
if (success) return this;
return append(errors.get());
}

static List<Error> merge(@Nonnull List<Error> e1, @Nonnull List<Error> e2) {
List<Error> res = new ArrayList<Error>(e1.size() + e2.size());
res.addAll(e1);
res.addAll(e2);
return Collections.unmodifiableList(res);
}

Result(boolean success, List<Error> errors) {
Validate.notNull(errors, "errors cannot be null");
this.success = success;
this.errors = errors;
}

Result(boolean success) {
this.success = success;
this.errors = Collections.emptyList();
}
}

private static class Error {
@Nullable
Type commandType;
String reason;
@Nullable
Type fallbackType;

Error(String reason) {
this.reason = reason;
}

Error(Type commandType, String reason, Type fallbackType) {
this.commandType = commandType;
this.reason = reason;
this.fallbackType = fallbackType;
}
}

}
Loading

0 comments on commit b4d129e

Please sign in to comment.