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のタスクスケジューラの紹介です。ぜひ皆さんも試してみてください。

RANKING
2021.1.08
2020.12.28
2020.12.01
2020.10.30
2020.12.18