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
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 afterdateTimeExecution
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
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 schedulerControlled 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?