org.nuiton.jaxx.runtime.application.action.ActionExecutor Maven / Gradle / Ivy
/*
* #%L
* JAXX :: Runtime
* %%
* Copyright (C) 2008 - 2024 Code Lutin, Ultreia.io
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* .
* #L%
*/
package org.nuiton.jaxx.runtime.application.action;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.nuiton.jaxx.runtime.application.ApplicationBoot;
import org.nuiton.jaxx.runtime.application.ApplicationContext;
import org.nuiton.jaxx.runtime.application.action.event.ActionExecutorEvent;
import org.nuiton.jaxx.runtime.application.action.event.ActionExecutorListener;
import org.nuiton.jaxx.runtime.application.action.event.DefaultActionExecutorListener;
import javax.swing.SwingWorker;
import javax.swing.event.EventListenerList;
import java.beans.PropertyChangeListener;
import java.io.Closeable;
import java.util.Date;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* Executor of {@link ActionWorker}.
*
* @author Tony Chemit - [email protected]
* @since 2.1
*/
public class ActionExecutor implements Closeable {
private static final Logger log = LogManager.getLogger(ActionExecutor.class);
/**
* Current running actions.
*/
private final Set> tasks = new HashSet<>();
/**
* Listener to attach on running actions to dispatch his internal state via {@link ActionExecutorListener}.
*/
private final PropertyChangeListener workerListener;
/**
* To store listeners.
*/
private final EventListenerList eventListenerList;
/**
* Application boot.
*/
private final ApplicationBoot boot;
/**
* Executor service.
*/
private ThreadPoolExecutor executorService;
public ActionExecutor(ApplicationBoot boot, ApplicationContext context) {
this.boot = boot;
this.eventListenerList = new EventListenerList();
this.workerListener = evt -> onActionWorkerChanged((ActionWorker, ?>) evt.getSource(), evt.getPropertyName(), evt.getOldValue(), evt.getNewValue());
addActionExecutorListener(new DefaultActionExecutorListener(context));
}
public void addActionExecutorListener(ActionExecutorListener listener) {
eventListenerList.add(ActionExecutorListener.class, Objects.requireNonNull(listener));
}
public void removeActionExecutorListener(ActionExecutorListener listener) {
eventListenerList.remove(ActionExecutorListener.class, Objects.requireNonNull(listener));
}
public ActionExecutorListener[] getActionExecutorListeners() {
return eventListenerList.getListeners(ActionExecutorListener.class);
}
/**
* Add an new action to perform.
*
* @param actionLabel the name of the action to perform
* @param action the action to perform
* @return the worker that will launch the action
*/
public ActionWorker, ?> addAction(String actionLabel, Runnable action) {
// boot.checkNotClosed("executor");
ActionWorker, ?> worker;
if (action instanceof ActionWorker) {
worker = (ActionWorker, ?>) action;
} else {
worker = new ActionWorker(actionLabel, action);
}
worker.addPropertyChangeListener(workerListener);
tasks.add(worker);
log.debug("Launch worker [" + actionLabel + "] now...");
try {
getWorkersExecutorService().execute(worker);
} catch (RejectedExecutionException e) {
log.error(String.format("Worker was rejected: %s for reason: %s", worker, e.getMessage()), e);
}
log.debug("Launch worker [" + actionLabel + "] is on...");
return worker;
}
/**
* Ask the stop any running actions.
*
* It will finish all incoming files (but will not accept more tasks).
*
* Note: The method does not return until all tasks are not consumed.
*/
@Override
public void close() {
log.info("Executor " + this + " is terminating...");
// for (ActionWorker, ?> task : tasks) {
// log.info(String.format("cancel task: %s", task.getActionLabel()));
// task.cancel(true);
// }
if (executorService != null) {
try {
executorService.awaitTermination(1000, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
log.error("Can't terminate executor service, will shutdown it.");
}
executorService.shutdownNow();
executorService = null;
}
for (ActionExecutorListener listener : getActionExecutorListeners()) {
removeActionExecutorListener(listener);
}
log.info("Executor " + this + " is terminated at " + new Date());
}
public int getNbActions() {
return getTasks().size();
}
public Set> getTasks() {
return tasks;
}
private void onActionWorkerChanged(ActionWorker, ?> source, String propertyName, Object oldValue, Object newValue) {
log.debug(String.format("action %s property %s changed <%s - %s>", source, propertyName, oldValue, newValue));
switch (propertyName) {
case "state":
SwingWorker.StateValue state = (SwingWorker.StateValue) Objects.requireNonNull(newValue);
if (SwingWorker.StateValue.STARTED == state) {
// starting new action
fireActionStart(this, source);
return;
}
if (SwingWorker.StateValue.DONE == state) {
// action done, let's dispatch to correct callback
ActionWorker.ActionStatus status = source.getStatus();
log.debug(String.format("Action [%s] status = %s", source.getActionLabel(), status));
try {
switch (status) {
case OK:
// normal end
fireActionEnd(this, source);
break;
case CANCEL:
// cancel end
fireActionCancel(this, source);
break;
case FAIL:
// error end
fireActionFail(this, source);
break;
}
} finally {
source.removePropertyChangeListener(workerListener);
tasks.remove(source);
fireAfterAction(this, source);
}
}
break;
}
}
/**
* On surcharge celui offer par SwingWorker car le corepool est à 1 et cela
* ne permet pas d'exécuter plusieurs actions en même temps...
* necessary.
*
* @return ExecutorService for the {@link ActionWorker}
*/
public ThreadPoolExecutor getWorkersExecutorService() {
// boot.checkNotClosed("Executor service");
if (executorService == null) {
executorService = new ThreadPoolExecutor(5, 10, 10L, TimeUnit.MINUTES, new LinkedBlockingQueue<>(), boot.getThreadFactory());
}
return executorService;
}
private void fireActionStart(ActionExecutor executor, ActionWorker, ?> source) {
ActionExecutorEvent event = null;
for (ActionExecutorListener listener : getActionExecutorListeners()) {
if (event == null) {
event = new ActionExecutorEvent(source, executor);
}
listener.onActionStart(event);
}
}
private void fireActionFail(ActionExecutor executor, ActionWorker, ?> source) {
ActionExecutorEvent event = null;
for (ActionExecutorListener listener : getActionExecutorListeners()) {
if (event == null) {
event = new ActionExecutorEvent(source, executor);
}
listener.onActionFail(event);
}
}
private void fireActionCancel(ActionExecutor executor, ActionWorker, ?> source) {
ActionExecutorEvent event = null;
for (ActionExecutorListener listener : getActionExecutorListeners()) {
if (event == null) {
event = new ActionExecutorEvent(source, executor);
}
listener.onActionCancel(event);
}
}
private void fireActionEnd(ActionExecutor executor, ActionWorker, ?> source) {
ActionExecutorEvent event = null;
for (ActionExecutorListener listener : getActionExecutorListeners()) {
if (event == null) {
event = new ActionExecutorEvent(source, executor);
}
listener.onActionEnd(event);
}
}
private void fireAfterAction(ActionExecutor executor, ActionWorker, ?> source) {
ActionExecutorEvent event = null;
for (ActionExecutorListener listener : getActionExecutorListeners()) {
if (event == null) {
event = new ActionExecutorEvent(source, executor);
}
listener.onAfterAction(event);
}
}
}