
com.hazelcast.test.archunit.CompletableFutureUsageCondition Maven / Gradle / Ivy
The newest version!
/*
* Copyright (c) 2008-2024, Hazelcast, Inc. All Rights Reserved.
*
* 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 com.hazelcast.test.archunit;
import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.core.domain.JavaMember;
import com.tngtech.archunit.core.domain.JavaMethodCall;
import com.tngtech.archunit.core.domain.JavaType;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.lang.ArchCondition;
import com.tngtech.archunit.lang.ConditionEvents;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.Executor;
import java.util.stream.Stream;
import static com.tngtech.archunit.lang.SimpleConditionEvent.violated;
import static java.util.stream.Collectors.toSet;
/**
* Hazelcast internal callbacks shouldn't use the {@code ForkJoinPool#commonPool}, due to the risk of blocking
* Hazelcast progress by other misbehaving applications/libraries.
* The following rules should enforce to use of a separate executor for executing dependent stages in a Hazelcast code:
*
* - from {@link CompletionStage} create a list of methods that have an {@code Async} counterpart
*
- based on this list, filtering all their calls on the {@link CompletableFuture} {@code instanceof} objects
*
- checking that no non-Async methods versions are used
*
- checking that for Async methods the {@link Executor} service is specified
*
- skipping methods that override {@link CompletableFuture} base class methods
*
*/
public class CompletableFutureUsageCondition extends ArchCondition {
private static final Set COMPLETION_STAGE_METHODS = new ClassFileImporter().importClass(CompletionStage.class)
.getMethods().stream()
.map(JavaMember::getName)
.collect(toSet());
private static final Set SYNC_AND_ASYNC_METHODS = collectSyncAndAsyncCounterpartMethods();
//TODO Remove Java 8 compatibility code after JDK upgrade
static {
Collection excludedSyncMethodsForJava8Compatibility
= Arrays.asList("exceptionally");
SYNC_AND_ASYNC_METHODS.removeAll(excludedSyncMethodsForJava8Compatibility);
}
CompletableFutureUsageCondition() {
super("use only CompletableFuture async methods with explicit executor service");
}
private static Set collectSyncAndAsyncCounterpartMethods() {
return COMPLETION_STAGE_METHODS.stream()
.flatMap(method -> {
if (method.endsWith("Async")) {
String syncMethod = method.substring(0, method.lastIndexOf(("Async")));
return COMPLETION_STAGE_METHODS.contains(syncMethod) ? Stream.of(syncMethod, method) : Stream.of(method);
} else {
return Stream.empty();
}
})
.collect(toSet());
}
static ArchCondition super JavaClass> useExplicitExecutorServiceInCFAsyncMethods() {
return new CompletableFutureUsageCondition();
}
public void check(JavaClass item, ConditionEvents events) {
for (JavaMethodCall methodCalled : item.getMethodCallsFromSelf()) {
String calledMethodName = methodCalled.getTarget().getName();
if (isFromCompletionStage(methodCalled)
&& isAsyncMethod(calledMethodName)
&& isNotOverrideCompletableFutureMethod(methodCalled, item)) {
List parameterTypes = methodCalled.getTarget().getParameterTypes();
if (withoutExecutorArgument(parameterTypes)) {
String violatingMethodName = methodCalled.getOwner().getFullName();
events.add(violated(item, violatingMethodName + ":" + methodCalled.getLineNumber()
+ " calls CompletableFuture." + calledMethodName));
}
}
}
}
private boolean isFromCompletionStage(JavaMethodCall methodCalled) {
JavaClass calledClass = methodCalled.getTarget().getOwner();
return calledClass.isAssignableTo(CompletionStage.class);
}
private boolean isNotOverrideCompletableFutureMethod(JavaMethodCall methodCalled, JavaClass item) {
return !item.isAssignableTo(CompletableFuture.class)
|| !COMPLETION_STAGE_METHODS.contains(methodCalled.getOwner().getName());
}
private boolean isAsyncMethod(String methodName) {
return SYNC_AND_ASYNC_METHODS.contains(methodName);
}
private boolean withoutExecutorArgument(List parameterTypes) {
return parameterTypes.isEmpty()
|| !parameterTypes.get(parameterTypes.size() - 1).toErasure().isEquivalentTo(Executor.class);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy