/*
* 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.validation;
import static java.lang.String.format;
import static org.mule.metadata.api.model.MetadataFormat.JAVA;
import static org.mule.metadata.api.utils.MetadataTypeUtils.getLocalPart;
import static org.mule.metadata.java.api.utils.JavaTypeUtils.getType;
import static org.mule.runtime.extension.api.util.ExtensionMetadataTypeUtils.isMap;
import static org.mule.runtime.module.extension.internal.util.IntrospectionUtils.isInstantiable;
import org.mule.metadata.api.TypeLoader;
import org.mule.metadata.api.annotation.TypeIdAnnotation;
import org.mule.metadata.api.model.ArrayType;
import org.mule.metadata.api.model.MetadataType;
import org.mule.metadata.api.model.ObjectFieldType;
import org.mule.metadata.api.model.ObjectType;
import org.mule.metadata.api.visitor.BasicTypeMetadataVisitor;
import org.mule.metadata.api.visitor.MetadataTypeVisitor;
import org.mule.runtime.api.meta.model.ExtensionModel;
import org.mule.runtime.api.meta.model.parameter.ParameterGroupModel;
import org.mule.runtime.api.meta.model.parameter.ParameterModel;
import org.mule.runtime.api.meta.model.parameter.ParameterizedModel;
import org.mule.runtime.api.meta.model.util.ExtensionWalker;
import org.mule.runtime.extension.api.annotation.param.NullSafe;
import org.mule.runtime.extension.api.declaration.type.ExtensionsTypeLoaderFactory;
import org.mule.runtime.extension.api.declaration.type.annotation.NullSafeTypeAnnotation;
import org.mule.runtime.extension.api.loader.ExtensionModelValidator;
import org.mule.runtime.extension.api.loader.Problem;
import org.mule.runtime.extension.api.loader.ProblemsReporter;
/**
* Validates that all fields of the {@link ParameterModel parameters} which are annotated with {@link NullSafe} honor that:
* <ul>
* <li>Both dictionaries and collections cannot specify a particular {@link NullSafe#defaultImplementingType()}</li>
* <li>{@link NullSafe#defaultImplementingType()} must be assignable to the field</li>
* <li>{@link NullSafe} cannot be used with basic types</li> reserved name.
* </ul>
*
* @since 4.0
*/
public final class NullSafeModelValidator implements ExtensionModelValidator {
private TypeLoader typeLoader = ExtensionsTypeLoaderFactory.getDefault().createTypeLoader();
@Override
public void validate(ExtensionModel extensionModel, ProblemsReporter problemsReporter) {
new ExtensionWalker() {
@Override
public void onParameter(ParameterizedModel owner, ParameterGroupModel groupModel, ParameterModel model) {
model.getType().accept(new MetadataTypeVisitor() {
@Override
public void visitObject(ObjectType objectType) {
if (objectType.getMetadataFormat().equals(JAVA) && !isMap(objectType)) {
objectType.getAnnotation(TypeIdAnnotation.class).map(TypeIdAnnotation::getValue)
.ifPresent(typeId -> typeLoader.load(typeId).ifPresent(fieldMetadataType -> objectType
.getFields().stream()
.filter(f -> f.getAnnotation(NullSafeTypeAnnotation.class).isPresent())
.forEach(f -> validateField(getLocalPart(f), f, getType(fieldMetadataType),
f.getAnnotation(NullSafeTypeAnnotation.class).get()))));
}
}
private void validateField(String fieldName, ObjectFieldType field, Class<?> declaringClass,
NullSafeTypeAnnotation nullSafeTypeAnnotation) {
Class<?> nullSafeType = nullSafeTypeAnnotation.getType();
Class<?> fieldType = getType(field.getValue());
boolean hasDefaultOverride = nullSafeTypeAnnotation.hasDefaultOverride();
field.getValue().accept(new BasicTypeMetadataVisitor() {
@Override
protected void visitBasicType(MetadataType metadataType) {
problemsReporter.addError(new Problem(extensionModel, format(
"Field '%s' in class '%s' is annotated with '@%s' but is of type '%s'. That annotation can only be "
+ "used with complex types (Pojos, Lists, Maps)",
fieldName, declaringClass.getName(),
NullSafe.class.getSimpleName(),
fieldType.getName())));
}
@Override
public void visitArrayType(ArrayType arrayType) {
if (hasDefaultOverride) {
problemsReporter.addError(
new Problem(extensionModel,
format("Field '%s' in class '%s' is annotated with '@%s' is of type '%s'"
+ " but a 'defaultImplementingType' was provided."
+ " Type override is not allowed for Collections",
fieldName,
declaringClass.getName(),
NullSafe.class.getSimpleName(),
fieldType.getName())));
}
}
@Override
public void visitObject(ObjectType objectType) {
if (objectType.isOpen()) {
if (hasDefaultOverride) {
problemsReporter.addError(
new Problem(extensionModel,
format("Field '%s' in class '%s' is annotated with '@%s' is of type '%s'"
+ " but a 'defaultImplementingType' was provided."
+ " Type override is not allowed for Maps",
fieldName,
declaringClass.getName(),
NullSafe.class.getSimpleName(),
fieldType.getName())));
}
return;
}
if (hasDefaultOverride && isInstantiable(fieldType)) {
problemsReporter.addError(new Problem(extensionModel, format(
"Field '%s' in class '%s' is annotated with '@%s' is of concrete type '%s',"
+ " but a 'defaultImplementingType' was provided."
+ " Type override is not allowed for concrete types",
fieldName,
declaringClass.getName(),
NullSafe.class.getSimpleName(),
fieldType.getName())));
}
if (!isInstantiable(nullSafeType)) {
problemsReporter.addError(new Problem(extensionModel, format(
"Field '%s' in class '%s' is annotated with '@%s' but is of type '%s'. That annotation can only be "
+ "used with complex instantiable types (Pojos, Lists, Maps)",
fieldName,
declaringClass.getName(),
NullSafe.class.getSimpleName(),
nullSafeType.getName())));
}
if (hasDefaultOverride && !fieldType.isAssignableFrom(nullSafeType)) {
problemsReporter.addError(new Problem(extensionModel, format(
"Field '%s' in class '%s' is annotated with '@%s' of type '%s', but provided type '%s"
+ " is not a subtype of the parameter's type",
fieldName,
declaringClass.getName(),
NullSafe.class.getSimpleName(),
fieldType.getName(),
nullSafeType.getName())));
}
}
});
}
});
}
}.walk(extensionModel);
}
}