/* * Geotoolkit - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2009 - 2012, Geomatys * * This library 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; * version 2.1 of the License. * * This library 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 * Lesser General Public License for more details. */ package org.geotoolkit.processing; import java.util.Date; import java.util.concurrent.CancellationException; import javax.swing.event.EventListenerList; import org.geotoolkit.process.*; import org.opengis.metadata.quality.ConformanceResult; import org.opengis.parameter.ParameterValueGroup; import org.apache.sis.metadata.iso.lineage.DefaultProcessing; import org.apache.sis.metadata.iso.lineage.DefaultProcessStep; import org.geotoolkit.parameter.Parameters; import static org.apache.sis.util.ArgumentChecks.*; /** * * @author Johann Sorel (Geomatys) * @module */ public abstract class AbstractProcess implements org.geotoolkit.process.Process { protected final EventListenerList listeners = new EventListenerList(); protected final ProcessDescriptor descriptor; protected final ParameterValueGroup outputParameters; protected ParameterValueGroup inputParameters; volatile boolean isCanceled = false; volatile boolean isPaused = false; public AbstractProcess(final ProcessDescriptor desc, final ParameterValueGroup input) { ensureNonNull("descriptor", desc); ensureNonNull("input", input); this.descriptor = desc; this.outputParameters = descriptor.getOutputDescriptor().createValue(); this.inputParameters = input; final ConformanceResult res = Parameters.isValid(inputParameters, inputParameters.getDescriptor()); if (!res.pass()) { throw new IllegalArgumentException("Input parameters are invalid:" + res.getExplanation()); } } @Override public ProcessDescriptor getDescriptor() { return descriptor; } @Override public ParameterValueGroup getInput() { return inputParameters; } /** * {@linkplain #execute() Executes} the process and returns the {@linkplain #outputParameters * output parameters}. This method takes care of invoking the * {@link ProcessListener#started(ProcessEvent) started} and * {@link ProcessListener#completed(ProcessEvent) completed} or * {@link ProcessListener#failed(ProcessEvent) failed} methods. * * @return The computation results stored in the {@linkplain #outputParameters output parameters}. * @throws ProcessException If the process failed. */ @Override public ParameterValueGroup call() throws ProcessException { fireProcessStarted(null); boolean success = false; Exception exception = null; try { execute(); success = true; } catch (ProcessException | RuntimeException e) { exception = e; throw e; // Will execute 'finally' before to exit. } catch (Throwable t) { exception = new ProcessException(t.getMessage(), this, t); throw (ProcessException)exception; // Will execute 'finally' before to exit. } finally { if (success) { fireProcessCompleted(null); } else { fireProcessFailed(null, exception); } } return outputParameters; } /** * Returns a description of the process, the geographic inputs and outputs and other metadata. * The default implementation performs the following mapping: * * <ul> * <li>{@link ProcessDescriptor#getIdentifier()} → {@link DefaultProcessing#getIdentifier()}.</li> * <li>{@link ProcessDescriptor#getDisplayName()} → {@link DefaultProcessStep#getDescription()}.</li> * <li>Current time → {@link DefaultProcessStep#getDate()}.</li> * </ul> * * Subclasses are encouraged to complete the metadata with their own information. * * @return A description of the process, the geographic inputs and outputs and other metadata. */ @Override public DefaultProcessStep getMetadata() { final DefaultProcessStep step = new DefaultProcessStep(descriptor.getDisplayName()); step.setDate(new Date()); // Set to current time. final DefaultProcessing processing = new DefaultProcessing(); processing.setIdentifier(descriptor.getIdentifier()); step.setProcessingInformation(processing); return step; } /** * {@linkplain #cancelProcess() CancelProcess} set the {@code isCanceled} flag to {@code true}. * The {@code isCanceled} flag is used by the process to know if someone ask for his cancelation. * Long process should when they can check the {@code isCanceled} flag through the {@link #isCanceled() isCanceled} method. * If a process see his {@code isCanceled} flag to {@code true} he should throw an {@link CancellationException exception}. */ public void cancelProcess(){ isCanceled = true; } /** * {@linkplain #pauseProcess() PauseProcess} set the {@code isPaused} flag to {@code true}. * The {@code isPaused} flag is used by the process to know if someone ask for his pause. * Long process should when they can check the {@code isPaused} flag through the {@link #isPaused() isPaused} method. * If a process see his {@code isPaused} flag to {@code true} he should throw an {@link PauseException exception}. */ public void pauseProcess(){ isPaused = true; } /** * {@linkplain #resumeProcess() ResumeProcess} set the {@code isPaused} flag to {@code false}. * The {@code isPaused} flag is used by the process to know if someone ask for his pause. * Long process should when they can check the {@code isPaused} flag through the {@link #isPaused() isPaused} method. * If a process see his {@code isPaused} flag to {@code true} he should throw an {@link PauseException exception}. */ public void resumeProcess(){ isPaused = false; } /** * Return the {@code isCanceled} flag value. * * @return {@code isCanceled} flag value. */ public boolean isCanceled(){ return isCanceled; } /** * Return the {@code isPaused} flag value. * * @return {@code isPaused} flag value. */ public boolean isPaused(){ return isPaused; } /** * Immediately performs the action of this process. This method is invoked by the {@link #call()} * method after any {@linkplain #addListener(ProcessListener) registered listeners} have been * notified of the process start. Listeners will also be notified when the process end, either * successfully or on failure. * * @throws ProcessException If the process failed. */ protected abstract void execute() throws ProcessException; @Override public void addListener(final ProcessListener listener) { listeners.add(ProcessListener.class, listener); } @Override public void removeListener(final ProcessListener listener) { listeners.remove(ProcessListener.class, listener); } @Override public ProcessListener[] getListeners() { return listeners.getListeners(ProcessListener.class); } /** * Invoked when the process is about to start. This method invokes * {@link ProcessListener#started(ProcessEvent)} for all registered listeners. * * @param task A description of the task which is starting, or {@code null} if none. */ protected void fireProcessStarted(final CharSequence task) { final ProcessEvent event = new ProcessEvent(this, task, 0f); for (ProcessListener listener : listeners.getListeners(ProcessListener.class)) { listener.started(event); } } /** * Invoked when the process is making progress. This method invokes * {@link ProcessListener#progressing(ProcessEvent)} for all registered listeners. * * @param task A description of the task which is progressing, or {@code null} if none. * @param progress The progress as a number between 0 and 100, or {@link Float#NaN} if undetermined. * @param hasIntermediateResults {@code true} if the {@link #outputParameters} contains * intermediate results that can be sent to the listeners. */ protected void fireProgressing(final CharSequence task, final float progress, final boolean hasIntermediateResults) { final ProcessEvent event = new ProcessEvent(this, task, progress, hasIntermediateResults ? outputParameters : null); for (ProcessListener listener : listeners.getListeners(ProcessListener.class)) { listener.progressing(event); } } /** * Invoked when a non-fatal exception occurred during process. This method invokes * {@link ProcessListener#progressing(ProcessEvent)} for all registered listeners. * * @param task A description of the task which is progressing, or {@code null} if none. * @param progress The progress as a number between 0 and 100, or {@link Float#NaN} if undetermined. * @param warning The non-fatal exception that occurred. */ protected void fireWarningOccurred(final CharSequence task, final float progress, final Exception warning) { final ProcessEvent event = new ProcessEvent(this, task, progress, warning); for (ProcessListener listener : listeners.getListeners(ProcessListener.class)) { listener.progressing(event); } } /** * Invoked after the process successfully completed. This method invokes * {@link ProcessListener#completed(ProcessEvent)} for all registered listeners. * * @param task A description of the completed task, or {@code null} if none. */ protected void fireProcessCompleted(final CharSequence task) { final ProcessEvent event = new ProcessEvent(this, task, 100f, outputParameters); for (ProcessListener listener : listeners.getListeners(ProcessListener.class)) { listener.completed(event); } } /** * Invoked after a fatal error occurred during the process execution. This method invokes * {@link ProcessListener#failed(ProcessEvent)} for all registered listeners. * * @param task A description of the task that failed, or {@code null} if none. * @param exception The exception which occurred, or {@code null} if unavailable. */ protected void fireProcessFailed(final CharSequence task, final Exception exception) { final ProcessEvent event = new ProcessEvent(this, task, Float.NaN, exception); for (ProcessListener listener : listeners.getListeners(ProcessListener.class)) { listener.failed(event); } } /** * Invoked when the process is paused during the process execution. This method invokes * {@link ProcessListener#paused(ProcessEvent)} for all registered listeners. * * @param task A description of the task that being paused, or {@code null} if none. */ protected void fireProcessPaused(final CharSequence task, final float progress) { final ProcessEvent event = new ProcessEvent(this, task, progress); for (ProcessListener listener : listeners.getListeners(ProcessListener.class)) { listener.paused(event); } } /** * Invoked when the process is resumed after being suspended. This method invokes * {@link ProcessListener#resumed(ProcessEvent)} for all registered listeners. * * @param task A description of the task that being resumed, or {@code null} if none. */ protected void fireProcessResumed(final CharSequence task, final float progress) { final ProcessEvent event = new ProcessEvent(this, task, progress); for (ProcessListener listener : listeners.getListeners(ProcessListener.class)) { listener.resumed(event); } } /** * Forward a start event to all listeners. * @param event */ @Deprecated protected void fireStartEvent(final ProcessEvent event) { for (ProcessListener listener : listeners.getListeners(ProcessListener.class)) { listener.started(event); } } /** * Forward a progress event to all listeners. * @param event */ @Deprecated protected void fireProgressEvent(final ProcessEvent event) { for (ProcessListener listener : listeners.getListeners(ProcessListener.class)) { listener.progressing(event); } } /** * Forward a fail event to all listeners. * @param event */ @Deprecated protected void fireFailEvent(final ProcessEvent event) { for (ProcessListener listener : listeners.getListeners(ProcessListener.class)) { listener.failed(event); } } /** * Forward an end event to all listeners. * @param event */ @Deprecated protected void fireEndEvent(final ProcessEvent event) { for (ProcessListener listener : listeners.getListeners(ProcessListener.class)) { listener.completed(event); } } }