Quartzを利用するカスタムタスクスケジューリング - Quartzを利用するカスタムタスクスケジューリング - aegif Labo Blog Liferay
null Quartzを利用するカスタムタスクスケジューリング
こんにちは、サムエルです。
今回はLiferay上でのタスクスケジューラーについて話したいと思います。
リマインダーなどで事前に登録しておいた情報を指定したタイミング・日付に発行したいことはあると思います。その時、タスクスケジューラーが役に立ちます。
タスクスケジューラの内容
LiferayのタスクスケジューラーはQuartzをラッピングしたものです。基本的には3つの部分で構成されています。
Activate/Modified
Activate/ModifiedはOSGiのモジュールがロードされた時の処理であり、ここで、タスクのトリガー、チェック間隔、ストレージタイプなどを登録します。
Deactivate
DeactivateはOSGiのモジュールが停止・アンロードされた時の処理です。この時、スケジュールを停止・登録から解除します。
doReceive()関数
タスクスケジューラが発行タイミングになったら呼ばれるコールバック関数です。ここから必要な処理(メール送信やサービス呼び出しなど)を行います。
使い方
以下にタスクスケジューラのサンプル(Liferay DXP 7.x環境)です。コードスニッペットの配下に説明します。
TaskListener.java
@Component(
immediate = true,
service = TaskListener.class
)
public class TaskListener extends BaseMessageListener {
public static int CHECK_INTERVAL = 5;
@Override
protected void doReceive(Message message) throws Exception {
// コールバック時に実施する処理
}
@Activate
@Modified
protected void activate() throws SchedulerException {
// ジョブのトリガーを作成する
String listenerClass = getClass().getName();
Trigger jobTrigger = _triggerFactory.createTrigger(listenerClass, listenerClass, new Date(), null,
CHECK_INTERVAL, TimeUnit.SECOND);
// スケジュールエントリーをラッピングする
_schedulerEntryImpl = new SchedulerEntryImpl(listenerClass, jobTrigger);
_schedulerEntryImpl = new StorageTypeAwareSchedulerEntryImpl(_schedulerEntryImpl, StorageType.PERSISTED);
// Configuration Admin による更新を対応するため、
// 一度unregisterとregisterを行うか、ジョブの更新を行う必要がある。
// [再登録する場合]
if (_initialized) {
// 一度スケジュールを停止させる
_log.info("TaskListener: deactivate before scheduling");
deactivate();
}
// スケジュールを登録する
_schedulerEngineHelper.register(this, _schedulerEntryImpl, DestinationNames.SCHEDULER_DISPATCH);
// [更新する場合]
// if (_initialized) {
// // 更新で重複ジョブが存在する場合、アップデートする
// for (SchedulerResponse sr : _schedulerEngineHelper.getScheduledJobs()) {
// _log.info("TaskListener: job=" + sr.getJobName());
// if (sr.getJobName().equals(listenerClass)) {
// _log.info("TaskListener: Duplicated job, update: " + sr.getJobName());
// _schedulerEngineHelper.update(jobTrigger, sr.getStorageType());
// }
// }
// } else {
// // スケジュールを登録する
// _schedulerEngineHelper.register(this, _schedulerEntryImpl, DestinationNames.SCHEDULER_DISPATCH);
// }
_initialized = true;
}
@Deactivate
protected void deactivate() {
if (_initialized) {
// スケジュールを停止させる
try {
_schedulerEngineHelper.unschedule(_schedulerEntryImpl, getStorageType());
} catch (SchedulerException se) {
if (_log.isWarnEnabled()) {
_log.warn("TaskListener: Unable to unschedule trigger", se);
}
}
// スケジュールの登録を削除する
_schedulerEngineHelper.unregister(this);
}
_initialized = false;
}
protected StorageType getStorageType() {
if (_schedulerEntryImpl instanceof StorageTypeAware) {
return ((StorageTypeAware) _schedulerEntryImpl).getStorageType();
}
return StorageType.MEMORY_CLUSTERED;
}
@Reference(target = ModuleServiceLifecycle.PORTAL_INITIALIZED, unbind = "-")
protected void setModuleServiceLifecycle(ModuleServiceLifecycle moduleServiceLifecycle) {
}
@Reference(unbind = "-")
protected void setTriggerFactory(TriggerFactory triggerFactory) {
_triggerFactory = triggerFactory;
}
@Reference(unbind = "-")
protected void setSchedulerEngineHelper(SchedulerEngineHelper schedulerEngineHelper) {
_schedulerEngineHelper = schedulerEngineHelper;
}
private static final Log _log = LogFactoryUtil.getLog(TaskListener.class);
private volatile boolean _initialized = false;
private TriggerFactory _triggerFactory;
private SchedulerEngineHelper _schedulerEngineHelper;
private SchedulerEntryImpl _schedulerEntryImpl = null;
}
StorageTypeAwareSchedulerEntryImpl.java
このクラスは、ジョブのStorageTypeを設定するために必要です。
public class StorageTypeAwareSchedulerEntryImpl extends SchedulerEntryImpl implements SchedulerEntry, StorageTypeAware {
/**
* StorageTypeAwareSchedulerEntryImpl: Constructor for the class.
* @param schedulerEntry
* @param storageType
*/
public StorageTypeAwareSchedulerEntryImpl(final SchedulerEntryImpl schedulerEntry, final StorageType storageType) {
super(schedulerEntry.getEventListenerClass(), schedulerEntry.getTrigger());
_schedulerEntry = schedulerEntry;
_storageType = storageType;
}
@Override
public String getDescription() {
return _schedulerEntry.getDescription();
}
@Override
public String getEventListenerClass() {
return _schedulerEntry.getEventListenerClass();
}
@Override
public StorageType getStorageType() {
return _storageType;
}
@Override
public Trigger getTrigger() {
return _schedulerEntry.getTrigger();
}
private SchedulerEntryImpl _schedulerEntry;
private StorageType _storageType;
}
activate関数の注意点
activate関数ではジョブを登録しますが、システムの異常停止などでdeactivateが呼ばれない可能性があるため、一度ジョブのチェック・再登録をお勧めします。また、オーバーライドによって更新が発生する場合に備え、ジョブの再登録または更新処理が必要です。(_initializedの判定参照)
deactivate関数の必要性
タスクスケジューラはLiferayのカーネルが処理するため、OSGiモジュールが停止されても自動的に解除されません。そのため、OSGiモジュールの停止と同時にタスクを無効化したい場合はスケジューラの停止と登録解除が必要になります。
StorageTypeについて
LiferayのStorageTypeは3つ(MEMORY_CLUSTERED, MEMORY, CLUSTERED)が存在するが、ジョブ処理ではMEMORY_CLUSTEREDかCLUSTEREDを使った方がいいです。MEMORYを使うと、クラスタリング環境下でLiferayノード毎にジョブが生成されるため、ノード数分タスクが発行されてしまいます。(例:メールが複数送信されます)
また、CLUSTEREDを設定することにより、ジョブの情報をDBの格納から取得するため、異常停止やモジュール更新によるタスクスキップを防ぐことができます。
まとめ
以上、Liferayのタスクスケジューラの紹介です。ぜひ皆さんも試してみてください。