Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add API to make it easier to write method interceptors and support Ko…
…tlin suspend functions
- Loading branch information
Showing
25 changed files
with
1,437 additions
and
549 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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
160
aop/src/main/java/io/micronaut/aop/InterceptedMethod.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} | ||
|
||
} |
113 changes: 113 additions & 0 deletions
113
...src/main/java/io/micronaut/aop/internal/intercepted/CompletionStageInterceptedMethod.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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() + "'")); | ||
} | ||
} |
56 changes: 56 additions & 0 deletions
56
aop/src/main/java/io/micronaut/aop/internal/intercepted/InterceptedMethodUtil.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
} | ||
|
||
} |
Oops, something went wrong.