/** * 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.InvocationTargetException; import java.lang.reflect.Method; import java.time.Instant; import java.util.ArrayList; import java.util.List; import javafx.concurrent.Task; import org.jrebirth.af.api.command.Command; import org.jrebirth.af.api.concurrent.JRebirthRunnable; import org.jrebirth.af.api.concurrent.Priority; import org.jrebirth.af.api.concurrent.RunnablePriority; import org.jrebirth.af.api.exception.CoreException; 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.Wave.Status; import org.jrebirth.af.api.wave.WaveGroup; import org.jrebirth.af.api.wave.contract.WaveType; import org.jrebirth.af.core.exception.ServiceException; import org.jrebirth.af.core.log.JRLoggerFactory; import org.jrebirth.af.core.wave.Builders; import org.jrebirth.af.core.wave.JRebirthItems; import org.jrebirth.af.core.wave.JRebirthWaves; import org.jrebirth.af.core.wave.WaveItemBase; /** * The class <strong>ServiceTask</strong>. * * @author Sébastien Bordes * * @param <T> the current Service Task type */ public final class ServiceTaskBase<T> extends Task<T> implements JRebirthRunnable, ServiceTask<T>, ServiceMessages { /** The class logger. */ private static final JRLogger LOGGER = JRLoggerFactory.getLogger(ServiceTaskBase.class); /** * The <code>parameterValues</code>. */ private final Object[] parameterValues; /** * The <code>method</code>. */ private final Method method; /** * The <code>localService</code>. */ private final Service service; /** * The <code>sourceWave</code>. */ private final Wave wave; /** * The workdone copied property stored locally to allow access outside JAT.<br/> * It implies to synchronize each access/modification */ private double localWorkDone; /** The runnable priority. */ private final RunnablePriority priority; /** The creation timestamp in milliseconds. */ private final Instant creationTime; /** * Default Constructor only visible by service package. * * @param parameterValues the list of function parameter * @param method the method to call * @param service the service object * @param wave the wave to process */ ServiceTaskBase(final Service service, final Method method, final Object[] parameterValues, final Wave wave) { super(); this.creationTime = Instant.now(); this.service = service; this.method = method; this.parameterValues = parameterValues.clone(); this.wave = wave; final Priority priorityA = method.getAnnotation(Priority.class); this.priority = priorityA == null ? RunnablePriority.Low : priorityA.value(); } /** * Return the full service handler name. * * ServiceName + method + ( parameters types ) * * @return the full service handler name */ @Override public String getServiceHandlerName() { final StringBuilder sb = new StringBuilder(); sb.append(this.service.getClass().getSimpleName()).append("."); sb.append(this.method.getName()).append("("); for (final Class<?> parameterType : this.method.getParameterTypes()) { sb.append(parameterType.getSimpleName()).append(", "); } sb.append(")"); return sb.toString(); } /** * {@inheritDoc} * * @throws CoreException if return WaveType has bad API */ @SuppressWarnings("unchecked") @Override protected T call() throws CoreException { this.wave.status(Status.Consumed); T res = null; try { // Build parameter list of the searched method final List<Object> params = new ArrayList<>(); for (final Object o : this.parameterValues) { params.add(o); } // Add the current wave to process params.add(this.wave); // Call this method with right parameters res = (T) this.method.invoke(this.service, params.toArray()); if (Void.TYPE.equals(this.method.getReturnType()) && this.wave.waveType().returnItem() != JRebirthItems.voidItem) { // No return wave required because the service method will return nothing (VOID) LOGGER.log(NO_RETURN_WAVE_CONSUMED, this.service.getClass().getSimpleName(), this.wave.toString()); // Consumed + Handled because there is only one handler: the service task this.wave.status(Status.Handled); // Otherwise prepare the return wave } else { // Send the result into a wave sendReturnWave(res); } } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { LOGGER.log(SERVICE_TASK_ERROR, e, getServiceHandlerName()); this.wave.status(Status.Failed); } catch (final ServiceException se) { LOGGER.log(SERVICE_TASK_EXCEPTION, se, se.getExplanation(), getServiceHandlerName()); this.wave.status(Status.Failed); } catch (final Exception e) { // NOSONAR, catch all to try to do something smart ! handleException(e); } return res; } /** * Handle all exception occurred while doing the task.TODO To complete. * * @param e the exception to handle */ private void handleException(final Exception e) { this.wave.status(Status.Failed); // Get the exact exception type final Wave exceptionHandlerWave = this.wave.waveType().waveExceptionHanler().get(e.getClass()); if (exceptionHandlerWave != null) { // Fill the wave with useful data exceptionHandlerWave.fromClass(this.service.getClass()) .add(JRebirthItems.exceptionItem, e) .relatedWave(this.wave); LOGGER.log(SERVICE_TASK_HANDLE_EXCEPTION, e, e.getClass().getSimpleName(), getServiceHandlerName()); // Send the exception wave to interested components this.service.sendWave(exceptionHandlerWave); } else { LOGGER.log(SERVICE_TASK_NOT_MANAGED_EXCEPTION, e, e.getClass().getSimpleName(), getServiceHandlerName()); } } /** * Send a wave that will carry the service result. * * 2 Kinds of wave can be sent according to service configuration * * @param res the service result * * @throws CoreException if the wave generation has failed */ @SuppressWarnings("unchecked") private void sendReturnWave(final T res) throws CoreException { Wave returnWave = null; // Try to retrieve the return Wave type, could be null final WaveType responseWaveType = this.wave.waveType().returnWaveType(); final Class<? extends Command> responseCommandClass = this.wave.waveType().returnCommandClass(); if (responseWaveType != null) { final WaveItemBase<T> resultWaveItem; // No service result type defined into a WaveItem if (responseWaveType != JRebirthWaves.RETURN_VOID && responseWaveType.items().isEmpty()) { LOGGER.log(NO_RETURNED_WAVE_ITEM); throw new CoreException(NO_RETURNED_WAVE_ITEM); } else { // Get the first (and unique) WaveItem used to define the service result type resultWaveItem = (WaveItemBase<T>) responseWaveType.items().get(0); } // Try to retrieve the command class, could be null // final Class<? extends Command> responseCommandClass = this.service.getReturnCommand(this.wave.waveType()); if (responseCommandClass != null) { // If a Command Class is provided, call it with the right WaveItem to get the real result type returnWave = Builders.wave() .waveGroup(WaveGroup.CALL_COMMAND) .fromClass(this.service.getClass()) .componentClass(responseCommandClass); } else { // Otherwise send a generic wave that can be handled by any component returnWave = Builders.wave() .waveType(responseWaveType) .fromClass(this.service.getClass()); } // Add the result wrapped into a WaveData with the right WaveItem if (resultWaveItem != null) { returnWave.addDatas(Builders.waveData(resultWaveItem, res)); } // Don't add data when method has returned VOID returnWave.relatedWave(this.wave); returnWave.addWaveListener(new ServiceTaskReturnWaveListener()); // Send the return wave to interested components this.service.sendWave(returnWave); } else { // No service return wave Type defined LOGGER.log(NO_RETURNED_WAVE_TYPE_DEFINED, this.wave.waveType()); throw new CoreException(NO_RETURNED_WAVE_ITEM, this.wave.waveType()); } } /** * {@inheritDoc} */ @Override public void updateProgress(final long workDone, final long totalWork) { synchronized (this) { this.localWorkDone = workDone; } super.updateProgress(workDone, totalWork); } /** * {@inheritDoc} */ @Override public void updateProgress(final double workDone, final double totalWork) { synchronized (this) { this.localWorkDone = workDone; } super.updateProgress(workDone, totalWork); } /** * {@inheritDoc} */ @Override public void updateMessage(final String message) { // NOSONAR Override method visibility super.updateMessage(message); } /** * {@inheritDoc} */ @Override public void updateTitle(final String title) { // NOSONAR Override method visibility super.updateTitle(title); } /** * The task has been terminated because the source wave was consumed or has failed. * * Remove the task from the service pending list */ @Override public void taskAchieved() { // We can now remove the pending task (even if the return wave isn't processed TO CHECK) this.service.removePendingTask(this.wave.getWUID()); } /** * {@inheritDoc} */ @Override public Wave getAssociatedWave() { return this.wave; } /** * Check if the task has enough progressed according to the given threshold. * * This method can be called outside the JAT, it's useful to filter useless call to JAT * * @param newWorkDone the amount of work done * @param totalWork the total amount of work * @param amountThreshold the minimum threshold amount to return true; range is [0.0 - 100.0] (typically 1.0 for 1%) * * @return true if the threshold is reached */ @Override public boolean checkProgressRatio(final double newWorkDone, final double totalWork, final double amountThreshold) { double currentRatio; synchronized (this) { // Compute the actual progression currentRatio = this.localWorkDone >= 0 ? 100 * this.localWorkDone / totalWork : 0.0; } // Compute the future progression final double newRatio = 100 * newWorkDone / totalWork; // return true if the task has progressed of at least block increment value return newRatio - currentRatio > amountThreshold; } /** * {@inheritDoc} */ @Override public RunnablePriority getPriority() { return this.priority; } /** * {@inheritDoc} */ @Override public Instant getCreationTime() { return this.creationTime; } }