Автоматизація процесу внесення в розклад і видалення з розкладу запланованих задач

Мабуть, кожен програміст на платформі Salesforce стикався з тим, що під час процесу розгортання (діплою – deploy) на продакшин [production] чи на сендбокс [sandbox – пісочниця], іноді виникає потреба видалити з розкладу заплановані задачі (шедулед джобс – scheduled jobs) через те, що платформа Salesforce не дозволяє здійснювати процес розгортання (діплою) чи будь-якої зміни сирцевих файлів (сурс-коду), які викликаються в запланованих задачах.
Якщо на проекті можуть відбуватися часті діплої, які вносять зміни у файли, що зав’язані з запланованими задачами, то це спричиняє незручності через те, що доводиться вручну скасовувати заплановані задачі, а потім після діплою, знову їх вносити в розклад.
Якщо є багато завдань, які треба зробити, іноді можна просто забути після довготривалого процесу діплоєння внести знову в розклад заплановані задачі, які були видалені з розкладу.

Тому виникає природнє бажання написати якоюсь сторінку-пейджу щоб там можна було б вносити в розклад (шедулити) або видаляти з розкладу (канселити) заплановані задачі (шедулабл джоби). Виявляється, можна не винаходити велосипед, бо існує пакет, який розв’язує цю проблему і декілька інших, з якими я особисто не стикався, ось посилання на тему обговорення:
http://zachelrath.wordpress.com/2012/06/28/relax-your-batch-scheduling-woes-are-over/
Ось посилання для того, щоб інсталювати останню версію цього пакету на момент публікації цієї статті:
https://login.salesforce.com/packaging/installPackage.apexp?p0=04tE0000000ISOx
Також сирцевий код можна знайти на ґітгабі:
https://github.com/zachelrath/Relax
Але, як на мене, там трохи забагато функціональності в порівнянні з тим, що мені особисто хотілося б зробити. Хоча, можливо, когось цей пакет зацікавить.
Натомість, з іншого боку, цей пакет не вирішує питання взаємодії з існуючими запланованими задачами. Тобто, він дозволяє створювати свої додаткові об’єкти, з яких можна вносити в розклад заплановані задачі, але цей пакет не вміє збирати інформацію про існуючі заплановані задачі і перетворювати їх в свої об’єкти.
Отож, все таки, для задоволення своїх потреб мені таки довелося написати власну сторінку, яка дозволяє виконувати наступні операції:
1. Зібрати інформацію про всі заплановані задачі незалежно від того, чи вони були внесені в розклад за допомогою цієї сторінки чи одним із стандартних способів.
2. Масово скасовувати з розкладу заплановані задачі або масово вносити їх в розклад за допомогою вибирання (чЕкання) відповідних чекбоксів і натискання відповідної кнопки.

Коротко розповім про те, як відбувається внесення до розкладу і видалення з розкладу.
Оскільки платформа Salesforce не надає методів для масового додавання в розклад\видалення з розкладу, доводиться викликати існуючі методи для додавання в розклад\видалення з розкладу однієї запланованої задачі в циклі.

Йдеться про метод System.schedule та про метод System.abortJob.
Тут подана специфікація методу додавання в розклад однієї запланованої задачі:
http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_System_System_schedule.htm
Зокрема, цей метод має таку сигнатуру:
public static String schedule(String JobName, String CronExpression, Object schedulable_class)
Отже, він має три параметри, перший – це ім’я запланованої задачі, другий – це вираз, який задає коли має викликатися задача, і третій – це, власне, посилання на інстанс класу, який має викликатися, його клас мусить імплементувати інтерфейс Schedulable, який має один обов’язковий метод global void execute(SchedulableContext SC), який викликається тоді, коли спрацьовують умови з виразу CronExpression.
Цей метод повертає значення айдішки запланованої задачі (айді об’єкту CronTrigger).
Ось документація методу видалення з розкладу однієї запланованої задачі:
http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_System_System_abortJob.htm
Він має таку сигнатуру:
public static Void abortJob(String Job_ID)
Єдиний параметр – це айдішка об’єкта CronTrigger або AsyncApexJob.

Окрема тема для розмови це те, що до недавнього часу платформа Salesforce не надавала доступу до назви запланованої задачі (яка передається першим аргументом в метод System.schedule). Однак, починаючи з версії 29 в релізі Зима 2014 був відкритий доступ до типу і назви запланованої задачі. Про це можна прочитати тут: http://docs.releasenotes.salesforce.com/en-us/winter14/release-notes/rn_186_apex_job_name_type.htm.

Хоча був наданий доступ до типу і назви запланованої задачі, так і не був наданий безпосередній доступ до класу запланованої задачі, який має викликатися.
Не зважаючи на це, я знайшов спосіб дістатися до нього. У платформі Salesforce насправді існують два різні вбудовані об’єкти бази даних (насправді, сейлзфорсівські об’єкти це просто об’єктна надбудова над записами в табличці, можна собі їх просто трактувати як таблички або як рядки в табличці), які відповідають за заплановані задачі, і які створюються після додавання до розкладу запланованої задачі. Це CronTrigger і AsyncApexJob. Оскільки айдішка класу запланованої задачі, що повинен викликатися відсутня в об’єкті CronTrigger як поле, тому доводиться її брати з класу AsyncApexJob, де вона присутня. Єдина проблема тут – це як заджойнати, тобто, як поєднати записи з цих двох табличок. Оскільки ці дві таблички ніяк не поєднані між собою, я вигадав хитрий спосіб, як їх можна поєднати між собою, який працює на ура, якщо запланована задача була додана в розклад користувачем або програмно але неодночасно з іншою задачею в ту ж саму секунду. Обидві таблички мають стандартне поле CreatedDate, в який записується час створення запису з точністю до секунди. Оскільки обидва об’єкти створюються одночасно, вони завжди мають однаковий час створення запису. І я використовую це поле для того, аби витягнути айді класу, який має викликатися, а з нього, відповідно за допомогою методу Type.forName витягую клас, і створюю новий інстанс цього класу методом newInstance(), і передаю його третім параметром в метод System.schedule.

Щодо видалення з розкладу, то тут з одного боку нібито простіше, бо треба передавати тільки один параметр, а, з другого боку, знову ж таки, не зрозуміло, якому об’єкту треба робити аборт: інстансу CronTrigger чи інстансу AsyncApexJob.
Спочатку я робив аборт обом об’єктам, тобто, двічі викликав метод System.abortJob для айдішки об’єкта CronTrigger та айдішки об’єкта AsyncApexJob.
Але потім я виявив у своїй сторінці баґу, яка могла неправильно перепризначати айдішку об’єкта AsyncApexJob для мого об’єкта, який, згідно мого задуму, повинен містити айдішки об’єктів CronTrigger та AsyncApexJob, що стосуються певної запланованої задачі. Відповідно, міг робитися аборт для об’єкта AsyncApexJob, який насправді не відповідав запланованій задачі, яку видаляють з розкладу. Хоча, наскільки я помітив, це не спричиняло невиконання запланованої задачі. З іншого боку, якщо зробити аборт тільки для об’єкта CronTrigger, відповідний об’єкт AsyncApexJob також отримує статус абортованого, отже, звідки слідує те, що можна викликати метод System.abortJob тільки для айдішки об’єкта CronTrigger.

Відповідно, загальна інформація збирається наступним чином. Якщо запланована задача була зашедулена (внесена до розкладу) за допомогою моєї сторінки, то повинен існувати запис в моєму об’єкті Scheduled_Job__c зі значенням true поля Active__c.
Тому, найперше, я витягую всі внесені до розкладу заплановані задачі, роблячи запит до об’єкта CronTrigger з фільтром за значення поля CronJobDetail.JobType = ‘7’. Значення 7 означає запланована задача, згідно з документацією: http://www.salesforce.com/us/developer/docs/api/Content/sforce_api_objects_cronjobdetail.htm; http://docs.releasenotes.salesforce.com/en-us/winter14/release-notes/rn_186_apex_job_name_type.htm .

Далі я роблю запит до мого власного кастомного об’єкті Scheduled_Job__c, і фільтрую отримані існуючі рядки з таблички CronTrigger, для яких нема рядків у моєму об’єкті Scheduled_Job__c з відповідним полем CronTriggerId__c, яке відповідає айдішці об’єкту CronTrigger.

Для відфільтрованих рядків з таблички CronTrigger шукаю відповідні записи з об’єкту AsyncApexJob, пов’язуючи їх між собою по даті створення.
Потім я створюю новий об’єкт Scheduled_Job__c, в який записую необхідні дані, отримані з об’єктів CronTrigger і AsyncApexJob. Найважливіші – це CronJobDetail.Name і CronExpression з CronTrigger і ApexClassId з AsyncApexJob. Решту інформації я отримую також з таблички ApexClass, з якої можна отримати по айдішці класу його ім’я і префікс простору імен (Name і NamespacePrefix), їх я об’єдную через крапку якщо префікс не порожній для отримання повного імені класу, яке я передаю у метод Type.forName, коли потрібно повторно внести до розкладу скасовану раніше планову задачу.

Якщо хтось знає елегантніший спосіб об’єдання табличок CronTrigger і AsyncApexJob або знає, як виправити помилку призначення неправильної айдішки об’єкта AsyncApexJob, коли одночасно в одну секунду вносяться до розкладу дві планові задачі, що мають однакову айдішку класу (не роблячи затримок в одну секунду між повторним внесенням до розкладу), то відпишіть в коментах.

This entry was posted in Posts in Ukrainian, salesforce and tagged , , , , , , , , , , , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s