The CampaignExecutionService
is responsible for all aspects of campaign execution, including:
Creating CampaignExecution
entities
Filtering clients using an Expression
This service integrates with ScriptManager
, ClientRepository
, and ExceptionEntityService
.
🔧 CampaignExecution createExecution(Campaign campaign)
Creates a new CampaignExecution
linked to the provided campaign.
@Transactional(propagation = Propagation.MANDATORY)
public CampaignExecution createExecution(Campaign campaign) {
CampaignExecution execution = new CampaignExecution();
execution.setCampaign(campaign);
return executionRepository.saveAndFlush(execution);
}
Called both manually (via CampaignService
) and automatically (via CampaignExecutionProducer
)
Saves the execution to the DB with status = NEW
🔐 _runExecution(CampaignExecution execution)
Private method containing the main business logic for campaign execution.
private void _runExecution(CampaignExecution execution) {
try {
Set<Client> clients = getSuitedBorrowers(execution, clientRepository.getAllIds());
execution.getClients().addAll(clients);
execution.setException(null);
} catch (Exception e) {
ExceptionEntity exceptionEntity = exceptionEntityService.saveException(e, e.getMessage());
execution.setException(exceptionEntity);
execution.setStatus(CampaignExecutionStatus.EXCEPTION_OCCURRED);
}
execution.setStatus(CampaignExecutionStatus.FINISHED);
executionRepository.saveAndFlush(execution);
}
What it does:
Retrieves a list of clients matching the Expression
Clears the exception if successful
On error, stores an ExceptionEntity
and marks the execution as EXCEPTION_OCCURRED
Sets the final status to FINISHED
🔍 Set<Client> getSuitedBorrowers(...)
Filters clients based on the campaign expression.
private Set<Client> getSuitedBorrowers(CampaignExecution execution, Set<UUID> candidates) throws ScriptException {
Script matchingScript = getClientMatchingScript(execution.getCampaign().getExpression());
Set<Client> suitedBorrowers = new HashSet<>();
for (UUID clientId : candidates) {
Optional<Client> client = evaluateClient(matchingScript, clientId);
client.ifPresent(suitedBorrowers::add);
}
return suitedBorrowers;
}
🔍 Optional<Client> evaluateClient(...)
Evaluates a single client using the script.
private Optional<Client> evaluateClient(Script script, UUID clientId) throws ScriptException {
Client client = clientRepository.getReferenceById(clientId);
Object result = script.eval(Map.of("client", client));
return Boolean.TRUE.equals(result) ? Optional.of(client) : Optional.empty();
}
The script must return true
or false
Only matching clients are included
🧰 Script getClientMatchingScript(...)
Compiles the Expression into a Script
.
private Script getClientMatchingScript(Expression expression) {
try {
return scriptManager.compile(expression);
} catch (ScriptException e) {
throw new RuntimeException("Script doesn't compile", e);
}
}
Throws a RuntimeException
on compilation failure
💡 Object evaluateExpression(UUID clientId, Expression expression)
Allows manual evaluation of an expression for a single client — useful in the UI.
@Transactional
public Object evaluateExpression(UUID clientId, Expression expression)
throws ScriptException, NotFoundException {
Client client = clientRepository.getReferenceById(clientId);
Script script = scriptManager.compile(expression);
return script.eval(Map.of("client", client));
}
Used by CampaignController
for real-time filtering checks in the UI
Example service
@Service
public class CampaignExecutionService {
private static final String CLIENT_KEY = "client";
@Autowired
private CampaignExecutionRepository executionRepository;
@Autowired
private ClientRepository clientRepository;
@Autowired
private ScriptManager scriptManager;
@Autowired
private ExceptionEntityService exceptionEntityService;
@Transactional(propagation = Propagation.MANDATORY)
public CampaignExecution createExecution(Campaign campaign) {
CampaignExecution execution = new CampaignExecution();
execution.setCampaign(campaign);
return executionRepository.saveAndFlush(execution);
}
@Transactional(propagation = Propagation.MANDATORY)
public void runExecution(CampaignExecution execution) {
_runExecution(execution);
}
@Transactional
public void runExecution(Long executionId) {
CampaignExecution execution = executionRepository.getSync(executionId);
_runExecution(execution);
}
private void _runExecution(CampaignExecution execution) {
try {
Set<Client> clients = getSuitedBorrowers(execution, clientRepository.getAllIds());
execution.getClients().addAll(clients);
execution.setException(null);
} catch (Exception e) {
ExceptionEntity exceptionEntity = exceptionEntityService.saveException(e, e.getMessage());
execution.setException(exceptionEntity);
execution.setStatus(CampaignExecutionStatus.EXCEPTION_OCCURRED);
}
execution.setStatus(CampaignExecutionStatus.FINISHED);
executionRepository.saveAndFlush(execution);
}
private Set<Client> getSuitedBorrowers(CampaignExecution execution, Set<UUID> candidates) throws ScriptException {
Script matchingScript = getClientMatchingScript(execution.getCampaign().getExpression());
Set<Client> suitedBorrowers = new HashSet<>();
for (UUID clientId : candidates) {
Optional<Client> client = evaluateClient(matchingScript, clientId);
client.ifPresent(suitedBorrowers::add);
}
return suitedBorrowers;
}
private Optional<Client> evaluateClient(Script script, UUID clientId) throws ScriptException {
Client client = clientRepository.getReferenceById(clientId);
Object evaluationResult = script.eval(Map.of(CLIENT_KEY, client));
return Boolean.TRUE.equals(evaluationResult) ? Optional.of(client) : Optional.empty();
}
private Script getClientMatchingScript(Expression expression) {
try {
return scriptManager.compile(expression);
} catch (ScriptException e) {
throw new RuntimeException("Script doesn't compile", e);
}
}
@Transactional
public Object evaluateExpression(UUID clientId, Expression expression) throws ScriptException, NotFoundException {
Client client = clientRepository.getReferenceById(clientId);
Script script = scriptManager.compile(expression);
Map<String, Client> binding = Map.of(CLIENT_KEY, client);
return script.eval(binding);
}
}