Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for Compilation Time Expressions in Annotations #8954

Merged
merged 40 commits into from
Mar 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
f600839
compile time expressions impl
GavrilovSV Aug 19, 2022
4f949fb
compile time expressions checkstyle fix
GavrilovSV Feb 14, 2023
cdb8f41
added docs
GavrilovSV Feb 26, 2023
ec517ed
review improvements
GavrilovSV Mar 7, 2023
5122279
review improvements ч2
GavrilovSV Mar 9, 2023
b0c9179
docs improvements
GavrilovSV Mar 9, 2023
6686a2d
checkstyle fixes
GavrilovSV Mar 9, 2023
7652d53
Merge branch '4.0.x' into compile-time-expression-language
graemerocher Mar 12, 2023
7afe597
fix merge
graemerocher Mar 12, 2023
eb80c0a
Merge branch '4.0.x' into compile-time-expression-language
graemerocher Mar 16, 2023
3c69135
Merge branch '4.0.x' into compile-time-expression-language
graemerocher Mar 16, 2023
1fdbc7e
Cleanup / remove ExpressionEvaluationContext annotation
graemerocher Mar 17, 2023
c8941a6
support safe navigation operator
graemerocher Mar 17, 2023
dc6b3f5
fix Kotlin
graemerocher Mar 17, 2023
1fd8b3d
license headers
graemerocher Mar 17, 2023
885ccfd
Add basic implementation and smoke test for KSP
graemerocher Mar 17, 2023
7664dd8
support conditional job scheduling as an example using expressions
graemerocher Mar 20, 2023
87d9531
fix tests
graemerocher Mar 20, 2023
ee0a4d8
fix tests
graemerocher Mar 20, 2023
13ea9e1
don't require # before identifier references
graemerocher Mar 20, 2023
d8f2d00
Add list, map and array dereferencing
graemerocher Mar 21, 2023
bb994de
fix sonar bugs
graemerocher Mar 21, 2023
adad35d
improve documentation
graemerocher Mar 21, 2023
ada0086
fix tests
graemerocher Mar 21, 2023
d9477ae
add empty / not empty operator
graemerocher Mar 21, 2023
fea4f3e
improve subscript operator to allow expressions in index
graemerocher Mar 22, 2023
844b653
cleanup
graemerocher Mar 22, 2023
17e288a
add elvis operator
graemerocher Mar 22, 2023
ba3c5de
test: Test ObjectUtils::coerceToBoolean
sdelamo Mar 23, 2023
f503fd3
test: add missing groovy tests (#9000)
sdelamo Mar 23, 2023
94457db
add missing kotlin test and slightly more idiomatic kotlin code for d…
sdelamo Mar 23, 2023
57c336c
imp: use class:isInstance and class:cast (#8998)
sdelamo Mar 23, 2023
fc77bb8
consistency with Groovy
graemerocher Mar 24, 2023
8adf990
Merge branch 'compile-time-expressions-imp1' into compile-time-expres…
graemerocher Mar 24, 2023
be13e10
Fix test
graemerocher Mar 24, 2023
97ac14f
Apply suggestions from code review
graemerocher Mar 24, 2023
b215a75
rollback changes to JavaMethodElement
graemerocher Mar 24, 2023
811d1f5
what's new docs
graemerocher Mar 24, 2023
c1c7c55
Merge branch '4.0.x' into compile-time-expression-language
graemerocher Mar 24, 2023
69bdd51
fix test
graemerocher Mar 24, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import io.micronaut.core.type.Argument;
import io.micronaut.core.type.Executable;
import io.micronaut.inject.ExecutableMethod;
import io.micronaut.context.ContextConfigurable;
import io.micronaut.inject.qualifiers.InterceptorBindingQualifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -238,6 +239,9 @@ public <T> Interceptor<T, T>[] resolveConstructorInterceptors(
}

private static void instrumentAnnotationMetadata(BeanContext beanContext, Object method) {
if (method instanceof ContextConfigurable ctxConfigurable) {
ctxConfigurable.configure(beanContext);
}
if (beanContext instanceof ApplicationContext applicationContext && method instanceof EnvironmentConfigurable environmentConfigurable) {
// ensure metadata is environment aware
if (environmentConfigurable.hasPropertyExpressions()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import io.micronaut.core.util.ArrayUtils;
import io.micronaut.inject.BeanDefinition;
import io.micronaut.inject.ExecutableMethod;
import io.micronaut.inject.annotation.EvaluatedAnnotationMetadata;
import io.micronaut.inject.qualifiers.Qualifiers;

import java.lang.reflect.Method;
Expand Down Expand Up @@ -97,6 +98,14 @@ public MethodInterceptorChain(Interceptor<T, R>[] interceptors, T target, Execut
this.kind = null;
}

@Override
public AnnotationMetadata getAnnotationMetadata() {
if (executionHandle.getAnnotationMetadata() instanceof EvaluatedAnnotationMetadata eam) {
return eam.withArguments(originalParameters);
}
return executionHandle.getAnnotationMetadata();
}

@Override
@NonNull
public InterceptorKind getKind() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,14 @@
@Primary
public class DefaultTaskExceptionHandler implements TaskExceptionHandler<Object, Throwable> {

private static final Logger LOG = LoggerFactory.getLogger(DefaultTaskExceptionHandler.class);
static final Logger LOG = LoggerFactory.getLogger(DefaultTaskExceptionHandler.class);

@Override
public void handle(@Nullable Object bean, @NonNull Throwable throwable) {
if (LOG.isErrorEnabled()) {
StringBuilder message = new StringBuilder("Error invoking scheduled task ");
if (bean != null) {
message.append("for bean [").append(bean.toString()).append("] ");
message.append("for bean [").append(bean).append("] ");
}
message.append(throwable.getMessage());
LOG.error(message.toString(), throwable);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package io.micronaut.scheduling;

import io.micronaut.core.exceptions.BeanExceptionHandler;
import io.micronaut.inject.BeanDefinition;

/**
* An exception handler interface for task related exceptions.
Expand All @@ -26,4 +27,21 @@
* @param <E> The generic type of the exception
*/
public interface TaskExceptionHandler<T, E extends Throwable> extends BeanExceptionHandler<T, E> {

/**
* Handle an error that occurs during creation of the scheduled task.
* @param beanType The bean type
* @param throwable The throwable
* @since 4.0.0
*/
default void handleCreationFailure(BeanDefinition<T> beanType, E throwable) {
if (DefaultTaskExceptionHandler.LOG.isErrorEnabled()) {
StringBuilder message = new StringBuilder("Error creating scheduled task ");
if (beanType != null) {
message.append("for bean [").append(beanType.asArgument()).append("] ");
}
message.append(throwable.getMessage());
DefaultTaskExceptionHandler.LOG.error(message.toString(), throwable);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,13 @@
* {@link java.util.concurrent.ScheduledExecutorService} to use to schedule the task
*/
String scheduler() default TaskExecutors.SCHEDULED;

/**
* A custom expression that can be used to indicate whether the job should run.
* Will be evaluated each time the job is scheduled to run and if the condition evaluates to false the job will not run.
*
* @return The condition
* @since 4.0.0
*/
String condition() default "";
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,18 @@

import io.micronaut.context.ApplicationContext;
import io.micronaut.context.BeanContext;
import io.micronaut.context.Qualifier;
import io.micronaut.context.bind.DefaultExecutableBeanContextBinder;
import io.micronaut.context.bind.ExecutableBeanContextBinder;
import io.micronaut.context.processor.ExecutableMethodProcessor;
import io.micronaut.core.annotation.AnnotationUtil;
import io.micronaut.core.annotation.AnnotationValue;
import io.micronaut.core.bind.BoundExecutable;
import io.micronaut.core.convert.ConversionService;
import io.micronaut.core.type.Argument;
import io.micronaut.core.util.StringUtils;
import io.micronaut.inject.BeanDefinition;
import io.micronaut.inject.ExecutableMethod;
import io.micronaut.inject.annotation.EvaluatedAnnotationValue;
import io.micronaut.inject.qualifiers.Qualifiers;
import io.micronaut.scheduling.ScheduledExecutorTaskScheduler;
import io.micronaut.scheduling.TaskExceptionHandler;
Expand Down Expand Up @@ -64,6 +68,7 @@ public class ScheduledMethodProcessor implements ExecutableMethodProcessor<Sched
private static final String MEMBER_ZONE_ID = "zoneId";
private static final String MEMBER_FIXED_DELAY = "fixedDelay";
private static final String MEMBER_SCHEDULER = "scheduler";
private static final String MEMBER_CONDITION = "condition";

private final BeanContext beanContext;
private final ConversionService conversionService;
Expand Down Expand Up @@ -112,33 +117,28 @@ public void process(BeanDefinition<?> beanDefinition, ExecutableMethod<?, ?> met
}

TaskScheduler taskScheduler = optionalTaskScheduler.orElseThrow(() -> new SchedulerConfigurationException(method, "No scheduler of type TaskScheduler configured for name: " + scheduler));

Argument<Object> beanType = (Argument<Object>) beanDefinition.asArgument();
Qualifier<Object> declaredQualifier = (Qualifier<Object>) beanDefinition.getDeclaredQualifier();
Runnable task = () -> {
io.micronaut.context.Qualifier<Object> qualifer = beanDefinition
.getAnnotationTypeByStereotype(AnnotationUtil.QUALIFIER)
.map(type -> Qualifiers.byAnnotation(beanDefinition, type))
.orElse(null);

Class<Object> beanType = (Class<Object>) beanDefinition.getBeanType();
Object bean = null;
try {
bean = beanContext.getBean(beanType, qualifer);
if (method.getArguments().length == 0) {
((ExecutableMethod) method).invoke(bean);
ExecutableBeanContextBinder binder = new DefaultExecutableBeanContextBinder();
BoundExecutable<?, ?> boundExecutable = binder.bind(method, beanContext);
Object bean = beanContext.getBean(beanType, declaredQualifier);
AnnotationValue<Scheduled> finalAnnotationValue = scheduledAnnotation;
if (finalAnnotationValue instanceof EvaluatedAnnotationValue<Scheduled> evaluated) {
finalAnnotationValue = evaluated.withArguments(boundExecutable.getBoundArguments());
}
} catch (Throwable e) {
io.micronaut.context.Qualifier<TaskExceptionHandler> qualifier = Qualifiers.byTypeArguments(beanType, e.getClass());
Collection<BeanDefinition<TaskExceptionHandler>> definitions = beanContext.getBeanDefinitions(TaskExceptionHandler.class, qualifier);
Optional<BeanDefinition<TaskExceptionHandler>> mostSpecific = definitions.stream().filter(def -> {
List<Argument<?>> typeArguments = def.getTypeArguments(TaskExceptionHandler.class);
if (typeArguments.size() == 2) {
return typeArguments.get(0).getType() == beanType && typeArguments.get(1).getType() == e.getClass();
boolean shouldRun = finalAnnotationValue.booleanValue(MEMBER_CONDITION).orElse(true);
if (shouldRun) {
try {
((BoundExecutable<Object, Object>) boundExecutable).invoke(bean);
} catch (Throwable e) {
handleException(beanType.getType(), bean, e);
}
return false;
}).findFirst();

TaskExceptionHandler finalHandler = mostSpecific.map(bd -> beanContext.getBean(bd.getBeanType(), qualifier)).orElse(this.taskExceptionHandler);
finalHandler.handle(bean, e);
}
} catch (Exception e) {
TaskExceptionHandler finalHandler = findHandler(beanDefinition.getBeanType(), e);
finalHandler.handleCreationFailure(beanDefinition, e);
}
};

Expand Down Expand Up @@ -188,6 +188,26 @@ public void process(BeanDefinition<?> beanDefinition, ExecutableMethod<?, ?> met
}
}

private void handleException(Class<Object> beanType, Object bean, Throwable e) {
TaskExceptionHandler finalHandler = findHandler(beanType, e);
finalHandler.handle(bean, e);
}

private TaskExceptionHandler findHandler(Class<?> beanType, Throwable e) {
io.micronaut.context.Qualifier<TaskExceptionHandler> qualifier = Qualifiers.byTypeArguments(beanType, e.getClass());
Collection<BeanDefinition<TaskExceptionHandler>> definitions = beanContext.getBeanDefinitions(TaskExceptionHandler.class, qualifier);
Optional<BeanDefinition<TaskExceptionHandler>> mostSpecific = definitions.stream().filter(def -> {
List<Argument<?>> typeArguments = def.getTypeArguments(TaskExceptionHandler.class);
if (typeArguments.size() == 2) {
return typeArguments.get(0).getType() == beanType && typeArguments.get(1).getType() == e.getClass();
}
return false;
}).findFirst();

TaskExceptionHandler finalHandler = mostSpecific.map(bd -> beanContext.getBean(bd.getBeanType(), qualifier)).orElse(this.taskExceptionHandler);
return finalHandler;
}

@Override
@PreDestroy
public void close() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import io.micronaut.core.util.ArrayUtils;
import io.micronaut.core.util.Toggleable;
import io.micronaut.core.value.OptionalValues;
import io.micronaut.expressions.context.ExpressionWithContext;
import io.micronaut.inject.BeanDefinition;
import io.micronaut.inject.ExecutableMethod;
import io.micronaut.inject.ProxyBeanDefinition;
Expand Down Expand Up @@ -1301,6 +1302,11 @@ public AnnotationMetadata getAnnotationMetadata() {
return proxyBeanDefinitionWriter.getAnnotationMetadata();
}

@Override
public Set<ExpressionWithContext> getEvaluatedExpressions() {
return proxyBeanDefinitionWriter.getEvaluatedExpressions();
}

@Override
public void visitConfigBuilderField(ClassElement type, String field, AnnotationMetadata annotationMetadata, ConfigurationMetadataBuilder metadataBuilder, boolean isInterface) {
proxyBeanDefinitionWriter.visitConfigBuilderField(type, field, annotationMetadata, metadataBuilder, isInterface);
Expand Down Expand Up @@ -1613,12 +1619,12 @@ private void processAlreadyVisitedMethods(BeanDefinitionWriter parent) {
* Method Reference class with names and a list of argument types. Used as the targets.
*/
private static final class MethodRef {
int methodIndex;
private final String name;
private final List<ClassElement> argumentTypes;
private final List<ClassElement> genericArgumentTypes;
private final Type returnType;
private final List<String> rawTypes;
int methodIndex;

public MethodRef(String name, List<ParameterElement> parameterElements, Type returnType) {
this.name = name;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright 2017-2022 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.expressions;

/**
* Set of constants used for evaluated expressions processing.
*
* @since 4.0.0
* @author Sergey Gavrilov
*/
public class EvaluatedExpressionConstants {
/**
* Evaluated expression prefix.
*/
public static final String EXPRESSION_PREFIX = "#{";

/**
* RegEx pattern used to determine whether string value in
* annotation includes evaluated expression.
*/
public static final String EXPRESSION_PATTERN = ".*#\\{.*}.*";
}
Loading