diff --git a/application/Espo/Classes/JobPreparators/CheckEmailAccounts.php b/application/Espo/Classes/JobPreparators/CheckEmailAccounts.php new file mode 100644 index 0000000000..2c06f685dd --- /dev/null +++ b/application/Espo/Classes/JobPreparators/CheckEmailAccounts.php @@ -0,0 +1,109 @@ +entityManager = $entityManager; + } + + public function prepare(Data $data, DateTimeImmutable $executeTime): void + { + $collection = $this->entityManager + ->getRDBRepository('EmailAccount') + ->join('assignedUser', 'assignedUserAdditional') + ->where([ + 'status' => 'Active', + 'useImap' => true, + 'assignedUserAdditional.isActive' => true, + ]) + ->find(); + + foreach ($collection as $entity) { + $running = $this->entityManager + ->getRDBRepository(JobEntity::ENTITY_TYPE) + ->where([ + 'scheduledJobId' => $data->getId(), + 'status' => [ + Status::RUNNING, + Status::READY, + ], + 'targetType' => 'EmailAccount', + 'targetId' => $entity->getId(), + ]) + ->findOne(); + + if ($running) { + continue; + } + + $countPending = $this->entityManager + ->getRDBRepository(JobEntity::ENTITY_TYPE) + ->where([ + 'scheduledJobId' => $data->getId(), + 'status' => Status::PENDING, + 'targetType' => 'EmailAccount', + 'targetId' => $entity->getId(), + ]) + ->count(); + + if ($countPending > 1) { + continue; + } + + $jobEntity = $this->entityManager->getEntity(JobEntity::ENTITY_TYPE); + + $jobEntity->set([ + 'name' => $data->getName(), + 'scheduledJobId' => $data->getId(), + 'executeTime' => $executeTime->format(DateTime::SYSTEM_DATE_TIME_FORMAT), + 'targetType' => 'EmailAccount', + 'targetId' => $entity->getId(), + ]); + + $this->entityManager->saveEntity($jobEntity); + } + } +} diff --git a/application/Espo/Classes/JobPreparators/CheckInboundEmails.php b/application/Espo/Classes/JobPreparators/CheckInboundEmails.php new file mode 100644 index 0000000000..1149ca1a24 --- /dev/null +++ b/application/Espo/Classes/JobPreparators/CheckInboundEmails.php @@ -0,0 +1,107 @@ +entityManager = $entityManager; + } + + public function prepare(Data $data, DateTimeImmutable $executeTime): void + { + $collection = $this->entityManager + ->getRDBRepository('InboundEmail') + ->where([ + 'status' => 'Active', + 'useImap' => true, + ]) + ->find(); + + foreach ($collection as $entity) { + $running = $this->entityManager + ->getRDBRepository(JobEntity::ENTITY_TYPE) + ->where([ + 'scheduledJobId' => $data->getId(), + 'status' => [ + Status::RUNNING, + Status::READY, + ], + 'targetType' => 'InboundEmail', + 'targetId' => $entity->getId(), + ]) + ->findOne(); + + if ($running) { + continue; + } + + $countPending = $this->entityManager + ->getRDBRepository(JobEntity::ENTITY_TYPE) + ->where([ + 'scheduledJobId' => $data->getId(), + 'status' => Status::PENDING, + 'targetType' => 'InboundEmail', + 'targetId' => $entity->getId(), + ]) + ->count(); + + if ($countPending > 1) { + continue; + } + + $jobEntity = $this->entityManager->getEntity(JobEntity::ENTITY_TYPE); + + $jobEntity->set([ + 'name' => $data->getName(), + 'scheduledJobId' => $data->getId(), + 'executeTime' => $executeTime->format(DateTime::SYSTEM_DATE_TIME_FORMAT), + 'targetType' => 'InboundEmail', + 'targetId' => $entity->getId(), + ]); + + $this->entityManager->saveEntity($jobEntity); + } + } +} diff --git a/application/Espo/Core/Job/Job/Jobs/AbstractGroupJob.php b/application/Espo/Core/Job/Job/Jobs/AbstractGroupJob.php new file mode 100644 index 0000000000..842008df90 --- /dev/null +++ b/application/Espo/Core/Job/Job/Jobs/AbstractGroupJob.php @@ -0,0 +1,61 @@ +jobManager = $jobManager; + $this->config = $config; + } + + public function run(Data $data): void + { + $limit = $this->config->get('jobGroupMaxPortion') ?? self::PORTION_NUMBER; + + $group = $data->get('group'); + + $this->jobManager->processGroup($group, $limit); + } +} diff --git a/application/Espo/Core/Job/AbstractQueueJob.php b/application/Espo/Core/Job/Job/Jobs/AbstractQueueJob.php similarity index 93% rename from application/Espo/Core/Job/AbstractQueueJob.php rename to application/Espo/Core/Job/Job/Jobs/AbstractQueueJob.php index b83301dd0d..91332d6fc2 100644 --- a/application/Espo/Core/Job/AbstractQueueJob.php +++ b/application/Espo/Core/Job/Job/Jobs/AbstractQueueJob.php @@ -27,9 +27,12 @@ * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. ************************************************************************/ -namespace Espo\Core\Job; +namespace Espo\Core\Job\Job\Jobs; use RuntimeException; +use Espo\Core\Job\JobDataLess; +use Espo\Core\Job\JobManager; +use Espo\Core\Job\QueuePortionNumberProvider; abstract class AbstractQueueJob implements JobDataLess { diff --git a/application/Espo/Core/Job/JobFactory.php b/application/Espo/Core/Job/JobFactory.php index 7daaf7598d..4f90d2dc83 100644 --- a/application/Espo/Core/Job/JobFactory.php +++ b/application/Espo/Core/Job/JobFactory.php @@ -76,26 +76,6 @@ class JobFactory return $job; } - /** - * Whether a job has prepare method. Prepare method creates job records from a scheduled job record. - * - * @throws Error - */ - public function isPreparable(string $name): bool - { - $className = $this->getClassName($name); - - if (!$className) { - throw new Error("Job '{$name}' not found."); - } - - if (method_exists($className, 'prepare')) { - return true; - } - - return false; - } - private function getClassName(string $name): ?string { return $this->classFinder->find('Jobs', ucfirst($name)); diff --git a/application/Espo/Core/Job/MetadataProvider.php b/application/Espo/Core/Job/MetadataProvider.php index 3cb0d3e66d..999f5e32a0 100644 --- a/application/Espo/Core/Job/MetadataProvider.php +++ b/application/Espo/Core/Job/MetadataProvider.php @@ -59,4 +59,14 @@ class MetadataProvider return $list; } -} \ No newline at end of file + + public function isJobPreparable(string $name): bool + { + return (bool) $this->metadata->get(['app', 'scheduledJobs', $name, 'isPreparable']); + } + + public function getPreparatorClassName(string $name): ?string + { + return $this->metadata->get(['app', 'scheduledJobs', $name, 'preparatorClassName']); + } +} diff --git a/application/Espo/Core/Job/JobPreparable.php b/application/Espo/Core/Job/Preparator.php similarity index 87% rename from application/Espo/Core/Job/JobPreparable.php rename to application/Espo/Core/Job/Preparator.php index ee9db0487a..d3653a0b20 100644 --- a/application/Espo/Core/Job/JobPreparable.php +++ b/application/Espo/Core/Job/Preparator.php @@ -29,15 +29,17 @@ namespace Espo\Core\Job; +use Espo\Core\Job\Preparator\Data; + use DateTimeImmutable; /** - * Can create multiple jobs for different targets according scheduling. + * Creates multiple jobs for different targets according scheduling. */ -interface JobPreparable extends Job +interface Preparator { /** * Create multiple job records for a scheduled job. */ - public function prepare(ScheduledJobData $data, DateTimeImmutable $executeTime): void; + public function prepare(Data $data, DateTimeImmutable $executeTime): void; } diff --git a/application/Espo/Core/Job/ScheduledJobData.php b/application/Espo/Core/Job/Preparator/Data.php similarity index 92% rename from application/Espo/Core/Job/ScheduledJobData.php rename to application/Espo/Core/Job/Preparator/Data.php index a66aa12f4a..1b2ceab19c 100644 --- a/application/Espo/Core/Job/ScheduledJobData.php +++ b/application/Espo/Core/Job/Preparator/Data.php @@ -27,9 +27,9 @@ * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. ************************************************************************/ -namespace Espo\Core\Job; +namespace Espo\Core\Job\Preparator; -class ScheduledJobData +class Data { private $id; @@ -41,11 +41,17 @@ class ScheduledJobData $this->name = $name; } + /** + * A scheduled job ID. + */ public function getId(): string { return $this->id; } + /** + * A scheduled job name. + */ public function getName(): string { return $this->name; diff --git a/application/Espo/Core/Job/AbstractGroupJob.php b/application/Espo/Core/Job/Preparator/Preparators/GroupPreparator.php similarity index 82% rename from application/Espo/Core/Job/AbstractGroupJob.php rename to application/Espo/Core/Job/Preparator/Preparators/GroupPreparator.php index 5ef55fd340..d290481d56 100644 --- a/application/Espo/Core/Job/AbstractGroupJob.php +++ b/application/Espo/Core/Job/Preparator/Preparators/GroupPreparator.php @@ -27,12 +27,12 @@ * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. ************************************************************************/ -namespace Espo\Core\Job; +namespace Espo\Core\Job\Preparator\Preparators; use Espo\Core\Utils\DateTime; -use Espo\Core\Utils\Config; -use Espo\Core\Job\Job\Data; use Espo\Core\Job\Job\Status; +use Espo\Core\Job\Preparator; +use Espo\Core\Job\Preparator\Data; use Espo\ORM\EntityManager; @@ -40,36 +40,16 @@ use Espo\Entities\Job as JobEntity; use DateTimeImmutable; -abstract class AbstractGroupJob implements JobPreparable +class GroupPreparator implements Preparator { - private const PORTION_NUMBER = 100; - - private $jobManager; - private $entityManager; - private $config; - - public function __construct( - JobManager $jobManager, - EntityManager $entityManager, - Config $config - ) { - $this->jobManager = $jobManager; - $this->entityManager = $entityManager; - $this->config = $config; - } - - public function run(Data $data): void + public function __construct(EntityManager $entityManager) { - $limit = $this->config->get('jobGroupMaxPortion') ?? self::PORTION_NUMBER; - - $group = $data->get('group'); - - $this->jobManager->processGroup($group, $limit); + $this->entityManager = $entityManager; } - public function prepare(ScheduledJobData $data, DateTimeImmutable $executeTime): void + public function prepare(Data $data, DateTimeImmutable $executeTime): void { $groupList = []; diff --git a/application/Espo/Core/Job/PreparatorFactory.php b/application/Espo/Core/Job/PreparatorFactory.php new file mode 100644 index 0000000000..aece213ac6 --- /dev/null +++ b/application/Espo/Core/Job/PreparatorFactory.php @@ -0,0 +1,62 @@ +metadataProvider = $metadataProvider; + $this->injectableFactory = $injectableFactory; + } + + /** + * Create a preparator. + * + * @throws Error + */ + public function create(string $name): Preparator + { + $className = $this->metadataProvider->getPreparatorClassName($name); + + if (!$className) { + throw new Error("Preparator for job '{$name}' not found."); + } + + return $this->injectableFactory->create($className); + } +} diff --git a/application/Espo/Core/Job/ScheduleProcessor.php b/application/Espo/Core/Job/ScheduleProcessor.php index 92c0422676..a30f186c86 100644 --- a/application/Espo/Core/Job/ScheduleProcessor.php +++ b/application/Espo/Core/Job/ScheduleProcessor.php @@ -29,6 +29,8 @@ namespace Espo\Core\Job; +use Espo\Core\Job\Preparator\Data as PreparatorData; + use Espo\Core\{ ORM\EntityManager, Utils\Log, @@ -70,20 +72,24 @@ class ScheduleProcessor private $scheduleUtil; - private $jobFactory; + private $metadataProvider; + + private $preparatorFactory; public function __construct( Log $log, EntityManager $entityManager, QueueUtil $queueUtil, ScheduleUtil $scheduleUtil, - JobFactory $jobFactory + PreparatorFactory $preparatorFactory, + MetadataProvider $metadataProvider ) { $this->log = $log; $this->entityManager = $entityManager; $this->queueUtil = $queueUtil; $this->scheduleUtil = $scheduleUtil; - $this->jobFactory = $jobFactory; + $this->preparatorFactory = $preparatorFactory; + $this->metadataProvider = $metadataProvider; } public function process(): void @@ -126,15 +132,15 @@ class ScheduleProcessor $jobName = $scheduledJob->getJob(); - if ($this->jobFactory->isPreparable($jobName)) { - $jobObj = $this->jobFactory->create($jobName); + if ($this->metadataProvider->isJobPreparable($jobName)) { + $preparator = $this->preparatorFactory->create($jobName); - $data = new ScheduledJobData($scheduledJob->getId(), $scheduledJob->getName()); + $data = new PreparatorData($scheduledJob->getId(), $scheduledJob->getName()); $executeTimeObj = DateTimeImmutable ::createFromFormat(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT, $executeTime); - $jobObj->prepare($data, $executeTimeObj); + $preparator->prepare($data, $executeTimeObj); return; } diff --git a/application/Espo/Jobs/CheckEmailAccounts.php b/application/Espo/Jobs/CheckEmailAccounts.php index 886305b9f8..6063bdc75a 100644 --- a/application/Espo/Jobs/CheckEmailAccounts.php +++ b/application/Espo/Jobs/CheckEmailAccounts.php @@ -32,19 +32,15 @@ namespace Espo\Jobs; use Espo\Core\Exceptions\Error; use Espo\Core\{ - Job\Job\Status, - Job\JobPreparable, + Job\Job, Job\Job\Data, - Job\ScheduledJobData, ServiceFactory, ORM\EntityManager, - Utils\DateTime, }; use Throwable; -use DateTimeImmutable; -class CheckEmailAccounts implements JobPreparable +class CheckEmailAccounts implements Job { private $serviceFactory; @@ -85,62 +81,4 @@ class CheckEmailAccounts implements JobPreparable ); } } - - public function prepare(ScheduledJobData $data, DateTimeImmutable $executeTime): void - { - $collection = $this->entityManager - ->getRDBRepository('EmailAccount') - ->join('assignedUser', 'assignedUserAdditional') - ->where([ - 'status' => 'Active', - 'useImap' => true, - 'assignedUserAdditional.isActive' => true, - ]) - ->find(); - - foreach ($collection as $entity) { - $running = $this->entityManager - ->getRDBRepository('Job') - ->where([ - 'scheduledJobId' => $data->getId(), - 'status' => [ - Status::RUNNING, - Status::READY, - ], - 'targetType' => 'EmailAccount', - 'targetId' => $entity->getId(), - ]) - ->findOne(); - - if ($running) { - continue; - } - - $countPending = $this->entityManager - ->getRDBRepository('Job') - ->where([ - 'scheduledJobId' => $data->getId(), - 'status' => Status::PENDING, - 'targetType' => 'EmailAccount', - 'targetId' => $entity->getId(), - ]) - ->count(); - - if ($countPending > 1) { - continue; - } - - $jobEntity = $this->entityManager->getEntity('Job'); - - $jobEntity->set([ - 'name' => $data->getName(), - 'scheduledJobId' => $data->getId(), - 'executeTime' => $executeTime->format(DateTime::SYSTEM_DATE_TIME_FORMAT), - 'targetType' => 'EmailAccount', - 'targetId' => $entity->getId(), - ]); - - $this->entityManager->saveEntity($jobEntity); - } - } } diff --git a/application/Espo/Jobs/CheckInboundEmails.php b/application/Espo/Jobs/CheckInboundEmails.php index 24e008b630..0965b3f68a 100644 --- a/application/Espo/Jobs/CheckInboundEmails.php +++ b/application/Espo/Jobs/CheckInboundEmails.php @@ -32,19 +32,15 @@ namespace Espo\Jobs; use Espo\Core\Exceptions\Error; use Espo\Core\{ - Job\Job\Status, - Job\JobPreparable, + Job\Job, Job\Job\Data, - Job\ScheduledJobData, ServiceFactory, ORM\EntityManager, - Utils\DateTime, }; use Throwable; -use DateTimeImmutable; -class CheckInboundEmails implements JobPreparable +class CheckInboundEmails implements Job { private $serviceFactory; @@ -85,60 +81,4 @@ class CheckInboundEmails implements JobPreparable ); } } - - public function prepare(ScheduledJobData $data, DateTimeImmutable $executeTime): void - { - $collection = $this->entityManager - ->getRDBRepository('InboundEmail') - ->where([ - 'status' => 'Active', - 'useImap' => true, - ]) - ->find(); - - foreach ($collection as $entity) { - $running = $this->entityManager - ->getRDBRepository('Job') - ->where([ - 'scheduledJobId' => $data->getId(), - 'status' => [ - Status::RUNNING, - Status::READY, - ], - 'targetType' => 'InboundEmail', - 'targetId' => $entity->getId(), - ]) - ->findOne(); - - if ($running) { - continue; - } - - $countPending = $this->entityManager - ->getRDBRepository('Job') - ->where([ - 'scheduledJobId' => $data->getId(), - 'status' => Status::PENDING, - 'targetType' => 'InboundEmail', - 'targetId' => $entity->getId(), - ]) - ->count(); - - if ($countPending > 1) { - continue; - } - - $jobEntity = $this->entityManager->getEntity('Job'); - - $jobEntity->set([ - 'name' => $data->getName(), - 'scheduledJobId' => $data->getId(), - 'executeTime' => $executeTime->format(DateTime::SYSTEM_DATE_TIME_FORMAT), - 'targetType' => 'InboundEmail', - 'targetId' => $entity->getId(), - ]); - - $this->entityManager->saveEntity($jobEntity); - } - } } diff --git a/application/Espo/Jobs/ProcessJobGroup.php b/application/Espo/Jobs/ProcessJobGroup.php index 99519c3aed..ad07ab9727 100644 --- a/application/Espo/Jobs/ProcessJobGroup.php +++ b/application/Espo/Jobs/ProcessJobGroup.php @@ -29,6 +29,6 @@ namespace Espo\Jobs; -use Espo\Core\Job\AbstractGroupJob; +use Espo\Core\Job\Job\Jobs\AbstractGroupJob; class ProcessJobGroup extends AbstractGroupJob {} diff --git a/application/Espo/Jobs/ProcessJobQueueE0.php b/application/Espo/Jobs/ProcessJobQueueE0.php index d0e75060e3..888ef19a0f 100644 --- a/application/Espo/Jobs/ProcessJobQueueE0.php +++ b/application/Espo/Jobs/ProcessJobQueueE0.php @@ -31,7 +31,7 @@ namespace Espo\Jobs; use Espo\Core\{ Job\QueueName, - Job\AbstractQueueJob, + Job\Job\Jobs\AbstractQueueJob, }; class ProcessJobQueueE0 extends AbstractQueueJob diff --git a/application/Espo/Jobs/ProcessJobQueueQ0.php b/application/Espo/Jobs/ProcessJobQueueQ0.php index f2c9b74ffe..4411ab3788 100644 --- a/application/Espo/Jobs/ProcessJobQueueQ0.php +++ b/application/Espo/Jobs/ProcessJobQueueQ0.php @@ -31,7 +31,7 @@ namespace Espo\Jobs; use Espo\Core\{ Job\QueueName, - Job\AbstractQueueJob, + Job\Job\Jobs\AbstractQueueJob, }; class ProcessJobQueueQ0 extends AbstractQueueJob diff --git a/application/Espo/Jobs/ProcessJobQueueQ1.php b/application/Espo/Jobs/ProcessJobQueueQ1.php index 338830d155..f0ec088d35 100644 --- a/application/Espo/Jobs/ProcessJobQueueQ1.php +++ b/application/Espo/Jobs/ProcessJobQueueQ1.php @@ -31,7 +31,7 @@ namespace Espo\Jobs; use Espo\Core\{ Job\QueueName, - Job\AbstractQueueJob, + Job\Job\Jobs\AbstractQueueJob, }; class ProcessJobQueueQ1 extends AbstractQueueJob diff --git a/application/Espo/Resources/metadata/app/scheduledJobs.json b/application/Espo/Resources/metadata/app/scheduledJobs.json index 1a1f3caada..d1d59780ba 100644 --- a/application/Espo/Resources/metadata/app/scheduledJobs.json +++ b/application/Espo/Resources/metadata/app/scheduledJobs.json @@ -3,7 +3,8 @@ "name": "Process Job Group", "isSystem": true, "scheduling": "* * * * *", - "isPreparable": true + "isPreparable": true, + "preparatorClassName": "Espo\\Core\\Job\\Preparator\\Preparators\\GroupPreparator" }, "ProcessJobQueueQ0": { "name": "Process Job Queue q0", @@ -35,9 +36,11 @@ "scheduling": "25 5 * * *" }, "CheckEmailAccounts": { - "isPreparable": true + "isPreparable": true, + "preparatorClassName": "Espo\\Classes\\JobPreparators\\CheckEmailAccounts" }, "CheckInboundEmails": { - "isPreparable": true + "isPreparable": true, + "preparatorClassName": "Espo\\Classes\\JobPreparators\\CheckInboundEmails" } }