/* (c) 2014 - 2016 Open Source Geospatial Foundation - all rights reserved
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.wps.remote;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.geotools.process.Process;
import org.geotools.process.ProcessException;
import org.geotools.util.logging.Logging;
import org.opengis.feature.type.Name;
import org.opengis.util.ProgressListener;
/**
* Stub for the remote processes generated at run-time by the {@link RemoteProcessFactory} upon a {@link RemoteProcessClient} registration request.
*
* @author Alessio Fabiani, GeoSolutions
*
*/
public class RemoteProcess implements Process, RemoteProcessClientListener {
/** The LOGGER. */
private static final Logger LOGGER = Logging.getLogger(RemoteProcess.class);
/** Check execution status every 10 seconds */
private static final long SIGNAL_TIMEOUT = 10;
/** The Process Name; declared by the remote service */
private Name name;
/** The {@link RemoteProcessClient} */
private RemoteProcessClient remoteClient;
/** A generic kvp map containing client specific implementation properties */
private Map<String, Object> metadata;
/** The Process Outputs; declared by the remote service */
private Map<String, Object> outputs;
/** Whether the Process is still running or not */
private boolean running;
/**
* A Process ID generated by the {@link RemoteProcessClient}; this is used to uniquely identify the remote service sending commands and messages
* to this {@link RemoteProcess} instance
*/
private String pid;
/** The progess listrener. */
private ProgressListener listener;
/**
* Whether the remote service raised and exception or not. This property contains the cause and is instantiated by the {@link RemoteProcessClient}
*/
private Exception exception;
/** The semaphore */
CountDownLatch doneSignal = new CountDownLatch(1);
/**
* Constructs a new stub for the {@link RemoteProcess} execution. Metadata is a kvp map containing specific properties of the
* {@link RemoteProcessClient} instance
*
* @param name
* @param remoteClient
* @param metadata
*/
public RemoteProcess(Name name, RemoteProcessClient remoteClient,
Map<String, Object> metadata) {
this.name = name;
this.remoteClient = remoteClient;
this.metadata = metadata;
}
@Override
public String getPID() {
return this.pid;
}
@Override
public Map<String, Object> execute(Map<String, Object> input, ProgressListener monitor) {
try {
// Generating a unique Process ID
LOGGER.info("Generating a unique Process ID for Remote Process [" + name
+ "] with the following parameters:");
LOGGER.info(" - name: " + name);
LOGGER.info(" - input: " + input);
LOGGER.info(" - metadata: " + metadata);
LOGGER.info(" - monitor: " + monitor);
if (remoteClient == null) {
LOGGER.log(Level.SEVERE, "Cannot execute Remote Process [" + name
+ "] since the RemoteClient is not available!");
throw new Exception("Cannot execute Remote Process [" + name
+ "] since the RemoteClient is not available!");
}
listener = monitor;
pid = remoteClient.execute(name, input, metadata, monitor);
LOGGER.info("Starting the execution of Remote Process with pId [" + pid + "]");
running = pid != null;
remoteClient.registerProcessClientListener(this);
while (running && (listener != null && !listener.isCanceled())) {
doneSignal.await(SIGNAL_TIMEOUT, TimeUnit.SECONDS);
}
LOGGER.info("Stopping the execution of Remote Process with pId [" + pid + "]");
// TODO: Forward Cancel/Expiration signal to RemoteClient
} catch (Exception e) {
if (listener != null) {
listener.exceptionOccurred(e);
}
LOGGER.log(Level.SEVERE, "The Remote Process with pId [" + pid + "] rasied an Exeption",
e);
throw new ProcessException(e);
} finally {
remoteClient.deregisterProcessClientListener(this);
}
// forward the Exception if necessary
if (exception != null) {
LOGGER.log(Level.SEVERE, "The Remote Service associated to the Process with pId [" + pid
+ "] rasied an Exeption", exception);
throw new ProcessException(exception);
}
// check if the Process has been cancelled
if (listener != null && listener.isCanceled()) {
LOGGER.log(Level.WARNING, "The Remote Service associated to the Process with pId ["
+ pid + "] has been cancelled");
throw new ProcessException("The Remote Service associated to the Process with pId ["
+ pid + "] has been cancelled");
}
return outputs;
}
/**
* @return the running
*/
public boolean isRunning() {
return running;
}
/**
* @param running the running to set
*/
public void setRunning(boolean running) {
this.running = running;
}
/**
* @return the outputs
*/
public Map<String, Object> getOutputs() {
return outputs;
}
/**
* @param outputs the outputs to set
*/
public void setOutputs(Map<String, Object> outputs) {
this.outputs = outputs;
}
@Override
public void progress(final String pId, final Double progress) {
if (pId.equals(pid)) {
listener.progress(progress.floatValue());
}
if (listener.isCanceled()) {
doneSignal.countDown();
}
}
@Override
public void complete(String pId, Object outputs) {
if (pId.equals(pid)) {
listener.complete();
try {
this.outputs = (Map<String, Object>) outputs;
} catch (Exception e) {
exception = e;
LOGGER.log(Level.SEVERE,
"The Remote Service associated to the Process with pId [" + pid
+ "] rasied an Exeption while setting the outputs on completion",
exception);
this.outputs = null;
}
running = false;
doneSignal.countDown();
}
}
@Override
public void exceptionOccurred(final String pId, Exception cause, Map<String, Object> metadata) {
if (pId != null && pId.equals(pid)) {
listener.exceptionOccurred(cause);
exception = cause;
running = false;
} else if (metadata != null) {
boolean metadataIsEqual = true;
for (Entry<String, Object> entry : metadata.entrySet()) {
if (!this.metadata.containsKey(entry.getKey())
|| this.metadata.get(entry.getKey()) != entry.getValue()) {
metadataIsEqual = false;
break;
}
}
if (metadataIsEqual) {
listener.exceptionOccurred(cause);
exception = cause;
running = false;
}
}
doneSignal.countDown();
}
}