job preparator

This commit is contained in:
Yuri Kuznetsov
2021-09-22 16:55:16 +03:00
parent a64a66912f
commit 1fbdd2edf4
18 changed files with 401 additions and 194 deletions

View File

@@ -0,0 +1,109 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\JobPreparators;
use Espo\Core\Utils\DateTime;
use Espo\Core\Job\Job\Status;
use Espo\Core\Job\Preparator;
use Espo\Core\Job\Preparator\Data;
use Espo\ORM\EntityManager;
use Espo\Entities\Job as JobEntity;
use DateTimeImmutable;
class CheckEmailAccounts implements Preparator
{
private $entityManager;
public function __construct(EntityManager $entityManager)
{
$this->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);
}
}
}

View File

@@ -0,0 +1,107 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\JobPreparators;
use Espo\Core\Utils\DateTime;
use Espo\Core\Job\Job\Status;
use Espo\Core\Job\Preparator;
use Espo\Core\Job\Preparator\Data;
use Espo\ORM\EntityManager;
use Espo\Entities\Job as JobEntity;
use DateTimeImmutable;
class CheckInboundEmails implements Preparator
{
private $entityManager;
public function __construct(EntityManager $entityManager)
{
$this->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);
}
}
}

View File

@@ -0,0 +1,61 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Job\Job\Jobs;
use Espo\Core\Utils\Config;
use Espo\Core\Job\Job;
use Espo\Core\Job\Job\Data;
use Espo\Core\Job\JobManager;
abstract class AbstractGroupJob implements Job
{
private const PORTION_NUMBER = 100;
private $jobManager;
private $config;
public function __construct(
JobManager $jobManager,
Config $config
) {
$this->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);
}
}

View File

@@ -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
{

View File

@@ -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));

View File

@@ -59,4 +59,14 @@ class MetadataProvider
return $list;
}
}
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']);
}
}

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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 = [];

View File

@@ -0,0 +1,62 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Job;
use Espo\Core\InjectableFactory;
use Espo\Core\Exceptions\Error;
class PreparatorFactory
{
private $metadataProvider;
private $injectableFactory;
public function __construct(MetadataProvider $metadataProvider, InjectableFactory $injectableFactory)
{
$this->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);
}
}

View File

@@ -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;
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -29,6 +29,6 @@
namespace Espo\Jobs;
use Espo\Core\Job\AbstractGroupJob;
use Espo\Core\Job\Job\Jobs\AbstractGroupJob;
class ProcessJobGroup extends AbstractGroupJob {}

View File

@@ -31,7 +31,7 @@ namespace Espo\Jobs;
use Espo\Core\{
Job\QueueName,
Job\AbstractQueueJob,
Job\Job\Jobs\AbstractQueueJob,
};
class ProcessJobQueueE0 extends AbstractQueueJob

View File

@@ -31,7 +31,7 @@ namespace Espo\Jobs;
use Espo\Core\{
Job\QueueName,
Job\AbstractQueueJob,
Job\Job\Jobs\AbstractQueueJob,
};
class ProcessJobQueueQ0 extends AbstractQueueJob

View File

@@ -31,7 +31,7 @@ namespace Espo\Jobs;
use Espo\Core\{
Job\QueueName,
Job\AbstractQueueJob,
Job\Job\Jobs\AbstractQueueJob,
};
class ProcessJobQueueQ1 extends AbstractQueueJob

View File

@@ -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"
}
}