All Downloads are FREE. Search and download functionalities are using the official Maven repository.

poussecafe.doc.model.processstepdoc.ProcessStepDocExtractor Maven / Gradle / Ivy

The newest version!
package poussecafe.doc.model.processstepdoc;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
import jdk.javadoc.doclet.DocletEnvironment;
import poussecafe.discovery.DefaultProcess;
import poussecafe.discovery.MessageListener;
import poussecafe.discovery.ProducesEvent;
import poussecafe.discovery.ProducesEvents;
import poussecafe.doc.annotations.AnnotationUtils;
import poussecafe.doc.doclet.ClassDocPredicates;
import poussecafe.doc.doclet.Logger;
import poussecafe.doc.model.AnnotationsResolver;
import poussecafe.doc.model.ComponentDoc;
import poussecafe.doc.model.ComponentDocFactory;
import poussecafe.doc.model.DocletAccess;
import poussecafe.doc.model.ModuleComponentDoc;
import poussecafe.doc.model.aggregatedoc.AggregateDocFactory;
import poussecafe.doc.model.aggregatedoc.AggregateDocId;
import poussecafe.doc.model.aggregatedoc.AggregateDocRepository;
import poussecafe.doc.model.domainprocessdoc.ComponentMethodName;
import poussecafe.doc.model.domainprocessdoc.DomainProcessDocFactory;
import poussecafe.doc.model.moduledoc.ModuleDocId;
import poussecafe.doc.model.moduledoc.ModuleDocRepository;
import poussecafe.domain.DomainEvent;
import poussecafe.domain.Service;
import poussecafe.exception.PousseCafeException;
import poussecafe.source.generation.NamingConventions;

import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.singleton;
import static java.util.stream.Collectors.toList;
import static poussecafe.collection.Collections.asSet;

public class ProcessStepDocExtractor implements Service {

    public List extractProcessStepDocs(ModuleDocId moduleDocId, TypeElement classDoc) {
        String moduleName = moduleDocRepository.get(moduleDocId).attributes().componentDoc().value().name();
        List stepDocs = new ArrayList<>();
        for(ExecutableElement methodDoc : docletAccess.methods(classDoc)) {
            if(isProcessStep(methodDoc)) {
                List customStepSignatures = annotationsResolver.step(methodDoc);
                if(!customStepSignatures.isEmpty()) {
                    stepDocs.addAll(extractCustomSteps(moduleDocId, moduleName, methodDoc));
                } else {
                    stepDocs.add(extractDeclaredStep(moduleDocId, moduleName, methodDoc));
                }
            }
        }
        return stepDocs;
    }

    private ModuleDocRepository moduleDocRepository;

    private DocletAccess docletAccess;

    private boolean isProcessStep(ExecutableElement methodDoc) {
        if(domainProcessDocFactory.isDomainProcessDoc((TypeElement) methodDoc.getEnclosingElement())) {
            return annotationsResolver.isStep(methodDoc);
        } else {
            Optional consumedMessage = consumedMessageExtractor.consumedMessage(methodDoc);
            Set producedEvents = extractProducedEvents(methodDoc);
            return annotationsResolver.isStep(methodDoc) ||
                    (docletAccess.isPublic(methodDoc) && (consumedMessage.isPresent() || !producedEvents.isEmpty()));
        }
    }

    private DomainProcessDocFactory domainProcessDocFactory;

    private AnnotationsResolver annotationsResolver;

    private ConsumedMessageExtractor consumedMessageExtractor;

    private Set extractProducedEvents(ExecutableElement methodDoc) {
        Set producedEvents = new HashSet<>();
        List javadocTagEvents = annotationsResolver.event(methodDoc);
        if(!javadocTagEvents.isEmpty()) {
            Logger.warn("@event tag is deprecated, use @ProducesEvent annotation instead");
            producedEvents.addAll(javadocTagEvents.stream().map(NameRequired::required).collect(toList()));
        }
        List producesEventAnnotations = producesEventAnnotations(methodDoc);
        for(AnnotationMirror mirror : producesEventAnnotations) {
            NameRequired nameRequired = nameRequired(mirror);
            producedEvents.add(nameRequired);
        }
        return producedEvents;
    }

    private List producesEventAnnotations(ExecutableElement methodDoc) {
        return AnnotationUtils.annotations(methodDoc, ProducesEvent.class, ProducesEvents.class);
    }

    private NameRequired nameRequired(AnnotationMirror producesEventMirror) {
        Optional producesEventValue = AnnotationUtils.value(producesEventMirror, "value");
        Element valueElement = producesEventValue
                .map(AnnotationValue::getValue)
                .map(value -> docletAccess.getTypesUtils().asElement((TypeMirror) value))
                .orElseThrow();

        boolean required = required(producesEventMirror);
        NameRequired nameRequired;
        if(required) {
            nameRequired = NameRequired.required(valueElement.getSimpleName().toString());
        } else {
            nameRequired = NameRequired.optional(valueElement.getSimpleName().toString());
        }
        return nameRequired;
    }

    private boolean required(AnnotationMirror producesEventMirror) {
        Optional producesEventRequired = AnnotationUtils.value(producesEventMirror, "required");
        boolean required;
        if(producesEventRequired.isPresent()) {
            required = (Boolean) producesEventRequired.get().getValue();
        } else {
            required = true;
        }
        return required;
    }

    private List extractCustomSteps(ModuleDocId moduleDocId,
            String moduleName,
            ExecutableElement methodDoc) {
        List stepDocs = new ArrayList<>();
        Set processNames = processNames(methodDoc);
        Set producedEvents = extractProducedEvents(methodDoc);
        Set fromExternals = extractFromExternals(methodDoc);
        Set toExternals = extractToExternals(methodDoc);
        Map> toExternalsByEvent = extractToExternalsByEvent(methodDoc);

        List methodSignatures = customStepsSignatures(methodDoc);
        for(StepMethodSignature signature : methodSignatures) {
            Logger.info("Extracting custom step " + signature);
            ProcessStepDocId messageListenerDocId = new ProcessStepDocId(signature.toString());
            ModuleComponentDoc moduleComponentDoc = new ModuleComponentDoc.Builder()
                    .moduleDocId(moduleDocId)
                    .moduleName(moduleName)
                    .componentDoc(new ComponentDoc.Builder()
                            .name(signature.toString())
                            .description(annotationsResolver.renderCommentBody(methodDoc))
                            .build())
                    .build();

            ProcessStepDoc processStepDoc = messageListenerDocFactory.createMessageListenerDoc(messageListenerDocId,
                    moduleComponentDoc);
            processStepDoc.attributes().processNames().value(processNames);
            processStepDoc.attributes().stepMethodSignature().value(Optional.of(signature));
            processStepDoc.attributes().producedEvents().value(producedEvents);
            processStepDoc.attributes().fromExternals().value(fromExternals);
            processStepDoc.attributes().toExternals().value(toExternals);
            processStepDoc.attributes().toExternalsByEvent().value(toExternalsByEvent);
            stepDocs.add(processStepDoc);
        }
        return stepDocs;
    }

    private Set extractToExternals(ExecutableElement methodDoc) {
        Set toExternals = new HashSet<>();
        List javadocTagToExternals = annotationsResolver.toExternal(methodDoc);
        if(!javadocTagToExternals.isEmpty()) {
            Logger.warn("@to_external tag is deprecated, use @ProducesEvent annotation and set consumedByExternal element instead");
            toExternals.addAll(javadocTagToExternals);
        }
        return toExternals;
    }

    private static final String CONSUMED_BY_EXTERNAL_ELEMENT_NAME = "consumedByExternal";

    @SuppressWarnings("unchecked")
    private Set extractFromExternals(ExecutableElement methodDoc) {
        Set fromExternals = new HashSet<>();
        List javadocTagToExternals = annotationsResolver.fromExternal(methodDoc);
        if(!javadocTagToExternals.isEmpty()) {
            Logger.warn("@from_external tag is deprecated, use @MessageListener annotation and set consumesFromExternal element instead");
            fromExternals.addAll(javadocTagToExternals);
        }

        Optional annotationMirror = AnnotationUtils.annotation(methodDoc, MessageListener.class);
        if(annotationMirror.isPresent()) {
           Optional optionalAnnotationValue = AnnotationUtils.value(annotationMirror.get(), "consumesFromExternal");
           if(optionalAnnotationValue.isPresent()) {
               List annotationValue = (List) optionalAnnotationValue.get().getValue();
               fromExternals.addAll(annotationValue.stream()
                       .map(external -> (String) external.getValue())
                       .collect(toList()));
           }
        }

        return fromExternals;
    }

    @SuppressWarnings("unchecked")
    private Map> extractToExternalsByEvent(ExecutableElement methodDoc) {
        Map> toExternals = new HashMap<>();
        List producesEventAnnotations = producesEventAnnotations(methodDoc);
        for(AnnotationMirror mirror : producesEventAnnotations) {
            NameRequired nameRequired = nameRequired(mirror);
            Optional consumedByExternal = AnnotationUtils.value(mirror, CONSUMED_BY_EXTERNAL_ELEMENT_NAME);
            if(consumedByExternal.isPresent()) {
                List externals = (List) consumedByExternal.get().getValue();
                toExternals.put(nameRequired, externals.stream()
                        .map(annotationValue -> (String) annotationValue.getValue())
                        .collect(toList()));
            }
        }
        return toExternals;
    }

    private List customStepsSignatures(ExecutableElement methodDoc) {
        List customStepSignatures = annotationsResolver.step(methodDoc);
        if(domainProcessDocFactory.isDomainProcessDoc((TypeElement) methodDoc.getEnclosingElement())) {
            if(customStepSignatures.size() != 1) {
                throw new PousseCafeException("Domain processes listeners must be tagged with a single step");
            }
            Optional consumedEvent = consumedEvent(methodDoc);
            ComponentMethodName componentMethodName = ComponentMethodName.parse(customStepSignatures.get(0));
            return asList(new StepMethodSignature.Builder()
                    .componentMethodName(componentMethodName)
                    .consumedMessageName(consumedEvent)
                    .build());
        } else {
            return customStepSignatures.stream().map(StepMethodSignature::parse).collect(toList());
        }
    }

    private Optional consumedEvent(ExecutableElement methodDoc) {
        List parameters = methodDoc.getParameters();
        if(parameters.isEmpty()) {
            return Optional.empty();
        }

        TypeMirror firstParameterType = parameters.get(0).asType();
        Element firstParameterElement = docletEnvironment.getTypeUtils().asElement(firstParameterType);
        if(firstParameterElement instanceof TypeElement) {
            TypeElement firstParameterTypeElement = (TypeElement) firstParameterElement;
            if(classDocPredicates.documentsWithSuperinterface(firstParameterTypeElement, DomainEvent.class)) {
                return Optional.of(firstParameterTypeElement.getQualifiedName().toString());
            } else {
                return Optional.empty();
            }
        } else {
            return Optional.empty();
        }
    }

    private DocletEnvironment docletEnvironment;

    private ClassDocPredicates classDocPredicates;

    private ProcessStepDocFactory messageListenerDocFactory;

    private Set processNames(ExecutableElement methodDoc) {
        Set processNames = new HashSet<>(annotationsResolver.process(methodDoc));
        processNames.addAll(namesFromMessageListenerAnnotation(methodDoc));
        TypeElement containingClass = (TypeElement) methodDoc.getEnclosingElement();
        if(domainProcessDocFactory.isDomainProcessDoc(containingClass)) {
            return asSet(domainProcessDocFactory.name(containingClass));
        } else if(!processNames.isEmpty()) {
            return Collections.unmodifiableSet(processNames);
        } else {
            return singleton(DefaultProcess.class.getSimpleName());
        }
    }

    private List namesFromMessageListenerAnnotation(ExecutableElement methodDoc) {
        Optional annotationMirror = AnnotationUtils.annotation(methodDoc, MessageListener.class);
        if(annotationMirror.isPresent()) {
            Optional value = AnnotationUtils.value(annotationMirror.get(), "processes");
            if(value.isPresent()) {
                @SuppressWarnings("unchecked")
                List processes = (List) value.get().getValue();
                return processes.stream()
                        .map(processValue -> docletAccess.getTypesUtils().asElement((TypeMirror) processValue.getValue()))
                        .map(process -> process.getSimpleName().toString())
                        .collect(toList());
            } else {
                return emptyList();
            }
        } else {
            return emptyList();
        }
    }

    private ProcessStepDoc extractDeclaredStep(ModuleDocId moduleDocId,
            String moduleName,
            ExecutableElement methodDoc) {
        Logger.info("Extracting declared step from method " + methodDoc.getSimpleName().toString());

        Set processNames = processNames(methodDoc);
        Set producedEvents = extractProducedEvents(methodDoc);
        Set fromExternals = extractFromExternals(methodDoc);
        Set toExternals = extractToExternals(methodDoc);
        Map> toExternalsByEvent = extractToExternalsByEvent(methodDoc);

        Optional consumedMessage = consumedMessageExtractor.consumedMessage(methodDoc);
        TypeElement enclosingType = (TypeElement) methodDoc.getEnclosingElement();
        var componentName = listenerContainerName(enclosingType);
        StepMethodSignature stepMethodSignature = new StepMethodSignature.Builder()
                .componentMethodName(new ComponentMethodName.Builder()
                        .componentName(componentName)
                        .methodName(methodDoc.getSimpleName().toString())
                        .build())
                .consumedMessageName(consumedMessage)
                .build();

        AggregateDocId aggregate = declaringAggregate(methodDoc);

        ProcessStepDocId id = new ProcessStepDocId(stepMethodSignature);
        ModuleComponentDoc moduleComponentDoc = new ModuleComponentDoc.Builder()
                .moduleDocId(moduleDocId)
                .componentDoc(componentDocFactory.buildDoc(id.stringValue(), methodDoc))
                .moduleName(moduleName)
                .build();

        ProcessStepDoc processStepDoc = messageListenerDocFactory.createMessageListenerDoc(id,
                moduleComponentDoc);
        processStepDoc.attributes().processNames().value(processNames);
        processStepDoc.attributes().stepMethodSignature().value(Optional.of(stepMethodSignature));
        processStepDoc.attributes().producedEvents().value(producedEvents);
        processStepDoc.attributes().fromExternals().value(fromExternals);
        processStepDoc.attributes().toExternals().value(toExternals);
        processStepDoc.attributes().toExternalsByEvent().value(toExternalsByEvent);
        processStepDoc.attributes().aggregate().value(Optional.of(aggregate));
        String aggregateName = aggregateDocRepository.get(aggregate).attributes().moduleComponentDoc().value().componentDoc().name();
        processStepDoc.attributes().aggregateName().value(Optional.of(aggregateName));

        return processStepDoc;
    }

    private String listenerContainerName(TypeElement enclosingType) {
        try {
            String componentName;
            if(aggregateDocFactory.isStandaloneRoot(enclosingType)) {
                componentName = NamingConventions.aggregateNameFromSimpleRootName(enclosingType.getSimpleName().toString());
            } else if(aggregateDocFactory.isStandaloneFactory(enclosingType)) {
                componentName = NamingConventions.aggregateNameFromSimpleFactoryName(enclosingType.getSimpleName().toString());
            } else if(aggregateDocFactory.isStandaloneRepository(enclosingType)) {
                componentName = NamingConventions.aggregateNameFromSimpleRepositoryName(enclosingType.getSimpleName().toString());
            } else {
                var potentialContainer = enclosingType.getEnclosingElement();
                if(potentialContainer instanceof TypeElement
                        && aggregateDocFactory.isContainer((TypeElement) potentialContainer)) {
                    componentName = potentialContainer.getSimpleName().toString();
                } else {
                    componentName = enclosingType.getSimpleName().toString();
                }
            }
            return componentName;
        } catch (IllegalArgumentException e) {
            Logger.warn("Listener container {} does not follow naming conventions", enclosingType.getQualifiedName().toString(), e);
            return enclosingType.getSimpleName().toString();
        }
    }

    private AggregateDocId declaringAggregate(ExecutableElement methodDoc) {
        TypeElement enclosingType = (TypeElement) methodDoc.getEnclosingElement();
        TypeElement aggregateType;
        if(aggregateDocFactory.isFactoryDoc(enclosingType)) {
            aggregateType = aggregateDocFactory.aggregateTypeElementOfFactory(enclosingType);
        } else if(aggregateDocFactory.isRepositoryDoc(enclosingType)) {
            aggregateType = aggregateDocFactory.aggregateTypeElementOfRepository(enclosingType);
        } else if(aggregateDocFactory.extendsAggregateRoot(enclosingType)) {
            aggregateType = aggregateDocFactory.aggregateTypeElementOfRoot(enclosingType);
        } else {
            aggregateType = enclosingType;
        }
        return AggregateDocId.ofClassName(aggregateType.getQualifiedName().toString());
    }

    private ComponentDocFactory componentDocFactory;

    private AggregateDocFactory aggregateDocFactory;

    private AggregateDocRepository aggregateDocRepository;
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy