Repository: OpenAEV-Platform/openaev (java)

Issue:
Fix issue when replacing variables in "multi" email
## Description

<!-- Please provide a clear and concise description of the bug. -->

## Environment

1. OS (where OpenBAS server runs): { e.g. Mac OS 10, Windows 10, Ubuntu 16.4, etc. }
2. OpenBAS version: { e.g. OpenBAS 1.0.2 }
3. OpenBAS client: { e.g. frontend or python }
4. Other environment details:

## Reproducible Steps

Steps to create the smallest reproducible scenario:
1. { e.g. Run ... }
2. { e.g. Click ... }
3. { e.g. Error ... }

## Expected Output

<!-- Please describe what you expected to happen. -->

## Actual Output

<!-- Please describe what actually happened. -->

## Additional information

<!-- Any additional information, including logs or screenshots if you have any. -->

## Screenshots (optional)



Discussion:

Patch:
diff --git a/openbas-api/src/main/java/io/openbas/injectors/challenge/ChallengeExecutor.java b/openbas-api/src/main/java/io/openbas/injectors/challenge/ChallengeExecutor.java
--- a/openbas-api/src/main/java/io/openbas/injectors/challenge/ChallengeExecutor.java
+++ b/openbas-api/src/main/java/io/openbas/injectors/challenge/ChallengeExecutor.java
@@ -111,7 +111,7 @@ public ExecutionProcess process(
                 // Send the email.
                 emailService.sendEmail(
                     execution,
-                    userInjectContext,
+                    List.of(userInjectContext),
                     from,
                     replyTos,
                     content.getInReplyTo(),
diff --git a/openbas-api/src/main/java/io/openbas/injectors/channel/ChannelExecutor.java b/openbas-api/src/main/java/io/openbas/injectors/channel/ChannelExecutor.java
--- a/openbas-api/src/main/java/io/openbas/injectors/channel/ChannelExecutor.java
+++ b/openbas-api/src/main/java/io/openbas/injectors/channel/ChannelExecutor.java
@@ -115,7 +115,7 @@ public ExecutionProcess process(
                   // Send the email.
                   emailService.sendEmail(
                       execution,
-                      userInjectContext,
+                      List.of(userInjectContext),
                       from,
                       replyTos,
                       content.getInReplyTo(),
diff --git a/openbas-api/src/main/java/io/openbas/injectors/email/EmailExecutor.java b/openbas-api/src/main/java/io/openbas/injectors/email/EmailExecutor.java
--- a/openbas-api/src/main/java/io/openbas/injectors/email/EmailExecutor.java
+++ b/openbas-api/src/main/java/io/openbas/injectors/email/EmailExecutor.java
@@ -65,7 +65,7 @@ private void sendSingle(
           try {
             emailService.sendEmail(
                 execution,
-                user,
+                List.of(user),
                 from,
                 replyTos,
                 inReplyTo,
diff --git a/openbas-api/src/main/java/io/openbas/injectors/email/service/EmailService.java b/openbas-api/src/main/java/io/openbas/injectors/email/service/EmailService.java
--- a/openbas-api/src/main/java/io/openbas/injectors/email/service/EmailService.java
+++ b/openbas-api/src/main/java/io/openbas/injectors/email/service/EmailService.java
@@ -18,7 +18,6 @@
 import jakarta.mail.util.ByteArrayDataSource;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
-import java.util.ArrayList;
 import java.util.List;
 import org.bouncycastle.openpgp.PGPPublicKey;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -62,25 +61,13 @@ public void sendEmail(
       String message,
       List<DataAttachment> attachments)
       throws Exception {
-    MimeMessage mimeMessage =
-        buildMimeMessage(from, replyTos, inReplyTo, subject, message, attachments);
-    List<InternetAddress> recipients = new ArrayList<>();
-    for (ExecutionContext userContext : usersContext) {
-      recipients.add(new InternetAddress(userContext.getUser().getEmail()));
-    }
-    mimeMessage.setRecipients(Message.RecipientType.TO, recipients.toArray(InternetAddress[]::new));
-    this.sendEmailWithRetry(execution, mimeMessage);
-    String emails = usersContext.stream().map(c -> c.getUser().getEmail()).collect(joining(", "));
-    List<String> userIds = usersContext.stream().map(c -> c.getUser().getId()).toList();
-    execution.addTrace(
-        getNewSuccessTrace("Mail sent to " + emails, ExecutionTraceAction.EXECUTION, userIds));
-    // Store message in Imap after sending
-    storeMessageImap(execution, mimeMessage, userIds);
+    sendEmail(
+        execution, usersContext, from, replyTos, inReplyTo, false, subject, message, attachments);
   }
 
   public void sendEmail(
       Execution execution,
-      ExecutionContext userContext,
+      List<ExecutionContext> usersContext,
       String from,
       List<String> replyTos,
       String inReplyTo,
@@ -89,24 +76,50 @@ public void sendEmail(
       String message,
       List<DataAttachment> attachments)
       throws Exception {
-    String email = userContext.getUser().getEmail();
-    String contextualSubject = buildContextualContent(subject, userContext);
-    String contextualBody = buildContextualContent(message, userContext);
+    ExecutionContext interpolationContext = (ExecutionContext) usersContext.getFirst().clone();
+    if (usersContext.size() > 1) {
+      interpolationContext.remove("user");
+    }
+    String contextualSubject = buildContextualContent(subject, interpolationContext);
+    String contextualBody = buildContextualContent(message, interpolationContext);
 
     MimeMessage mimeMessage =
         buildMimeMessage(from, replyTos, inReplyTo, contextualSubject, contextualBody, attachments);
-    mimeMessage.setRecipient(Message.RecipientType.TO, new InternetAddress(email));
-    // Crypt if needed
-    if (mustBeEncrypted) {
+    mimeMessage.setRecipients(
+        Message.RecipientType.TO,
+        usersContext.stream()
+            .map(
+                uc -> {
+                  try {
+                    return new InternetAddress(uc.getUser().getEmail());
+                  } catch (AddressException e) {
+                    throw new RuntimeException(e);
+                  }
+                })
+            .toArray(InternetAddress[]::new));
+
+    // request encryption but this is possible only for an email to a single recipient
+    if (mustBeEncrypted && usersContext.size() == 1) {
+      ExecutionContext singleUserContext = usersContext.getFirst();
       MimeMessage encMessage =
-          getEncryptedMimeMessage(userContext, from, replyTos, subject, email, mimeMessage);
+          getEncryptedMimeMessage(
+              singleUserContext,
+              from,
+              replyTos,
+              subject,
+              singleUserContext.getUser().getEmail(),
+              mimeMessage);
       this.sendEmailWithRetry(execution, encMessage);
     } else {
       this.sendEmailWithRetry(execution, mimeMessage);
     }
-    List<String> userIds = List.of(userContext.getUser().getId());
+    List<String> userIds = usersContext.stream().map(c -> c.getUser().getId()).toList();
     execution.addTrace(
-        getNewSuccessTrace("Mail sent to " + email, ExecutionTraceAction.EXECUTION, userIds));
+        getNewSuccessTrace(
+            "Mail sent to "
+                + usersContext.stream().map(c -> c.getUser().getEmail()).collect(joining(", ")),
+            ExecutionTraceAction.EXECUTION,
+            userIds));
     // Store message in Imap after sending
     storeMessageImap(execution, mimeMessage, userIds);
   }


Test patch:
diff --git a/openbas-api/src/test/java/io/openbas/rest/scenario/ScenarioInjectTestApiTest.java b/openbas-api/src/test/java/io/openbas/rest/scenario/ScenarioInjectTestApiTest.java
--- a/openbas-api/src/test/java/io/openbas/rest/scenario/ScenarioInjectTestApiTest.java
+++ b/openbas-api/src/test/java/io/openbas/rest/scenario/ScenarioInjectTestApiTest.java
@@ -68,7 +68,7 @@ public void setup() {
   }
 
   @Nested
-  @DisplayName("Email test send inject tests")
+  @DisplayName("Single email test send inject tests")
   public class EmailTestSendInjectTests {
     private InjectorContractComposer.Composer createEmailContract() {
       return injectorContractComposer
@@ -123,6 +123,45 @@ public void scenarioVariableIsInterpolated() throws Exception {
           .isEqualTo("<div>%s</div>".formatted(varValue));
     }
 
+    @Test
+    @DisplayName("User variable is interpolated in scenario inject test")
+    @WithMockPlannerUser
+    public void userVariableIsInterpolatedInScenarioInjectTest() throws Exception {
+      ArgumentCaptor<MimeMessage> argument = ArgumentCaptor.forClass(MimeMessage.class);
+      ScenarioComposer.Composer scenarioWithEmailInjectWrapper =
+          scenarioComposer.forScenario(ScenarioFixture.createDefaultCrisisScenario());
+
+      Inject injectFixture =
+          InjectFixture.getInjectForEmailContract(
+              injectorContractFixture.getWellKnownSingleEmailContract());
+      injectFixture.getContent().set("subject", mapper.valueToTree("test email"));
+      injectFixture.getContent().set("body", mapper.valueToTree("${user.email}"));
+
+      InjectComposer.Composer injectWrapper =
+          injectComposer.forInject(injectFixture).withInjectorContract(createEmailContract());
+
+      scenarioWithEmailInjectWrapper.withInject(injectWrapper).persist();
+
+      entityManager.flush();
+      entityManager.clear();
+
+      when(mailSender.createMimeMessage()).thenReturn(new MimeMessage((Session) null));
+      Mockito.doCallRealMethod().when(mailSender).send((MimeMessage) any());
+      mvc.perform(
+              get(
+                  SCENARIO_URI + "/{scenarioId}/injects/{injectId}/test",
+                  scenarioWithEmailInjectWrapper.get().getId(),
+                  scenarioWithEmailInjectWrapper.get().getInjects().getFirst().getId()))
+          .andExpect(status().isOk());
+
+      verify(mailSender).send(argument.capture());
+      assertThat(
+              ((MimeMultipart) argument.getAllValues().getFirst().getContent())
+                  .getBodyPart(0)
+                  .getContent())
+          .isEqualTo("<div>planner@openbas.io</div>");
+    }
+
     @Test
     @DisplayName("Simulation variable is interpolated")
     @WithMockPlannerUser
@@ -171,6 +210,235 @@ public void simulationVariableIsInterpolated() throws Exception {
               "<div style=\"text-align: center; margin-bottom: 10px;\">SIMULATION HEADER</div><div>%s</div>"
                   .formatted(varValue));
     }
+
+    @Test
+    @DisplayName("User variable is interpolated in simulation inject test")
+    @WithMockPlannerUser
+    public void userVariableIsInterpolatedInSimulationInjectTest() throws Exception {
+      ArgumentCaptor<MimeMessage> argument = ArgumentCaptor.forClass(MimeMessage.class);
+      ExerciseComposer.Composer simulationWithEmailInjectWrapper =
+          simulationComposer.forExercise(ExerciseFixture.createDefaultExercise());
+
+      Inject injectFixture =
+          InjectFixture.getInjectForEmailContract(
+              injectorContractFixture.getWellKnownSingleEmailContract());
+      injectFixture.getContent().set("subject", mapper.valueToTree("test email"));
+      injectFixture.getContent().set("body", mapper.valueToTree("${user.email}"));
+
+      InjectComposer.Composer injectWrapper =
+          injectComposer.forInject(injectFixture).withInjectorContract(createEmailContract());
+
+      simulationWithEmailInjectWrapper.withInject(injectWrapper).persist();
+
+      entityManager.flush();
+      entityManager.clear();
+
+      when(mailSender.createMimeMessage()).thenReturn(new MimeMessage((Session) null));
+      Mockito.doCallRealMethod().when(mailSender).send((MimeMessage) any());
+      mvc.perform(
+              get(
+                  EXERCISE_URI + "/{simulationId}/injects/{injectId}/test",
+                  simulationWithEmailInjectWrapper.get().getId(),
+                  simulationWithEmailInjectWrapper.get().getInjects().getFirst().getId()))
+          .andExpect(status().isOk());
+
+      verify(mailSender).send(argument.capture());
+      assertThat(
+              ((MimeMultipart) argument.getAllValues().getFirst().getContent())
+                  .getBodyPart(0)
+                  .getContent())
+          .isEqualTo(
+              "<div style=\"text-align: center; margin-bottom: 10px;\">SIMULATION HEADER</div><div>%s</div>"
+                  .formatted("planner@openbas.io"));
+    }
+  }
+
+  @Nested
+  @DisplayName("Multi (global) email test send inject tests")
+  public class MultiEmailTestSendInjectTests {
+    private InjectorContractComposer.Composer createEmailContract() {
+      return injectorContractComposer
+          .forInjectorContract(injectorContractFixture.getWellKnownGlobalEmailContract())
+          .withInjector(injectorFixture.getWellKnownEmailInjector());
+    }
+
+    @Test
+    @DisplayName("Scenario variable is interpolated")
+    @WithMockPlannerUser
+    public void scenarioVariableIsInterpolated() throws Exception {
+      ArgumentCaptor<MimeMessage> argument = ArgumentCaptor.forClass(MimeMessage.class);
+      String varKey = "var_key";
+      String varValue = "var_value";
+      Variable var = VariableFixture.getDefaultVariable();
+      var.setKey(varKey);
+      var.setValue(varValue);
+      ScenarioComposer.Composer scenarioWithEmailInjectWrapper =
+          scenarioComposer.forScenario(ScenarioFixture.createDefaultCrisisScenario());
+
+      Inject injectFixture =
+          InjectFixture.getInjectForEmailContract(
+              injectorContractFixture.getWellKnownSingleEmailContract());
+      injectFixture.getContent().set("subject", mapper.valueToTree("test email"));
+      injectFixture.getContent().set("body", mapper.valueToTree("${%s}".formatted(varKey)));
+
+      InjectComposer.Composer injectWrapper =
+          injectComposer.forInject(injectFixture).withInjectorContract(createEmailContract());
+
+      scenarioWithEmailInjectWrapper
+          .withInject(injectWrapper)
+          .withVariable(variableComposer.forVariable(var))
+          .persist();
+
+      entityManager.flush();
+      entityManager.clear();
+
+      when(mailSender.createMimeMessage()).thenReturn(new MimeMessage((Session) null));
+      Mockito.doCallRealMethod().when(mailSender).send((MimeMessage) any());
+      mvc.perform(
+              get(
+                  SCENARIO_URI + "/{scenarioId}/injects/{injectId}/test",
+                  scenarioWithEmailInjectWrapper.get().getId(),
+                  scenarioWithEmailInjectWrapper.get().getInjects().getFirst().getId()))
+          .andExpect(status().isOk());
+
+      verify(mailSender).send(argument.capture());
+      assertThat(
+              ((MimeMultipart) argument.getAllValues().getFirst().getContent())
+                  .getBodyPart(0)
+                  .getContent())
+          .isEqualTo("<div>%s</div>".formatted(varValue));
+    }
+
+    @Test
+    @DisplayName(
+        "When a single user is used in a multi email, user variable is interpolated in scenario inject test")
+    @WithMockPlannerUser
+    public void userVariableIsInterpolatedInScenarioInjectTest() throws Exception {
+      ArgumentCaptor<MimeMessage> argument = ArgumentCaptor.forClass(MimeMessage.class);
+      ScenarioComposer.Composer scenarioWithEmailInjectWrapper =
+          scenarioComposer.forScenario(ScenarioFixture.createDefaultCrisisScenario());
+
+      Inject injectFixture =
+          InjectFixture.getInjectForEmailContract(
+              injectorContractFixture.getWellKnownSingleEmailContract());
+      injectFixture.getContent().set("subject", mapper.valueToTree("test email"));
+      injectFixture.getContent().set("body", mapper.valueToTree("${user.email}"));
+
+      InjectComposer.Composer injectWrapper =
+          injectComposer.forInject(injectFixture).withInjectorContract(createEmailContract());
+
+      scenarioWithEmailInjectWrapper.withInject(injectWrapper).persist();
+
+      entityManager.flush();
+      entityManager.clear();
+
+      when(mailSender.createMimeMessage()).thenReturn(new MimeMessage((Session) null));
+      Mockito.doCallRealMethod().when(mailSender).send((MimeMessage) any());
+      mvc.perform(
+              get(
+                  SCENARIO_URI + "/{scenarioId}/injects/{injectId}/test",
+                  scenarioWithEmailInjectWrapper.get().getId(),
+                  scenarioWithEmailInjectWrapper.get().getInjects().getFirst().getId()))
+          .andExpect(status().isOk());
+
+      verify(mailSender).send(argument.capture());
+      assertThat(
+              ((MimeMultipart) argument.getAllValues().getFirst().getContent())
+                  .getBodyPart(0)
+                  .getContent())
+          .isEqualTo("<div>planner@openbas.io</div>");
+    }
+
+    @Test
+    @DisplayName("Simulation variable is interpolated")
+    @WithMockPlannerUser
+    public void simulationVariableIsInterpolated() throws Exception {
+      ArgumentCaptor<MimeMessage> argument = ArgumentCaptor.forClass(MimeMessage.class);
+      String varKey = "var_key";
+      String varValue = "var_value";
+      Variable var = VariableFixture.getDefaultVariable();
+      var.setKey(varKey);
+      var.setValue(varValue);
+      ExerciseComposer.Composer simulationWithEmailInjectWrapper =
+          simulationComposer.forExercise(ExerciseFixture.createDefaultExercise());
+
+      Inject injectFixture =
+          InjectFixture.getInjectForEmailContract(
+              injectorContractFixture.getWellKnownSingleEmailContract());
+      injectFixture.getContent().set("subject", mapper.valueToTree("test email"));
+      injectFixture.getContent().set("body", mapper.valueToTree("${%s}".formatted(varKey)));
+
+      InjectComposer.Composer injectWrapper =
+          injectComposer.forInject(injectFixture).withInjectorContract(createEmailContract());
+
+      simulationWithEmailInjectWrapper
+          .withInject(injectWrapper)
+          .withVariable(variableComposer.forVariable(var))
+          .persist();
+
+      entityManager.flush();
+      entityManager.clear();
+
+      when(mailSender.createMimeMessage()).thenReturn(new MimeMessage((Session) null));
+      Mockito.doCallRealMethod().when(mailSender).send((MimeMessage) any());
+      mvc.perform(
+              get(
+                  EXERCISE_URI + "/{simulationId}/injects/{injectId}/test",
+                  simulationWithEmailInjectWrapper.get().getId(),
+                  simulationWithEmailInjectWrapper.get().getInjects().getFirst().getId()))
+          .andExpect(status().isOk());
+
+      verify(mailSender).send(argument.capture());
+      assertThat(
+              ((MimeMultipart) argument.getAllValues().getFirst().getContent())
+                  .getBodyPart(0)
+                  .getContent())
+          .isEqualTo(
+              "<div style=\"text-align: center; margin-bottom: 10px;\">SIMULATION HEADER</div><div>%s</div>"
+                  .formatted(varValue));
+    }
+
+    @Test
+    @DisplayName(
+        "When a single user is used in a multi email, user variable is interpolated in simulation inject test")
+    @WithMockPlannerUser
+    public void userVariableIsInterpolatedInSimulationInjectTest() throws Exception {
+      ArgumentCaptor<MimeMessage> argument = ArgumentCaptor.forClass(MimeMessage.class);
+      ExerciseComposer.Composer simulationWithEmailInjectWrapper =
+          simulationComposer.forExercise(ExerciseFixture.createDefaultExercise());
+
+      Inject injectFixture =
+          InjectFixture.getInjectForEmailContract(
+              injectorContractFixture.getWellKnownSingleEmailContract());
+      injectFixture.getContent().set("subject", mapper.valueToTree("test email"));
+      injectFixture.getContent().set("body", mapper.valueToTree("${user.email}"));
+
+      InjectComposer.Composer injectWrapper =
+          injectComposer.forInject(injectFixture).withInjectorContract(createEmailContract());
+
+      simulationWithEmailInjectWrapper.withInject(injectWrapper).persist();
+
+      entityManager.flush();
+      entityManager.clear();
+
+      when(mailSender.createMimeMessage()).thenReturn(new MimeMessage((Session) null));
+      Mockito.doCallRealMethod().when(mailSender).send((MimeMessage) any());
+      mvc.perform(
+              get(
+                  EXERCISE_URI + "/{simulationId}/injects/{injectId}/test",
+                  simulationWithEmailInjectWrapper.get().getId(),
+                  simulationWithEmailInjectWrapper.get().getInjects().getFirst().getId()))
+          .andExpect(status().isOk());
+
+      verify(mailSender).send(argument.capture());
+      assertThat(
+              ((MimeMultipart) argument.getAllValues().getFirst().getContent())
+                  .getBodyPart(0)
+                  .getContent())
+          .isEqualTo(
+              "<div style=\"text-align: center; margin-bottom: 10px;\">SIMULATION HEADER</div><div>%s</div>"
+                  .formatted("planner@openbas.io"));
+    }
   }
 
   @Nested
diff --git a/openbas-api/src/test/java/io/openbas/service/EmailServiceTest.java b/openbas-api/src/test/java/io/openbas/service/EmailServiceTest.java
--- a/openbas-api/src/test/java/io/openbas/service/EmailServiceTest.java
+++ b/openbas-api/src/test/java/io/openbas/service/EmailServiceTest.java
@@ -36,7 +36,7 @@ void shouldSetReplyToInHeaderEqualsToFrom() throws Exception {
     when(emailSender.createMimeMessage()).thenReturn(new MimeMessage((Session) null));
     emailService.sendEmail(
         execution,
-        userContext,
+        List.of(userContext),
         "user@openbas.io",
         List.of("user-reply-to@openbas.io"),
         null,
diff --git a/openbas-api/src/test/java/io/openbas/utils/fixtures/InjectorContractFixture.java b/openbas-api/src/test/java/io/openbas/utils/fixtures/InjectorContractFixture.java
--- a/openbas-api/src/test/java/io/openbas/utils/fixtures/InjectorContractFixture.java
+++ b/openbas-api/src/test/java/io/openbas/utils/fixtures/InjectorContractFixture.java
@@ -4,6 +4,7 @@
 import static io.openbas.database.model.InjectorContract.CONTRACT_ELEMENT_CONTENT_KEY_TARGETED_PROPERTY;
 import static io.openbas.injector_contract.fields.ContractSelect.selectFieldWithDefault;
 import static io.openbas.injectors.email.EmailContract.EMAIL_DEFAULT;
+import static io.openbas.injectors.email.EmailContract.EMAIL_GLOBAL;
 import static io.openbas.utils.fixtures.InjectorFixture.createDefaultPayloadInjector;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
@@ -36,6 +37,10 @@ public InjectorContract getWellKnownSingleEmailContract() {
     return injectorContractRepository.findById(EMAIL_DEFAULT).orElseThrow();
   }
 
+  public InjectorContract getWellKnownGlobalEmailContract() {
+    return injectorContractRepository.findById(EMAIL_GLOBAL).orElseThrow();
+  }
+
   private static ObjectNode createDefaultContent(ObjectMapper objectMapper) {
     ObjectNode node = objectMapper.createObjectNode();
     node.set(CONTRACT_CONTENT_FIELDS, objectMapper.valueToTree(new ArrayList<>()));
diff --git a/openbas-api/src/test/java/io/openbas/utils/fixtures/composers/InjectorContractComposer.java b/openbas-api/src/test/java/io/openbas/utils/fixtures/composers/InjectorContractComposer.java
--- a/openbas-api/src/test/java/io/openbas/utils/fixtures/composers/InjectorContractComposer.java
+++ b/openbas-api/src/test/java/io/openbas/utils/fixtures/composers/InjectorContractComposer.java
@@ -3,6 +3,7 @@
 import static io.openbas.injectors.challenge.ChallengeContract.CHALLENGE_PUBLISH;
 import static io.openbas.injectors.channel.ChannelContract.CHANNEL_PUBLISH;
 import static io.openbas.injectors.email.EmailContract.EMAIL_DEFAULT;
+import static io.openbas.injectors.email.EmailContract.EMAIL_GLOBAL;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.node.ObjectNode;
@@ -29,7 +30,7 @@ public class InjectorContractComposer extends ComposerBase<InjectorContract> {
 
   public class Composer extends InnerComposerBase<InjectorContract> {
     private final List<String> WELL_KNOWN_CONTRACT_IDS =
-        List.of(CHALLENGE_PUBLISH, CHANNEL_PUBLISH, EMAIL_DEFAULT);
+        List.of(CHALLENGE_PUBLISH, CHANNEL_PUBLISH, EMAIL_DEFAULT, EMAIL_GLOBAL);
 
     private final InjectorContract injectorContract;
     private final List<AttackPatternComposer.Composer> attackPatternComposer = new ArrayList<>();