Producer

📌 Purpose

The CampaignExecutionProducer component is responsible for automatically creating CampaignExecution instances based on a preconfigured schedule. It periodically checks campaigns with status SCHEDULED and triggers execution if the time has come.


🔧 Class: CampaignExecutionProducer

@Component
public class CampaignExecutionProducer {

    @Autowired
    private CampaignRepository campaignRepository;
    @Autowired
    private CampaignExecutionService executionService;
    @Autowired
    private TransactionTemplateBuilder ttb;

    @SchedulerLock(name = "CampaignExecutionProducer_produce", lockAtMostFor = "PT5M", lockAtLeastFor = "PT30S")
    @Scheduled(initialDelay = 120_000, fixedDelayString = "${campaign.execution.producer.fixedDelay}")
    public void produce() {
        final LocalDateTime now = LocalDateTime.now(ZoneId.systemDefault());
        campaignRepository.findAutomaticScheduledCampaigns()
            .forEach(id -> {
                ttb.requiresNew().executeWithoutResult(s -> {
                    Campaign campaign = campaignRepository.getSync(id);
                    if (campaign.isActive()
                        && campaign.getAutomaticCampaignStatus() == AutomaticCampaignStatus.SCHEDULED
                        && isWaitNewExecution(campaign, now)) {
                        executionService.createExecution(campaign);
                        campaignRepository.saveAndFlush(campaign);
                    }
                });
            });
    }

    // Helper methods below...
}

Execution Frequency

The @Scheduled annotation launches the produce() method at intervals defined in configuration:

campaign.execution.producer.fixedDelay=60000  # Example: every 60 seconds

The @SchedulerLock annotation (via Shedlock) ensures the method runs in only one service instance in a clustered deployment.


🧠 Conditions for Creating a New CampaignExecution

The produce() method creates a new execution only if all of the following are true:

  • The campaign is active

  • Its status is SCHEDULED

  • The current time now is equal to or after dateTimeExecution

  • Depending on the restartable flag, one of the following applies:

    • All previous executions are DISABLED

    • The next scheduled interval has arrived (based on restart configuration)


🔁 Support for restartable Campaigns

✅ If restartable = false

private boolean isCreateForNotRestartable(Campaign campaign) {
    return !campaign.isRestartable()
        && campaign.getExecutions().stream().allMatch(e -> e.getStatus() == DISABLED);
}

Only one execution is allowed — further executions require all previous ones to be disabled.


🔁 If restartable = true

private boolean isCreateForRestartable(Campaign campaign, LocalDateTime now) {
    Duration interval = campaign.getRestartableDuration(); // hours, days
    LocalDateTime current = campaign.getDateTimeExecution();

    while (!current.plus(interval).isAfter(now)) {
        current = current.plus(interval);
    }

    LocalDateTime nextStart = current;

    return campaign.getExecutions().stream().allMatch(e -> ofInstant(e.getCreatedAt()).isBefore(nextStart))
        || campaign.getExecutions().stream().filter(e -> !ofInstant(e.getCreatedAt()).isBefore(nextStart))
           .allMatch(e -> e.getStatus() == DISABLED);
}

This enables cyclic execution (e.g., once every hour/day) based on restartableUnit and restartableUnitType.


🧩 Helper Method

private boolean isWaitNewExecution(Campaign campaign, LocalDateTime now) {
    return (now.isEqual(campaign.getDateTimeExecution()) || now.isAfter(campaign.getDateTimeExecution()))
        && (isCreateForNotRestartable(campaign) || isCreateForRestartable(campaign, now));
}

This method consolidates all logic for determining whether a new execution should be created.


📝 Example Behavior

Campaign A:

  • executionType = AUTOMATIC

  • dateTimeExecution = 2025-05-01T09:00

  • restartable = true

  • restartableUnitType = HOURS, restartableUnit = 6

Producer output:

  • May 1, 09:00 → first execution

  • May 1, 15:00 → second execution

  • May 1, 21:00 → third execution

  • ...


📌 Summary

  • CampaignExecutionProducer is an automatic campaign scheduler

  • Controlled via @Scheduled and @SchedulerLock

  • Checks campaign status, activation, execution time, and recurrence settings

  • Operates in a new isolated transaction, preventing interference with outer processes

Last updated

Was this helpful?