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 extends AnnotationMirror> 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 extends AnnotationMirror> 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 extends VariableElement> 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;
}