/**
* Get more info at : www.jrebirth.org .
* Copyright JRebirth.org © 2011-2013
* Contact : sebastien.bordes@jrebirth.org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jrebirth.af.core.service;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableMap;
import javafx.scene.control.ProgressBar;
import org.jrebirth.af.api.exception.CoreException;
import org.jrebirth.af.api.exception.JRebirthThreadException;
import org.jrebirth.af.api.facade.JRebirthEventType;
import org.jrebirth.af.api.log.JRLogger;
import org.jrebirth.af.api.service.Service;
import org.jrebirth.af.api.service.ServiceTask;
import org.jrebirth.af.api.wave.Wave;
import org.jrebirth.af.api.wave.contract.WaveData;
import org.jrebirth.af.core.component.behavior.AbstractBehavioredComponent;
import org.jrebirth.af.core.concurrent.AbstractJrbRunnable;
import org.jrebirth.af.core.concurrent.JRebirth;
import org.jrebirth.af.core.concurrent.JrbReferenceRunnable;
import org.jrebirth.af.core.log.JRLoggerFactory;
import org.jrebirth.af.core.util.ClassUtility;
import org.jrebirth.af.core.wave.Builders;
import org.jrebirth.af.core.wave.JRebirthWaves;
/**
*
* The class <strong>ServiceBase</strong>.
*
* Base implementation of the service.
*
* @author Sébastien Bordes
*/
public abstract class AbstractService extends AbstractBehavioredComponent<Service> implements Service, ServiceMessages {
/** The string used to separate the workdone and total work of a service task. */
private static final String RATIO_SEPARATOR = " / ";
/** The class logger. */
private static final JRLogger LOGGER = JRLoggerFactory.getLogger(AbstractService.class);
/** The map that stores pending tasks, useful to access to Worker implementation. */
private final ObservableMap<String, ServiceTask<?>> pendingTasks = FXCollections.observableMap(new HashMap<String, ServiceTask<?>>());
/**
* {@inheritDoc}
*/
@Override
public final void ready() throws CoreException {
// Call the custom method
initService();
}
/**
* Custom method used to initialize the service.
*
* Called into JIT by ready method.
*/
protected abstract void initService();
/**
* {@inheritDoc}
*/
@Override
protected void finalize() throws Throwable {
getLocalFacade().getGlobalFacade().trackEvent(JRebirthEventType.DESTROY_SERVICE, null, this.getClass());
super.finalize();
}
/**
* {@inheritDoc}
*/
@Override
public <T extends Object> ServiceTask<T> returnData(final Wave sourceWave) {
ServiceTask<T> task = null;
try {
// Build parameter list of the searched method
final List<Object> parameterValues = new ArrayList<>();
for (final WaveData<?> wd : sourceWave.waveDatas()) {
// Add only wave items defined as parameter
if (wd.getKey().isParameter()) {
parameterValues.add(wd.getValue());
}
}
// Add the current wave to process
// parameterValues.add(wave);
if (sourceWave.waveType() == null) {
LOGGER.error(NO_WAVE_TYPE_DEFINED, this.getClass().getSimpleName());
}
// Search the wave handler method
final Method method = ClassUtility.getMethodByName(this.getClass(), ClassUtility.underscoreToCamelCase(sourceWave.waveType().toString()));
if (method != null) {
// final Class<T> returnClass = (Class<T>) method.getReturnType();
task = runTask(sourceWave, method, parameterValues.toArray());
}
} catch (final NoSuchMethodException e) {
// If no method was found, call the default method
processWave(sourceWave);
}
return task;
}
/**
* Run the wave type method.
*
* @param sourceWave the source wave
* @param parameterValues values to pass to the method
* @param method method to call
*
* @param <T> the type of the returned type
*
* @return the service task created
*/
private <T> ServiceTask<T> runTask(final Wave sourceWave, final Method method, final Object[] parameterValues) {
// Allow to remove the pending task when the service is finished
sourceWave.addWaveListener(new ServiceTaskWaveListener());
// Create a new ServiceTask to handle this request and follow progression
final ServiceTaskBase<T> task = new ServiceTaskBase<T>(this, method, parameterValues, sourceWave);
// Store the task into the pending map
this.pendingTasks.put(sourceWave.getWUID(), task);
// Attach ServiceTask to the source wave
sourceWave.addDatas(Builders.waveData(JRebirthWaves.SERVICE_TASK, task));
// Bind ProgressBar
if (sourceWave.containsNotNull(JRebirthWaves.PROGRESS_BAR)) { // Check double call
bindProgressBar(task, sourceWave.getData(JRebirthWaves.PROGRESS_BAR).getValue());
}
// Bind Title
if (sourceWave.containsNotNull(JRebirthWaves.TASK_TITLE)) {
bindTitle(task, sourceWave.getData(JRebirthWaves.TASK_TITLE).getValue());
}
// Bind ProgressBar
if (sourceWave.containsNotNull(JRebirthWaves.TASK_MESSAGE)) {
bindMessage(task, sourceWave.getData(JRebirthWaves.TASK_MESSAGE).getValue());
}
// Call the task into the JRebirth Thread Pool
JRebirth.runIntoJTP(task);
return task;
}
/**
* Bind a task to a progress bar widget to follow its progression.
*
* @param task the service task that we need to follow the progression
* @param progressBar graphical progress bar
*/
private void bindProgressBar(final ServiceTaskBase<?> task, final ProgressBar progressBar) {
// Perform this binding into the JAT to respect widget and task API
JRebirth.runIntoJAT(new AbstractJrbRunnable("Bind ProgressBar to " + task.getServiceHandlerName()) {
/**
* {@inheritDoc}
*/
@Override
protected void runInto() throws JRebirthThreadException {
// Avoid the progress bar to display 100% at start up
task.updateProgress(0, 0);
// Bind the progress bar
progressBar.progressProperty().bind(task.workDoneProperty().divide(task.totalWorkProperty()));
}
});
}
/**
* Bind a task to a string property that will display the task title.
*
* @param task the service task that we need to follow the progression
* @param titleProperty the title presenter
*/
private void bindTitle(final ServiceTask<?> task, final StringProperty titleProperty) {
// Perform this binding into the JAT to respect widget and task API
JRebirth.runIntoJAT(new AbstractJrbRunnable("Bind Title for " + task.getServiceHandlerName()) {
/**
* {@inheritDoc}
*/
@Override
protected void runInto() throws JRebirthThreadException {
// Bind the task title
titleProperty.bind(task.titleProperty());
}
});
}
/**
* Bind a task to a string property that will display the task message.
*
* @param task the service task that we need to follow the progression
* @param messageProperty the message presenter
*/
private void bindMessage(final ServiceTask<?> task, final StringProperty messageProperty) {
// Perform this binding into the JAT to respect widget and task API
JRebirth.runIntoJAT(new AbstractJrbRunnable("Bind Message for " + task.getServiceHandlerName()) {
/**
* {@inheritDoc}
*/
@Override
protected void runInto() throws JRebirthThreadException {
// Bind the task title
messageProperty.bind(task.messageProperty());
}
});
}
/**
* {@inheritDoc}
*/
@Override
public ObservableMap<String, ServiceTask<?>> pendingTasksProperty() {
return this.pendingTasks;
}
/**
* {@inheritDoc}
*/
@Override
public Collection<ServiceTask<?>> getPendingTaskList() {
return this.pendingTasks.values();
}
/**
* {@inheritDoc}
*/
@Override
public void removePendingTask(final String taskKey) {
this.pendingTasks.remove(taskKey);
}
/**
* {@inheritDoc}
*/
@Override
public ServiceTask<?> getPendingTask(final String taskKey) {
return this.pendingTasks.get(taskKey);
}
/**
* Update the progress of the service task related to the given wave.
*
* This method will use a 1.0 block increment
*
* @param wave the wave that trigger the service task call
* @param workDone the amount of overall work done
* @param totalWork the amount of total work to do
*/
public void updateProgress(final Wave wave, final long workDone, final long totalWork) {
updateProgress(wave, workDone, totalWork, 1.0);
}
/**
* Update the progress of the service task related to the given wave.
*
* @param wave the wave that trigger the service task call
* @param workDone the amount of overall work done
* @param totalWork the amount of total work to do
* @param progressIncrement the value increment used to filter useless progress event
*/
public void updateProgress(final Wave wave, final long workDone, final long totalWork, final double progressIncrement) {
if (wave.get(JRebirthWaves.SERVICE_TASK).checkProgressRatio(workDone, totalWork, progressIncrement)) {
// Increase the task progression
JRebirth.runIntoJAT(new AbstractJrbRunnable("ServiceTask Workdone (lng) " + workDone + RATIO_SEPARATOR + totalWork + "[" + workDone * 100 / totalWork + "%]") {
/**
* {@inheritDoc}
*/
@Override
protected void runInto() throws JRebirthThreadException {
wave.get(JRebirthWaves.SERVICE_TASK).updateProgress(workDone, totalWork);
}
});
}
}
/**
* Update the progress of the service task related to the given wave.
*
* This method will use a 1.0 block increment
*
* @param wave the wave that trigger the service task call
* @param workDone the amount of overall work done
* @param totalWork the amount of total work to do
*/
public void updateProgress(final Wave wave, final double workDone, final double totalWork) {
updateProgress(wave, workDone, totalWork, 1.0);
}
/**
* Update the progress of the service task related to the given wave.
*
* @param wave the wave that trigger the service task call
* @param workDone the amount of overall work done
* @param totalWork the amount of total work to do
* @param progressIncrement the value increment used to filter useless progress event
*/
public void updateProgress(final Wave wave, final double workDone, final double totalWork, final double progressIncrement) {
if (wave.get(JRebirthWaves.SERVICE_TASK).checkProgressRatio(workDone, totalWork, progressIncrement)) {
// Increase the task progression
JRebirth.runIntoJAT(new JrbReferenceRunnable("ServiceTask Workdone (dbl) " + workDone + RATIO_SEPARATOR + totalWork,
() -> wave.get(JRebirthWaves.SERVICE_TASK).updateProgress(workDone, totalWork)));
}
}
/**
* Update the current message of the service task related to the given wave.
*
* @param wave the wave that trigger the service task call
* @param message the current message of the service task processed
*/
public void updateMessage(final Wave wave, final String message) {
// Update the current task message
JRebirth.runIntoJAT(new JrbReferenceRunnable("Service Task Mesage => " + message,
() -> wave.get(JRebirthWaves.SERVICE_TASK).updateMessage(message)));
}
/**
* Update the current message of the service task related to the given wave.
*
* @param wave the wave that trigger the service task call
* @param title the title of the service task processed
*/
public void updateTitle(final Wave wave, final String title) {
// Update the task title into JAT
JRebirth.runIntoJAT(new JrbReferenceRunnable("Service Task Title => " + title,
() -> wave.get(JRebirthWaves.SERVICE_TASK).updateTitle(title)));
}
}