/*
* Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com
* The software in this package is published under the terms of the CPAL v1.0
* license, a copy of which has been included with this distribution in the
* LICENSE.txt file.
*/
package org.mule.runtime.module.extension.internal.loader.enricher;
import static java.util.stream.Collectors.toList;
import static org.reflections.ReflectionUtils.getAllFields;
import static org.reflections.ReflectionUtils.withAnnotation;
import org.mule.runtime.api.meta.model.declaration.fluent.BaseDeclaration;
import org.mule.runtime.api.meta.model.declaration.fluent.ConfigurationDeclaration;
import org.mule.runtime.api.meta.model.declaration.fluent.ConnectionProviderDeclaration;
import org.mule.runtime.core.util.CollectionUtils;
import org.mule.runtime.extension.api.annotation.param.ConfigName;
import org.mule.runtime.extension.api.declaration.fluent.util.IdempotentDeclarationWalker;
import org.mule.runtime.extension.api.exception.IllegalConfigurationModelDefinitionException;
import org.mule.runtime.extension.api.loader.DeclarationEnricher;
import org.mule.runtime.extension.api.loader.ExtensionLoadingContext;
import org.mule.runtime.module.extension.internal.loader.java.property.ImplementingTypeModelProperty;
import org.mule.runtime.module.extension.internal.loader.java.property.RequireNameField;
import com.google.common.base.Joiner;
import java.lang.reflect.Field;
import java.util.Collection;
/**
* A {@link DeclarationEnricher} which looks for configurations with fields annotated with {@link ConfigName}.
* <p>
* It validates that the annotations is used properly and if so it adds a {@link RequireNameField} on the
* {@link ConfigurationDeclaration}.
* <p>
* If the {@link ConfigName} annotation is used in a way which breaks the rules set on its javadoc, an
* {@link IllegalConfigurationModelDefinitionException} is thrown
*
* @since 4.0
*/
public final class ConfigNameDeclarationEnricher implements DeclarationEnricher {
@Override
public void enrich(ExtensionLoadingContext extensionLoadingContext) {
new IdempotentDeclarationWalker() {
@Override
public void onConfiguration(ConfigurationDeclaration declaration) {
doEnrich(declaration);
}
@Override
protected void onConnectionProvider(ConnectionProviderDeclaration declaration) {
doEnrich(declaration);
}
}.walk(extensionLoadingContext.getExtensionDeclarer().getDeclaration());
}
private void doEnrich(BaseDeclaration declaration) {
declaration.getModelProperty(ImplementingTypeModelProperty.class).ifPresent(p -> {
ImplementingTypeModelProperty typeProperty = (ImplementingTypeModelProperty) p;
Collection<Field> fields = getAllFields(typeProperty.getType(), withAnnotation(ConfigName.class));
if (CollectionUtils.isEmpty(fields)) {
return;
}
if (fields.size() > 1) {
throw new IllegalConfigurationModelDefinitionException(String
.format("Only one configuration field is allowed to be annotated with @%s, but class '%s' has %d fields "
+ "with such annotation. Offending fields are: [%s]", ConfigName.class.getSimpleName(), typeProperty.getType()
.getName(), fields.size(), Joiner.on(", ").join(fields.stream().map(Field::getName).collect(toList()))));
}
final Field configNameField = fields.iterator().next();
if (!String.class.equals(configNameField.getType())) {
throw new IllegalConfigurationModelDefinitionException(String
.format("Config class '%s' declares the field '%s' which is annotated with @%s and is of type '%s'. Only "
+ "fields of type String are allowed to carry such annotation", typeProperty.getType().getName(),
configNameField.getName(), ConfigName.class.getSimpleName(), configNameField.getType().getName()));
}
declaration.addModelProperty(new RequireNameField(configNameField));
});
}
}