use of io.quarkus.arc.deployment.ValidationPhaseBuildItem.ValidationErrorBuildItem in project quarkus by quarkusio.
the class ArcProcessor method registerSyntheticObservers.
// PHASE 3 - register synthetic observers
@BuildStep
public ObserverRegistrationPhaseBuildItem registerSyntheticObservers(BeanRegistrationPhaseBuildItem beanRegistrationPhase, List<BeanConfiguratorBuildItem> beanConfigurators, BuildProducer<ReflectiveMethodBuildItem> reflectiveMethods, BuildProducer<ReflectiveFieldBuildItem> reflectiveFields, BuildProducer<UnremovableBeanBuildItem> unremovableBeans, BuildProducer<ValidationPhaseBuildItem.ValidationErrorBuildItem> validationErrors) {
for (BeanConfiguratorBuildItem configurator : beanConfigurators) {
// Just make sure the configurator is processed
configurator.getValues().forEach(BeanConfigurator::done);
}
// Initialize the type -> bean map
beanRegistrationPhase.getBeanProcessor().getBeanDeployment().initBeanByTypeMap();
// Register a synthetic bean for each List<?> with qualifier @All
List<InjectionPointInfo> listAll = beanRegistrationPhase.getInjectionPoints().stream().filter(this::isListAllInjectionPoint).collect(Collectors.toList());
for (InjectionPointInfo injectionPoint : listAll) {
// Note that at this point we can be sure that the required type is List<>
Type typeParam = injectionPoint.getType().asParameterizedType().arguments().get(0);
if (typeParam.kind() == Type.Kind.WILDCARD_TYPE) {
ClassInfo declaringClass;
if (injectionPoint.isField()) {
declaringClass = injectionPoint.getTarget().asField().declaringClass();
} else {
declaringClass = injectionPoint.getTarget().asMethod().declaringClass();
}
if (declaringClass.classAnnotation(DotNames.KOTLIN_METADATA_ANNOTATION) != null) {
validationErrors.produce(new ValidationErrorBuildItem(new DefinitionException("kotlin.collections.List cannot be used together with the @All qualifier, please use MutableList or java.util.List instead: " + injectionPoint.getTargetInfo())));
} else {
validationErrors.produce(new ValidationErrorBuildItem(new DefinitionException("Wildcard is not a legal type argument for " + injectionPoint.getTargetInfo())));
}
} else if (typeParam.kind() == Type.Kind.TYPE_VARIABLE) {
validationErrors.produce(new ValidationErrorBuildItem(new DefinitionException("Type variable is not a legal type argument for " + injectionPoint.getTargetInfo())));
}
}
if (!listAll.isEmpty()) {
registerListInjectionPointsBeans(beanRegistrationPhase, listAll, reflectiveMethods, reflectiveFields, unremovableBeans);
}
BeanProcessor beanProcessor = beanRegistrationPhase.getBeanProcessor();
ObserverRegistrar.RegistrationContext registrationContext = beanProcessor.registerSyntheticObservers();
return new ObserverRegistrationPhaseBuildItem(registrationContext, beanProcessor);
}
use of io.quarkus.arc.deployment.ValidationPhaseBuildItem.ValidationErrorBuildItem in project quarkus by quarkusio.
the class WrongAnnotationUsageProcessor method detect.
@BuildStep
void detect(ArcConfig config, ApplicationIndexBuildItem applicationIndex, CustomScopeAnnotationsBuildItem scopeAnnotations, TransformedAnnotationsBuildItem transformedAnnotations, BuildProducer<ValidationErrorBuildItem> validationErrors) {
if (!config.detectWrongAnnotations) {
return;
}
IndexView index = applicationIndex.getIndex();
// Detect unsupported annotations
List<UnsupportedAnnotation> unsupported = new ArrayList<>();
Function<AnnotationInstance, String> singletonFun = new Function<AnnotationInstance, String>() {
@Override
public String apply(AnnotationInstance annotationInstance) {
return String.format("%s declared on %s, use @javax.inject.Singleton instead", annotationInstance.toString(false), getTargetInfo(annotationInstance));
}
};
unsupported.add(new UnsupportedAnnotation(DotName.createSimple("com.google.inject.Singleton"), singletonFun));
unsupported.add(new UnsupportedAnnotation(DotName.createSimple("javax.ejb.Singleton"), singletonFun));
unsupported.add(new UnsupportedAnnotation(DotName.createSimple("groovy.lang.Singleton"), singletonFun));
unsupported.add(new UnsupportedAnnotation(DotName.createSimple("jakarta.ejb.Singleton"), singletonFun));
Map<AnnotationInstance, String> wrongUsages = new HashMap<>();
for (UnsupportedAnnotation annotation : unsupported) {
for (AnnotationInstance annotationInstance : index.getAnnotations(annotation.name)) {
wrongUsages.put(annotationInstance, annotation.messageFun.apply(annotationInstance));
}
}
if (!wrongUsages.isEmpty()) {
for (Entry<AnnotationInstance, String> entry : wrongUsages.entrySet()) {
validationErrors.produce(new ValidationErrorBuildItem(new IllegalStateException(entry.getValue())));
}
}
// Note that a scope annotation is typically defined as @Target({TYPE, METHOD, FIELD}) but it's not forbidden to use ElementType.TYPE_USE
for (ClassInfo clazz : index.getKnownClasses()) {
NestingType nestingType = clazz.nestingType();
if (NestingType.ANONYMOUS == nestingType || NestingType.LOCAL == nestingType || (NestingType.INNER == nestingType && !Modifier.isStatic(clazz.flags()))) {
// Annotations declared on the class, incl. the annotations added via transformers
Collection<AnnotationInstance> classAnnotations = transformedAnnotations.getAnnotations(clazz);
if (classAnnotations.isEmpty() && clazz.annotations().isEmpty()) {
continue;
}
if (scopeAnnotations.isScopeIn(classAnnotations)) {
validationErrors.produce(new ValidationErrorBuildItem(new IllegalStateException(String.format("The %s class %s has a scope annotation but it must be ignored per the CDI rules", clazz.nestingType().toString(), clazz.name().toString()))));
} else if (clazz.annotations().containsKey(DotNames.OBSERVES)) {
validationErrors.produce(new ValidationErrorBuildItem(new IllegalStateException(String.format("The %s class %s declares an observer method but it must be ignored per the CDI rules", clazz.nestingType().toString(), clazz.name().toString()))));
} else if (clazz.annotations().containsKey(DotNames.PRODUCES)) {
validationErrors.produce(new ValidationErrorBuildItem(new IllegalStateException(String.format("The %s class %s declares a producer but it must be ignored per the CDI rules", clazz.nestingType().toString(), clazz.name().toString()))));
}
}
}
}
use of io.quarkus.arc.deployment.ValidationPhaseBuildItem.ValidationErrorBuildItem in project quarkus by quarkusio.
the class CacheProcessor method validateCacheAnnotationsAndProduceCacheNames.
@BuildStep
void validateCacheAnnotationsAndProduceCacheNames(CombinedIndexBuildItem combinedIndex, List<AdditionalCacheNameBuildItem> additionalCacheNames, BuildProducer<ValidationErrorBuildItem> validationErrors, BuildProducer<CacheNamesBuildItem> cacheNames) {
// Validation errors produced by this build step.
List<Throwable> throwables = new ArrayList<>();
// Cache names produced by this build step.
Set<String> names = new HashSet<>();
/*
* First, for each non-repeated cache interceptor binding:
* - its target is validated
* - the corresponding cache name is collected
*/
for (DotName bindingName : INTERCEPTOR_BINDINGS) {
for (AnnotationInstance binding : combinedIndex.getIndex().getAnnotations(bindingName)) {
throwables.addAll(validateInterceptorBindingTarget(binding, binding.target()));
if (binding.target().kind() == METHOD) {
/*
* Cache names from the interceptor bindings placed on cache interceptors must not be collected to prevent
* the instantiation of a cache with an empty name.
*/
names.add(binding.value(CACHE_NAME_PARAM).asString());
}
}
}
// The exact same things need to be done for repeated cache interceptor bindings.
for (DotName containerName : INTERCEPTOR_BINDING_CONTAINERS) {
for (AnnotationInstance container : combinedIndex.getIndex().getAnnotations(containerName)) {
for (AnnotationInstance binding : container.value("value").asNestedArray()) {
throwables.addAll(validateInterceptorBindingTarget(binding, container.target()));
names.add(binding.value(CACHE_NAME_PARAM).asString());
}
/*
* Interception from repeated interceptor bindings won't work with the CDI implementation from MicroProfile REST
* Client. Using repeated interceptor bindings on a method from a class annotated with @RegisterRestClient must
* therefore be forbidden.
*/
if (container.target().kind() == METHOD) {
MethodInfo methodInfo = container.target().asMethod();
if (methodInfo.declaringClass().classAnnotation(REGISTER_REST_CLIENT) != null) {
throwables.add(new UnsupportedRepeatedAnnotationException(methodInfo));
}
}
}
}
// Let's also collect the cache names from the @CacheName annotations.
for (AnnotationInstance qualifier : combinedIndex.getIndex().getAnnotations(CACHE_NAME)) {
// The @CacheName annotation from CacheProducer must be ignored.
if (qualifier.target().kind() == METHOD) {
/*
* This should only happen in CacheProducer. It'd be nice if we could forbid using @CacheName on a method in
* any other class, but Arc throws an AmbiguousResolutionException before we get a chance to validate things
* here.
*/
} else {
names.add(qualifier.value().asString());
}
}
// Finally, additional cache names provided by other extensions must be added to the cache names collection.
for (AdditionalCacheNameBuildItem additionalCacheName : additionalCacheNames) {
names.add(additionalCacheName.getName());
}
validationErrors.produce(new ValidationErrorBuildItem(throwables.toArray(new Throwable[0])));
cacheNames.produce(new CacheNamesBuildItem(names));
}
use of io.quarkus.arc.deployment.ValidationPhaseBuildItem.ValidationErrorBuildItem in project quarkus by quarkusio.
the class MailerProcessor method validateMailTemplates.
@BuildStep
void validateMailTemplates(List<TemplatePathBuildItem> templatePaths, ValidationPhaseBuildItem validationPhase, BuildProducer<ValidationErrorBuildItem> validationErrors) {
Set<String> filePaths = new HashSet<>();
for (TemplatePathBuildItem templatePath : templatePaths) {
String filePath = templatePath.getPath();
if (File.separatorChar != '/') {
filePath = filePath.replace(File.separatorChar, '/');
}
if (filePath.endsWith("html") || filePath.endsWith("htm") || filePath.endsWith("txt")) {
// For e-mails we only consider html and txt templates
filePaths.add(filePath);
int idx = filePath.lastIndexOf('.');
if (idx != -1) {
// Also add version without suffix from the path
// For example for "items.html" also add "items"
filePaths.add(filePath.substring(0, idx));
}
}
}
for (InjectionPointInfo injectionPoint : validationPhase.getContext().get(BuildExtension.Key.INJECTION_POINTS)) {
if (injectionPoint.getRequiredType().name().equals(MAIL_TEMPLATE)) {
AnnotationInstance resourcePath = injectionPoint.getRequiredQualifier(QuteProcessor.LOCATION);
String name;
if (resourcePath != null) {
name = resourcePath.value().asString();
} else if (injectionPoint.hasDefaultedQualifier()) {
name = QuteProcessor.getName(injectionPoint);
} else {
name = null;
}
if (name != null) {
// For "@ResourcePath("github/pulls") MailTemplate pulls" we try to match "github/pulls"
if (filePaths.stream().noneMatch(path -> path.endsWith(name))) {
validationErrors.produce(new ValidationErrorBuildItem(new IllegalStateException("No mail template found for " + injectionPoint.getTargetInfo())));
}
}
}
}
}
use of io.quarkus.arc.deployment.ValidationPhaseBuildItem.ValidationErrorBuildItem in project quarkus by quarkusio.
the class SchedulerProcessor method validateScheduledBusinessMethods.
@BuildStep
void validateScheduledBusinessMethods(SchedulerConfig config, List<ScheduledBusinessMethodItem> scheduledMethods, ValidationPhaseBuildItem validationPhase, BuildProducer<ValidationErrorBuildItem> validationErrors) {
List<Throwable> errors = new ArrayList<>();
Map<String, AnnotationInstance> encounteredIdentities = new HashMap<>();
for (ScheduledBusinessMethodItem scheduledMethod : scheduledMethods) {
MethodInfo method = scheduledMethod.getMethod();
if (Modifier.isAbstract(method.flags())) {
errors.add(new IllegalStateException("@Scheduled method must not be abstract: " + method.declaringClass().name() + "#" + method.name() + "()"));
continue;
}
if (Modifier.isPrivate(method.flags())) {
errors.add(new IllegalStateException("@Scheduled method must not be private: " + method.declaringClass().name() + "#" + method.name() + "()"));
continue;
}
boolean isSuspendMethod = KotlinUtil.isSuspendMethod(method);
// Validate method params and return type
List<Type> params = method.parameters();
int maxParamSize = isSuspendMethod ? 2 : 1;
if (params.size() > maxParamSize || (params.size() == maxParamSize && !params.get(0).equals(SCHEDULED_EXECUTION_TYPE))) {
errors.add(new IllegalStateException(String.format("Invalid scheduled business method parameters %s [method: %s, bean: %s]", params, method, scheduledMethod.getBean())));
}
if (!isValidReturnType(method)) {
if (isSuspendMethod) {
errors.add(new IllegalStateException(String.format("Suspending scheduled business method must return Unit [method: %s, bean: %s]", method, scheduledMethod.getBean())));
} else {
errors.add(new IllegalStateException(String.format("Scheduled business method must return void, CompletionStage<Void> or Uni<Void> [method: %s, bean: %s]", method, scheduledMethod.getBean())));
}
}
// Validate cron() and every() expressions
CronParser parser = new CronParser(CronDefinitionBuilder.instanceDefinitionFor(config.cronType));
for (AnnotationInstance scheduled : scheduledMethod.getSchedules()) {
Throwable error = validateScheduled(parser, scheduled, encounteredIdentities, validationPhase.getContext());
if (error != null) {
errors.add(error);
}
}
}
if (!errors.isEmpty()) {
validationErrors.produce(new ValidationErrorBuildItem(errors));
}
}
Aggregations