/* * 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.lang.Thread.currentThread; import static org.mule.runtime.api.meta.model.display.LayoutModel.builderFrom; import static org.mule.runtime.module.extension.internal.util.IntrospectionUtils.getAnnotatedElement; import org.mule.metadata.api.ClassTypeLoader; import org.mule.runtime.api.meta.model.declaration.fluent.BaseDeclaration; import org.mule.runtime.api.meta.model.declaration.fluent.ComponentDeclaration; import org.mule.runtime.api.meta.model.declaration.fluent.ExtensionDeclaration; import org.mule.runtime.api.meta.model.declaration.fluent.OperationDeclaration; import org.mule.runtime.api.meta.model.declaration.fluent.ParameterDeclaration; import org.mule.runtime.api.meta.model.declaration.fluent.ParameterDeclarer; import org.mule.runtime.api.meta.model.declaration.fluent.ParameterGroupDeclaration; import org.mule.runtime.api.meta.model.declaration.fluent.SourceDeclaration; import org.mule.runtime.api.meta.model.declaration.fluent.TypedDeclaration; import org.mule.runtime.core.internal.metadata.DefaultMetadataResolverFactory; import org.mule.runtime.core.internal.metadata.NullMetadataResolverFactory; import org.mule.runtime.extension.api.annotation.metadata.MetadataKeyId; import org.mule.runtime.extension.api.annotation.metadata.MetadataKeyPart; import org.mule.runtime.extension.api.annotation.metadata.MetadataScope; import org.mule.runtime.extension.api.annotation.param.Query; import org.mule.runtime.extension.api.declaration.fluent.util.IdempotentDeclarationWalker; import org.mule.runtime.extension.api.declaration.type.ExtensionsTypeLoaderFactory; import org.mule.runtime.extension.api.exception.IllegalParameterModelDefinitionException; import org.mule.runtime.extension.api.loader.DeclarationEnricher; import org.mule.runtime.extension.api.loader.ExtensionLoadingContext; import org.mule.runtime.extension.api.metadata.MetadataResolverFactory; import org.mule.runtime.module.extension.internal.loader.java.property.ImplementingMethodModelProperty; import org.mule.runtime.module.extension.internal.loader.java.property.ImplementingParameterModelProperty; import org.mule.runtime.module.extension.internal.loader.java.property.ImplementingTypeModelProperty; import org.mule.runtime.extension.internal.property.MetadataKeyIdModelProperty; import org.mule.runtime.extension.internal.property.MetadataKeyPartModelProperty; import org.mule.runtime.module.extension.internal.loader.java.property.MetadataResolverFactoryModelProperty; import org.mule.runtime.module.extension.internal.loader.java.property.ParameterGroupModelProperty; import org.mule.runtime.module.extension.internal.loader.java.property.QueryParameterModelProperty; import org.mule.runtime.module.extension.internal.metadata.MetadataScopeAdapter; import org.mule.runtime.module.extension.internal.metadata.QueryMetadataResolverFactory; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; import java.util.Optional; import java.util.Set; /** * {@link DeclarationEnricher} implementation that walks through a {@link ExtensionDeclaration} and looks for components (Operations and * Message sources) annotated with {@link MetadataScope} or {@link Query}. If a custom metadata scope is used, the component will * be considered of dynamic type. * * @since 4.0 */ public class DynamicMetadataDeclarationEnricher extends AbstractAnnotatedDeclarationEnricher { private Class<?> extensionType; private ClassTypeLoader typeLoader; @Override public void enrich(ExtensionLoadingContext extensionLoadingContext) { Optional<ImplementingTypeModelProperty> implementingType = extractImplementingTypeProperty(extensionLoadingContext.getExtensionDeclarer().getDeclaration()); if (implementingType.isPresent()) { extensionType = implementingType.get().getType(); typeLoader = ExtensionsTypeLoaderFactory.getDefault().createTypeLoader(currentThread().getContextClassLoader()); new IdempotentDeclarationWalker() { @Override public void onSource(SourceDeclaration declaration) { enrichSourceMetadata(declaration); } @Override public void onOperation(OperationDeclaration declaration) { enrichOperationMetadata(declaration); } @Override protected void onParameter(ParameterGroupDeclaration parameterGroup, ParameterDeclaration declaration) { enrichParameter(declaration); } }.walk(extensionLoadingContext.getExtensionDeclarer().getDeclaration()); } } private void enrichParameter(ParameterDeclaration declaration) { Optional<AnnotatedElement> annotatedElement = getAnnotatedElement(declaration); if (annotatedElement.isPresent()) { parseMetadataAnnotations(annotatedElement.get(), declaration); } } private void enrichSourceMetadata(SourceDeclaration declaration) { declaration.getModelProperty(ImplementingTypeModelProperty.class) .ifPresent(prop -> { final Class<?> sourceType = prop.getType(); MetadataScopeAdapter metadataScope = new MetadataScopeAdapter(extensionType, sourceType); declareMetadataResolverFactory(declaration, metadataScope); }); } private void enrichOperationMetadata(OperationDeclaration declaration) { declaration.getModelProperty(ImplementingMethodModelProperty.class) .ifPresent(prop -> { final Method method = prop.getMethod(); if (method.isAnnotationPresent(Query.class)) { enrichWithDsql(declaration, method); } else { MetadataScopeAdapter metadataScope = new MetadataScopeAdapter(extensionType, method, declaration); declareMetadataResolverFactory(declaration, metadataScope); } }); } private void declareMetadataResolverFactory(ComponentDeclaration<? extends ComponentDeclaration> declaration, MetadataScopeAdapter metadataScope) { MetadataResolverFactory metadataResolverFactory = getMetadataResolverFactory(metadataScope); declaration.addModelProperty(new MetadataResolverFactoryModelProperty(metadataResolverFactory)); declareMetadataKeyId(declaration); declareOutputResolvers(declaration, metadataScope); declareInputResolvers(declaration, metadataScope); } private void enrichWithDsql(OperationDeclaration declaration, Method method) { Query query = method.getAnnotation(Query.class); declaration.addModelProperty(new MetadataResolverFactoryModelProperty( new QueryMetadataResolverFactory(query .nativeOutputResolver(), query.entityResolver()))); addQueryModelProperties(declaration, query); declareDynamicType(declaration.getOutput()); declareMetadataKeyId(declaration); } private void addQueryModelProperties(OperationDeclaration declaration, Query query) { ParameterDeclaration parameterDeclaration = declaration.getAllParameters() .stream() .filter(p -> p.getModelProperty(ImplementingParameterModelProperty.class).isPresent()) .filter(p -> p.getModelProperty(ImplementingParameterModelProperty.class).get() .getParameter().isAnnotationPresent(MetadataKeyId.class)) .findFirst() .orElseThrow(() -> new IllegalParameterModelDefinitionException( "Query operation must have a parameter annotated with @MetadataKeyId")); parameterDeclaration.addModelProperty(new QueryParameterModelProperty(query.translator())); parameterDeclaration.setLayoutModel(builderFrom(parameterDeclaration.getLayoutModel()).asQuery().build()); } private MetadataResolverFactory getMetadataResolverFactory(MetadataScopeAdapter scope) { return scope.isCustomScope() ? new DefaultMetadataResolverFactory(scope.getKeysResolver(), scope.getInputResolvers(), scope.getOutputResolver(), scope.getAttributesResolver()) : new NullMetadataResolverFactory(); } private void declareInputResolvers(ComponentDeclaration<?> declaration, MetadataScopeAdapter metadataScope) { if (metadataScope.hasInputResolvers()) { Set<String> dynamicParameters = metadataScope.getInputResolvers().keySet(); declaration.getAllParameters().stream() .filter(p -> dynamicParameters.contains(p.getName())) .forEach(this::declareDynamicType); } } private void declareOutputResolvers(ComponentDeclaration<?> declaration, MetadataScopeAdapter metadataScope) { if (metadataScope.hasOutputResolver()) { declareDynamicType(declaration.getOutput()); } if (metadataScope.hasAttributesResolver()) { declareDynamicType(declaration.getOutputAttributes()); } } private void declareDynamicType(TypedDeclaration component) { component.setType(component.getType(), true); } private void declareMetadataKeyId(ComponentDeclaration<? extends ComponentDeclaration> component) { getMetadataKeyModelProperty(component).ifPresent(property -> component.addModelProperty(property)); } private Optional<MetadataKeyIdModelProperty> getMetadataKeyModelProperty( ComponentDeclaration<? extends ComponentDeclaration> component) { Optional<MetadataKeyIdModelProperty> keyId = findMetadataKeyIdInGroups(component); return keyId.isPresent() ? keyId : findMetadataKeyIdInParameters(component); } private Optional<MetadataKeyIdModelProperty> findMetadataKeyIdInGroups( ComponentDeclaration<? extends ComponentDeclaration> component) { return component.getParameterGroups().stream() .map(group -> group.getModelProperty(ParameterGroupModelProperty.class).orElse(null)) .filter(group -> group != null) .filter(group -> group.getDescriptor().getContainer().getAnnotation(MetadataKeyId.class) != null) .map(group -> new MetadataKeyIdModelProperty(typeLoader.load(group.getDescriptor().getType().getDeclaringClass()), group.getDescriptor().getName())) .findFirst(); } private Optional<MetadataKeyIdModelProperty> findMetadataKeyIdInParameters( ComponentDeclaration<? extends ComponentDeclaration> component) { return component.getParameterGroups().stream() .flatMap(g -> g.getParameters().stream()) .filter(p -> getAnnotatedElement(p).map(element -> element.isAnnotationPresent(MetadataKeyId.class)).orElse(false)) .map(p -> new MetadataKeyIdModelProperty(p.getType(), p.getName())) .findFirst(); } /** * Enriches the {@link ParameterDeclarer} with a {@link MetadataKeyPartModelProperty} * if the parsedParameter is annotated either as {@link MetadataKeyId} or {@link MetadataKeyPart} * * @param element the method annotated parameter parsed * @param baseDeclaration the {@link ParameterDeclarer} associated to the parsed parameter */ private void parseMetadataAnnotations(AnnotatedElement element, BaseDeclaration baseDeclaration) { if (element.isAnnotationPresent(MetadataKeyId.class)) { baseDeclaration.addModelProperty(new MetadataKeyPartModelProperty(1)); } if (element.isAnnotationPresent(MetadataKeyPart.class)) { MetadataKeyPart metadataKeyPart = element.getAnnotation(MetadataKeyPart.class); baseDeclaration.addModelProperty(new MetadataKeyPartModelProperty(metadataKeyPart.order())); } } }