Skip to content

Commit

Permalink
Add API to make it easier to write method interceptors and support Ko…
Browse files Browse the repository at this point in the history
…tlin suspend functions
  • Loading branch information
dstepanov committed Sep 18, 2020
1 parent 694199f commit 1b5b692
Show file tree
Hide file tree
Showing 25 changed files with 1,437 additions and 549 deletions.
9 changes: 9 additions & 0 deletions aop/build.gradle
@@ -1,7 +1,16 @@
plugins {
id "org.jetbrains.kotlin.jvm" version "1.3.72"
}

ext {
shadowJarEnabled = true
}
dependencies {
api project(':inject')
api project(':core')
compileOnly "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.8"
}

compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}
160 changes: 160 additions & 0 deletions aop/src/main/java/io/micronaut/aop/InterceptedMethod.java
@@ -0,0 +1,160 @@
/*
* Copyright 2017-2020 original 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
*
* https://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 io.micronaut.aop;

import io.micronaut.aop.internal.intercepted.InterceptedMethodUtil;
import io.micronaut.context.exceptions.ConfigurationException;
import io.micronaut.core.annotation.Experimental;
import io.micronaut.core.type.Argument;
import org.reactivestreams.Publisher;

import java.util.concurrent.CompletionStage;

/**
* The intercept method supporting intercepting different reactive invocations.
*
* @author Denis Stepanov
* @since 2.1.0
*/
@Experimental
public interface InterceptedMethod {

/**
* Creates a new instance of intercept method supporting intercepting different reactive invocations.
*
* @param context The {@link MethodInvocationContext}
* @return The {@link InterceptedMethod}
*/
static InterceptedMethod of(MethodInvocationContext<?, ?> context) {
return InterceptedMethodUtil.of(context);
}

/**
* Returns result type of the method.
*
* @return The {@link ResultType}
*/
ResultType resultType();

/**
* Returns result type value.
*
* @return The return type value.
*/
Argument<?> returnTypeValue();

/**
* Proceeds with invocation of {@link InvocationContext#proceed()} and converts result to appropriate type.
*
* @return The intercepted result
*/
Object interceptResult();


/**
* Proceeds with invocation of {@link InvocationContext#proceed(Interceptor)} and converts result to appropriate type.
*
* @param from The interceptor to start from
* @return The intercepted result
*/
Object interceptResult(Interceptor<?, ?> from);

/**
* Proceeds with invocation of {@link InvocationContext#proceed()} and converts result to {@link CompletionStage}.
*
* @return The intercepted result
*/
default CompletionStage<?> interceptResultAsCompletionStage() {
if (resultType() != ResultType.COMPLETION_STAGE) {
throw new ConfigurationException("Cannot return `CompletionStage` result from '" + resultType() + "' interceptor");
}
return (CompletionStage<?>) interceptResult();
}

/**
* Proceeds with invocation of {@link InvocationContext#proceed()} and converts result to {@link Publisher}.
*
* @return The intercepted result
*/
default Publisher<?> interceptResultAsPublisher() {
if (resultType() != ResultType.PUBLISHER) {
throw new ConfigurationException("Cannot return `Publisher` result from '" + resultType() + "' interceptor");
}
return (Publisher<?>) interceptResult();
}

/**
* Proceeds with invocation of {@link InvocationContext#proceed(Interceptor)} and converts result to {@link CompletionStage}.
*
* @param from The interceptor to start from
* @return The intercepted result
*/
default CompletionStage<?> interceptResultAsCompletionStage(Interceptor<?, ?> from) {
if (resultType() != ResultType.COMPLETION_STAGE) {
throw new ConfigurationException("Cannot return `CompletionStage` result from '" + resultType() + "' interceptor");
}
return (CompletionStage<?>) interceptResult(from);
}

/**
* Proceeds with invocation of {@link InvocationContext#proceed(Interceptor)} and converts result to {@link Publisher}.
*
* @param from The interceptor to start from
* @return The intercepted result
*/
default Publisher<?> interceptResultAsPublisher(Interceptor<?, ?> from) {
if (resultType() != ResultType.PUBLISHER) {
throw new ConfigurationException("Cannot return `Publisher` result from '" + resultType() + "' interceptor");
}
return (Publisher<?>) interceptResult(from);
}


/**
* Handle the value that should be the result of the invocation.
*
* @param result The result of the invocation
* @return The result of the invocation being returned from the interceptor
*/
Object handleResult(Object result);

/**
* Handle the exception that should be thrown out of the invocation.
*
* @param exception The exception
* @param <E> Sneaky throws helper
* @return The result of the invocation being returned from the interceptor
* @throws E The exception
*/
<E extends Throwable> Object handleException(Exception exception) throws E;

/**
* Indicated unsupported return type.
*
* @return The result of the invocation being returned from the interceptor
*/
default Object unsupported() {
throw new ConfigurationException("Cannot intercept method invocation, missing '" + resultType() + "' interceptor configured");
}

/**
* Possible result types.
*/
enum ResultType {
COMPLETION_STAGE, PUBLISHER, SYNCHRONOUS
}

}
@@ -0,0 +1,113 @@
/*
* Copyright 2017-2020 original 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
*
* https://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 io.micronaut.aop.internal.intercepted;

import io.micronaut.aop.InterceptedMethod;
import io.micronaut.aop.Interceptor;
import io.micronaut.aop.MethodInvocationContext;
import io.micronaut.core.annotation.Experimental;
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.convert.ConversionService;
import io.micronaut.core.type.Argument;
import io.micronaut.core.type.ReturnType;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.Future;

/**
* The {@link CompletionStage} method intercept.
*
* @author Denis Stepanov
* @since 2.1.0
*/
@Internal
@Experimental
class CompletionStageInterceptedMethod implements InterceptedMethod {
private final ConversionService<?> conversionService = ConversionService.SHARED;

private final MethodInvocationContext<?, ?> context;
private final Argument<?> returnTypeValue;

CompletionStageInterceptedMethod(MethodInvocationContext<?, ?> context) {
this.context = context;
this.returnTypeValue = context.getReturnType().asArgument().getFirstTypeVariable().orElse(Argument.OBJECT_ARGUMENT);
}

@Override
public ResultType resultType() {
return ResultType.COMPLETION_STAGE;
}

@Override
public Argument<?> returnTypeValue() {
return returnTypeValue;
}

@Override
public Object interceptResult() {
return interceptResultAsCompletionStage();
}

@Override
public Object interceptResult(Interceptor<?, ?> from) {
return interceptResultAsCompletionStage(from);
}

@Override
public CompletionStage<Object> interceptResultAsCompletionStage() {
return convertToCompletionStage(context.proceed());
}

@Override
public CompletionStage<Object> interceptResultAsCompletionStage(Interceptor<?, ?> from) {
return convertToCompletionStage(context.proceed(from));
}

@Override
public Object handleResult(Object result) {
if (result == null) {
result = CompletableFuture.completedFuture(null);
}
return convertCompletionStageResult(context.getReturnType(), result);
}

@Override
public <E extends Throwable> Object handleException(Exception exception) throws E {
CompletableFuture<Object> newFuture = new CompletableFuture<>();
newFuture.completeExceptionally(exception);
return convertCompletionStageResult(context.getReturnType(), newFuture);
}

private CompletionStage<Object> convertToCompletionStage(Object result) {
if (result instanceof CompletionStage) {
return (CompletionStage<Object>) result;
}
throw new IllegalStateException("Cannot convert " + result + " to 'java.util.concurrent.CompletionStage'");
}

private Object convertCompletionStageResult(ReturnType<?> returnType, Object result) {
Class<?> returnTypeClass = returnType.getType();
if (returnTypeClass.isInstance(result)) {
return result;
}
if (result instanceof CompletionStage && (returnTypeClass == CompletableFuture.class || returnTypeClass == Future.class)) {
return ((CompletionStage<?>) result).toCompletableFuture();
}
return conversionService.convert(result, returnType.asArgument())
.orElseThrow(() -> new IllegalStateException("Cannot convert completion stage result: " + result + " to '" + returnType.getType().getName() + "'"));
}
}
@@ -0,0 +1,56 @@
/*
* Copyright 2017-2020 original 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
*
* https://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 io.micronaut.aop.internal.intercepted;

import io.micronaut.aop.InterceptedMethod;
import io.micronaut.aop.MethodInvocationContext;
import io.micronaut.core.async.publisher.Publishers;
import io.micronaut.core.type.ReturnType;

import java.util.concurrent.CompletionStage;
import java.util.concurrent.Future;

/**
* The {@link InterceptedMethod} utils class.
*
* @author Denis Stepanov
* @since 2.1.0
*/
public class InterceptedMethodUtil {

/**
* Find possible {@link InterceptedMethod} implementation.
*
* @param context The {@link MethodInvocationContext}
* @return The {@link InterceptedMethod}
*/
public static InterceptedMethod of(MethodInvocationContext<?, ?> context) {
ReturnType<?> returnType = context.getReturnType();
Class<?> returnTypeClass = returnType.getType();
if (CompletionStage.class.isAssignableFrom(returnTypeClass) || Future.class.isAssignableFrom(returnTypeClass)) {
return new CompletionStageInterceptedMethod(context);
} else if (Publishers.isConvertibleToPublisher(returnTypeClass)) {
return new PublisherInterceptedMethod(context);
} else {
KotlinInterceptedMethod kotlinInterceptedMethod = KotlinInterceptedMethod.of(context);
if (kotlinInterceptedMethod != null) {
return kotlinInterceptedMethod;
}
return new SynchronousInterceptedMethod(context);
}
}

}

0 comments on commit 1b5b692

Please sign in to comment.