diff --git a/Model/pom.xml b/Model/pom.xml
index a843cabd8c..6565a81d7c 100644
--- a/Model/pom.xml
+++ b/Model/pom.xml
@@ -16,23 +16,7 @@
org.gusdb
- wsf-client
-
-
- javax.servlet
- javax.servlet-api
-
-
-
-
-
- org.gusdb
- wsf-common
-
-
-
- org.gusdb
- wsf-plugin
+ wdk-wsf
diff --git a/WSF/README.md b/WSF/README.md
new file mode 100644
index 0000000000..acf162d462
--- /dev/null
+++ b/WSF/README.md
@@ -0,0 +1,6 @@
+# WSF
+The [WDK](https://github.com/VEuPathDB/WDK) Web Services Framework: web service architecture to support WDK searches that run a process rather than issue an RDBMS query.
+
+Defines a [plugin API](Plugin/src/main/java/org/gusdb/wsf/plugin/Plugin.java) so that process-based searches can be plugged into the WDK search engine.
+
+This repo will be retired soon, when the plugin functionality will be folded into the WDK directly.
diff --git a/WSF/pom.xml b/WSF/pom.xml
new file mode 100644
index 0000000000..2f7526b0c1
--- /dev/null
+++ b/WSF/pom.xml
@@ -0,0 +1,65 @@
+
+ 4.0.0
+
+
+ org.gusdb
+ wdk
+ 1.0.0
+
+
+ Web Development Kit - WSF
+ wdk-wsf
+ jar
+
+
+
+
+ org.gusdb
+ fgputil-core
+
+
+
+ org.gusdb
+ fgputil-db
+
+
+
+ org.gusdb
+ fgputil-json
+
+
+
+ org.glassfish.jersey.core
+ jersey-client
+
+
+
+ org.json
+ json
+
+
+
+ javax.servlet
+ servlet-api
+ provided
+
+
+
+ org.apache.logging.log4j
+ log4j-core
+
+
+ org.apache.logging.log4j
+ log4j-1.2-api
+
+
+
+ junit
+ junit
+ test
+
+
+
+
+
diff --git a/WSF/src/main/java/org/gusdb/wsf/client/ClientModelException.java b/WSF/src/main/java/org/gusdb/wsf/client/ClientModelException.java
new file mode 100644
index 0000000000..0c44b61c55
--- /dev/null
+++ b/WSF/src/main/java/org/gusdb/wsf/client/ClientModelException.java
@@ -0,0 +1,31 @@
+package org.gusdb.wsf.client;
+
+
+/**
+ * @author Jerric
+ */
+public class ClientModelException extends Exception {
+
+ private static final long serialVersionUID = 2L;
+
+ public ClientModelException() {
+ super();
+ }
+
+ public ClientModelException(String message) {
+ super(message);
+ }
+
+ public ClientModelException(Throwable cause) {
+ super(cause);
+ }
+
+ public ClientModelException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public ClientModelException(String message, Throwable cause, boolean enableSuppression,
+ boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
+}
diff --git a/WSF/src/main/java/org/gusdb/wsf/client/ClientRequest.java b/WSF/src/main/java/org/gusdb/wsf/client/ClientRequest.java
new file mode 100644
index 0000000000..dd630ed1de
--- /dev/null
+++ b/WSF/src/main/java/org/gusdb/wsf/client/ClientRequest.java
@@ -0,0 +1,66 @@
+package org.gusdb.wsf.client;
+
+import org.gusdb.wsf.plugin.PluginRequest;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * @author jerric
+ */
+@SuppressWarnings("hiding")
+public class ClientRequest extends PluginRequest {
+
+ public static final String PLUGIN_KEY = "plugin";
+ public static final String PROJECT_KEY = PluginRequest.PROJECT_KEY;
+ public static final String COLUMNS_ARRAY_KEY = PluginRequest.COLUMNS_ARRAY_KEY;
+ public static final String PARAMETER_MAP_KEY = PluginRequest.PARAMETER_MAP_KEY;
+ public static final String CONTEXT_MAP_KEY = PluginRequest.CONTEXT_MAP_KEY;
+
+ private String pluginClass;
+
+ public ClientRequest() {
+ super();
+ }
+
+ public ClientRequest(ClientRequest request) {
+ super(request);
+ this.pluginClass = request.pluginClass;
+ }
+
+ public ClientRequest(String jsonString) throws ClientModelException {
+ try {
+ JSONObject jsRequest = new JSONObject(jsonString);
+ parseJSON(jsRequest);
+ this.pluginClass = jsRequest.getString(PLUGIN_KEY);
+ }
+ catch (JSONException ex) {
+ throw new ClientModelException(ex);
+ }
+ }
+
+ /**
+ * the full class name of the WSF plugin. The service will instantiate a plugin instance from this class
+ * name, and invoke it.
+ *
+ * @return the pluginClass
+ */
+ public String getPluginClass() {
+ return pluginClass;
+ }
+
+ /**
+ * @param pluginClass
+ * the pluginClass to set
+ */
+ public void setPluginClass(String pluginClass) {
+ this.pluginClass = pluginClass;
+ }
+
+ @Override
+ protected JSONObject getJSON() throws JSONException {
+ JSONObject jsRequest = super.getJSON();
+ jsRequest.put(PLUGIN_KEY, pluginClass);
+ return jsRequest;
+ }
+
+}
diff --git a/WSF/src/main/java/org/gusdb/wsf/client/ClientUserException.java b/WSF/src/main/java/org/gusdb/wsf/client/ClientUserException.java
new file mode 100644
index 0000000000..846f9a9b75
--- /dev/null
+++ b/WSF/src/main/java/org/gusdb/wsf/client/ClientUserException.java
@@ -0,0 +1,30 @@
+package org.gusdb.wsf.client;
+
+/**
+ * @author Jerric
+ */
+public class ClientUserException extends Exception {
+
+ private static final long serialVersionUID = 2L;
+
+ public ClientUserException() {
+ super();
+ }
+
+ public ClientUserException(String message) {
+ super(message);
+ }
+
+ public ClientUserException(Throwable cause) {
+ super(cause);
+ }
+
+ public ClientUserException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public ClientUserException(String message, Throwable cause,
+ boolean enableSuppression, boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
+}
diff --git a/WSF/src/main/java/org/gusdb/wsf/client/WsfClient.java b/WSF/src/main/java/org/gusdb/wsf/client/WsfClient.java
new file mode 100644
index 0000000000..09724a10a0
--- /dev/null
+++ b/WSF/src/main/java/org/gusdb/wsf/client/WsfClient.java
@@ -0,0 +1,11 @@
+package org.gusdb.wsf.client;
+
+import org.gusdb.wsf.plugin.DelayedResultException;
+
+public interface WsfClient {
+
+ void setResponseListener(WsfResponseListener listener);
+
+ int invoke(ClientRequest request) throws ClientModelException, ClientUserException, DelayedResultException;
+
+}
diff --git a/WSF/src/main/java/org/gusdb/wsf/client/WsfClientFactory.java b/WSF/src/main/java/org/gusdb/wsf/client/WsfClientFactory.java
new file mode 100644
index 0000000000..477a41aade
--- /dev/null
+++ b/WSF/src/main/java/org/gusdb/wsf/client/WsfClientFactory.java
@@ -0,0 +1,10 @@
+package org.gusdb.wsf.client;
+
+import java.net.URI;
+
+public interface WsfClientFactory {
+
+ WsfClient newClient(WsfResponseListener listener);
+
+ WsfClient newClient(WsfResponseListener listener, URI serviceURI);
+}
diff --git a/WSF/src/main/java/org/gusdb/wsf/client/WsfClientFactoryImpl.java b/WSF/src/main/java/org/gusdb/wsf/client/WsfClientFactoryImpl.java
new file mode 100644
index 0000000000..6a3d7432af
--- /dev/null
+++ b/WSF/src/main/java/org/gusdb/wsf/client/WsfClientFactoryImpl.java
@@ -0,0 +1,20 @@
+package org.gusdb.wsf.client;
+
+import java.net.URI;
+
+public final class WsfClientFactoryImpl implements WsfClientFactory {
+
+ @Override
+ public WsfClient newClient(WsfResponseListener listener) {
+ WsfClient client = new WsfLocalClient();
+ client.setResponseListener(listener);
+ return client;
+ }
+
+ @Override
+ public WsfClient newClient(WsfResponseListener listener, URI serviceURI) {
+ WsfClient client = new WsfRemoteClient(serviceURI);
+ client.setResponseListener(listener);
+ return client;
+ }
+}
diff --git a/WSF/src/main/java/org/gusdb/wsf/client/WsfLocalClient.java b/WSF/src/main/java/org/gusdb/wsf/client/WsfLocalClient.java
new file mode 100644
index 0000000000..f506cd44ad
--- /dev/null
+++ b/WSF/src/main/java/org/gusdb/wsf/client/WsfLocalClient.java
@@ -0,0 +1,76 @@
+package org.gusdb.wsf.client;
+
+import org.gusdb.wsf.plugin.DelayedResultException;
+import org.gusdb.wsf.plugin.PluginExecutor;
+import org.gusdb.wsf.plugin.PluginModelException;
+import org.gusdb.wsf.plugin.PluginResponse;
+import org.gusdb.wsf.plugin.PluginUserException;
+
+/**
+ * @author Jerric
+ */
+public class WsfLocalClient implements WsfClient, PluginResponse {
+
+ private WsfResponseListener listener;
+
+ protected WsfLocalClient() {}
+
+ @Override
+ public void addRow(String[] row) throws PluginModelException, PluginUserException {
+ try {
+ listener.onRowReceived(row);
+ }
+ catch (ClientModelException ex) {
+ throw new PluginModelException(ex);
+ }
+ catch (ClientUserException ex) {
+ throw new PluginUserException(ex);
+ }
+ }
+
+ @Override
+ public void addAttachment(String key, String attachment) throws PluginModelException, PluginUserException {
+ try {
+ listener.onAttachmentReceived(key, attachment);
+ }
+ catch (ClientModelException ex) {
+ throw new PluginModelException(ex);
+ }
+ catch (ClientUserException ex) {
+ throw new PluginUserException(ex);
+ }
+ }
+
+ @Override
+ public void setMessage(String message) throws PluginModelException, PluginUserException {
+ try {
+ listener.onMessageReceived(message);
+ }
+ catch (ClientModelException ex) {
+ throw new PluginModelException(ex);
+ }
+ catch (ClientUserException ex) {
+ throw new PluginUserException(ex);
+ }
+ }
+
+ @Override
+ public void setResponseListener(WsfResponseListener listener) {
+ this.listener = listener;
+ }
+
+ @Override
+ public int invoke(ClientRequest request) throws ClientModelException, ClientUserException, DelayedResultException {
+ PluginExecutor executor = new PluginExecutor();
+ String pluginClassName = request.getPluginClass();
+ try {
+ return executor.execute(pluginClassName, request, this);
+ }
+ catch (PluginModelException ex) {
+ throw new ClientModelException(ex);
+ }
+ catch (PluginUserException ex) {
+ throw new ClientUserException(ex);
+ }
+ }
+}
diff --git a/WSF/src/main/java/org/gusdb/wsf/client/WsfRemoteClient.java b/WSF/src/main/java/org/gusdb/wsf/client/WsfRemoteClient.java
new file mode 100644
index 0000000000..efe92c51fb
--- /dev/null
+++ b/WSF/src/main/java/org/gusdb/wsf/client/WsfRemoteClient.java
@@ -0,0 +1,165 @@
+package org.gusdb.wsf.client;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.net.URI;
+import java.time.Duration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.core.Form;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.apache.log4j.Logger;
+import org.glassfish.jersey.client.ClientProperties;
+import org.gusdb.wsf.common.ResponseAttachment;
+import org.gusdb.wsf.common.ResponseMessage;
+import org.gusdb.wsf.common.ResponseRow;
+import org.gusdb.wsf.common.ResponseStatus;
+import org.gusdb.wsf.common.WsfRequest;
+import org.gusdb.wsf.plugin.DelayedResultException;
+import org.gusdb.wsf.plugin.PluginUserException;
+
+public class WsfRemoteClient implements WsfClient {
+
+ private static final Logger LOG = Logger.getLogger(WsfRemoteClient.class);
+
+ private final URI serviceURI;
+
+ private WsfResponseListener listener;
+
+ protected WsfRemoteClient(URI serviceURI) {
+ this.serviceURI = serviceURI;
+ }
+
+ @Override
+ public void setResponseListener(WsfResponseListener listener) {
+ this.listener = listener;
+ }
+
+ @Override
+ public int invoke(ClientRequest request) throws ClientModelException, ClientUserException, DelayedResultException {
+ Client client = ClientBuilder.newClient();
+ int checksum = request.getChecksum();
+ LOG.debug("WSF Remote: checksum=" + checksum + ", url=" + serviceURI + "\n" + request);
+
+ // prepare the form
+ Form form = new Form();
+ form.param(WsfRequest.PARAM_REQUEST, request.toString());
+
+ // invoke service
+ Response response;
+ try {
+ final Optional timeout = request.getRemoteExecuteTimeout();
+ final Future responseFuture = client.target(serviceURI)
+ .property(ClientProperties.FOLLOW_REDIRECTS, Boolean.TRUE)
+ .request(MediaType.APPLICATION_OCTET_STREAM_TYPE)
+ .async()
+ .post(Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE));
+ if (timeout.isPresent()) {
+ response = responseFuture.get(timeout.get().toMillis(), TimeUnit.MILLISECONDS);
+ } else {
+ response = responseFuture.get();
+ }
+ } catch (InterruptedException ex) {
+ LOG.warn(String.format("Interrupted while invoking service at %s.", serviceURI.toString()), ex);
+ Thread.currentThread().interrupt();
+ throw new ClientModelException(ex);
+ } catch (ExecutionException | TimeoutException ex) {
+ LOG.warn(String.format("Exception while invoking service at %s.", serviceURI.toString()), ex);
+ throw new ClientModelException(ex);
+ }
+ int status = response.getStatus();
+ if (status >= 400)
+ throw new ClientModelException("Request failed with status code: " + status);
+
+ InputStream inStream = null;
+ int signal;
+ Map stats = new HashMap<>();
+ stats.put("rows", 0);
+ stats.put("attachments", 0);
+ try {
+ inStream = response.readEntity(InputStream.class);
+ signal = readStream(inStream, stats);
+ }
+ catch (ClassNotFoundException | IOException ex) {
+ throw new ClientModelException(ex);
+ }
+ finally {
+ if (inStream != null) {
+ try {
+ inStream.close();
+ }
+ catch (IOException ex) {
+ throw new ClientModelException(ex);
+ }
+ finally {
+ response.close();
+ }
+ }
+ LOG.debug("WSF Remote finished: checksum=" + checksum + ", status " + status + ", #rows=" +
+ stats.get("rows") + ", #attch=" + stats.get("attachments") + ", url=" + serviceURI);
+ }
+ return signal;
+ }
+
+ private int readStream(InputStream inStream, Map stats) throws ClientUserException,
+ ClientModelException, IOException, ClassNotFoundException, DelayedResultException {
+ ObjectInputStream objectStream = new ObjectInputStream(inStream);
+ while (true) {
+ Object object = objectStream.readUnshared();
+ if (object instanceof ResponseStatus) {
+ // received a status object, which should be the last object to receive
+ ResponseStatus status = (ResponseStatus) object;
+
+ // check if an exception is present
+ Exception exception = status.getException();
+ if (exception != null) {
+ if (exception instanceof PluginUserException) {
+ throw new ClientUserException(exception);
+ }
+ else if (exception instanceof DelayedResultException) {
+ throw (DelayedResultException) exception;
+ }
+ else if (exception instanceof RuntimeException) {
+ throw (RuntimeException) exception;
+ }
+ else {
+ throw new ClientModelException(exception);
+ }
+ }
+
+ // return the signal
+ return status.getSignal();
+ }
+ else if (object instanceof ResponseRow) { // received a new row from the service
+ ResponseRow row = (ResponseRow) object;
+ listener.onRowReceived(row.getRow());
+ stats.put("rows", stats.get("rows") + 1);
+ }
+ else if (object instanceof ResponseAttachment) { // received a new attachment
+ ResponseAttachment attachment = (ResponseAttachment) object;
+ listener.onAttachmentReceived(attachment.getKey(), attachment.getContent());
+ stats.put("rows", stats.get("attachments") + 1);
+ }
+ else if (object instanceof ResponseMessage) { // received message
+ ResponseMessage message = (ResponseMessage) object;
+ listener.onMessageReceived(message.getMessage());
+ }
+ else {
+ throw new ClientModelException("Unknown object type received: [" + object.getClass().getName() +
+ "] - " + object);
+ }
+ }
+ }
+}
diff --git a/WSF/src/main/java/org/gusdb/wsf/client/WsfResponseListener.java b/WSF/src/main/java/org/gusdb/wsf/client/WsfResponseListener.java
new file mode 100644
index 0000000000..dd7b8d92ed
--- /dev/null
+++ b/WSF/src/main/java/org/gusdb/wsf/client/WsfResponseListener.java
@@ -0,0 +1,12 @@
+package org.gusdb.wsf.client;
+
+
+
+public interface WsfResponseListener {
+
+ void onRowReceived(String[] row) throws ClientModelException, ClientUserException;
+
+ void onAttachmentReceived(String key, String content) throws ClientModelException, ClientUserException;
+
+ void onMessageReceived(String message) throws ClientModelException, ClientUserException;
+}
diff --git a/WSF/src/main/java/org/gusdb/wsf/common/ResponseAttachment.java b/WSF/src/main/java/org/gusdb/wsf/common/ResponseAttachment.java
new file mode 100644
index 0000000000..9e6013fddd
--- /dev/null
+++ b/WSF/src/main/java/org/gusdb/wsf/common/ResponseAttachment.java
@@ -0,0 +1,31 @@
+package org.gusdb.wsf.common;
+
+import java.io.Serializable;
+
+public class ResponseAttachment implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ private final String key;
+ private final String content;
+
+ public ResponseAttachment(String key, String content) {
+ this.key = key;
+ this.content = content;
+ }
+
+ /**
+ * @return the key
+ */
+ public String getKey() {
+ return key;
+ }
+
+ /**
+ * @return the content
+ */
+ public String getContent() {
+ return content;
+ }
+
+}
diff --git a/WSF/src/main/java/org/gusdb/wsf/common/ResponseMessage.java b/WSF/src/main/java/org/gusdb/wsf/common/ResponseMessage.java
new file mode 100644
index 0000000000..2aac187cce
--- /dev/null
+++ b/WSF/src/main/java/org/gusdb/wsf/common/ResponseMessage.java
@@ -0,0 +1,22 @@
+package org.gusdb.wsf.common;
+
+import java.io.Serializable;
+
+public class ResponseMessage implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ private final String message;
+
+ public ResponseMessage(String message) {
+ this.message = message;
+ }
+
+ /**
+ * @return the message
+ */
+ public String getMessage() {
+ return message;
+ }
+
+}
diff --git a/WSF/src/main/java/org/gusdb/wsf/common/ResponseRow.java b/WSF/src/main/java/org/gusdb/wsf/common/ResponseRow.java
new file mode 100644
index 0000000000..a0b3bdd088
--- /dev/null
+++ b/WSF/src/main/java/org/gusdb/wsf/common/ResponseRow.java
@@ -0,0 +1,22 @@
+package org.gusdb.wsf.common;
+
+import java.io.Serializable;
+
+public class ResponseRow implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ private final String[] row;
+
+ public ResponseRow(String[] row) {
+ this.row = row;
+ }
+
+ /**
+ * @return the row
+ */
+ public String[] getRow() {
+ return row;
+ }
+
+}
diff --git a/WSF/src/main/java/org/gusdb/wsf/common/ResponseStatus.java b/WSF/src/main/java/org/gusdb/wsf/common/ResponseStatus.java
new file mode 100644
index 0000000000..6d5c66834f
--- /dev/null
+++ b/WSF/src/main/java/org/gusdb/wsf/common/ResponseStatus.java
@@ -0,0 +1,43 @@
+package org.gusdb.wsf.common;
+
+import java.io.Serializable;
+
+public class ResponseStatus implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ private int signal;
+
+ private Exception exception;
+
+ public void setSignal(int signal) {
+ this.signal = signal;
+ }
+
+ /**
+ * @return the signal
+ */
+ public int getSignal() {
+ return signal;
+ }
+
+ /**
+ * @return the exception
+ */
+ public Exception getException() {
+ return exception;
+ }
+
+ /**
+ * @param exception
+ * the exception to set
+ */
+ public void setException(Exception exception) {
+ this.exception = exception;
+ }
+
+ @Override
+ public String toString() {
+ return "signal=" + signal + ", exception=" + exception;
+ }
+}
diff --git a/WSF/src/main/java/org/gusdb/wsf/common/WsfRequest.java b/WSF/src/main/java/org/gusdb/wsf/common/WsfRequest.java
new file mode 100644
index 0000000000..d8bbd22084
--- /dev/null
+++ b/WSF/src/main/java/org/gusdb/wsf/common/WsfRequest.java
@@ -0,0 +1,39 @@
+package org.gusdb.wsf.common;
+
+import java.util.Map;
+
+public interface WsfRequest {
+
+ String PARAM_REQUEST = "request";
+ String REMOTE_EXECUTE_TIMEOUT_ISO_8601_CONTEXT_KEY = "timeout_iso_8601";
+
+ /**
+ * @return the projectId
+ */
+ String getProjectId();
+
+ /**
+ * @return the params
+ */
+ Map getParams();
+
+ /**
+ * @return the orderedColumns
+ */
+ String[] getOrderedColumns();
+
+ /**
+ * @return a map of ordered columns, where the key is the column name, and the
+ * value is the zero-based order of that column.
+ */
+ Map getColumnMap();
+
+ /**
+ * The context can be used to hold additional information, such as user id,
+ * calling query name, etc, which can be used by plugins.
+ *
+ * @return the context
+ */
+ Map getContext();
+
+}
diff --git a/WSF/src/main/java/org/gusdb/wsf/plugin/AbstractPlugin.java b/WSF/src/main/java/org/gusdb/wsf/plugin/AbstractPlugin.java
new file mode 100644
index 0000000000..faec20b190
--- /dev/null
+++ b/WSF/src/main/java/org/gusdb/wsf/plugin/AbstractPlugin.java
@@ -0,0 +1,292 @@
+package org.gusdb.wsf.plugin;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.util.InvalidPropertiesFormatException;
+import java.util.List;
+import java.util.Optional;
+import java.util.Properties;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import javax.sql.DataSource;
+
+import org.apache.log4j.Logger;
+import org.gusdb.fgputil.FormatUtil;
+import org.gusdb.fgputil.db.stream.ResultSets;
+import org.gusdb.fgputil.runtime.GusHome;
+
+/**
+ * An abstract super class for all WSF plugins. This class is a SINGLETON.
+ * Instance variables apply to all calls of the plugin.
+ *
+ * @author Jerric
+ * @since Feb 9, 2006
+ */
+public abstract class AbstractPlugin implements Plugin {
+
+ protected abstract int execute(PluginRequest request, PluginResponse response) throws PluginModelException,
+ PluginUserException, DelayedResultException;
+
+ /**
+ * The logger for this plugin. It is a recommended way to record standard
+ * output and error messages.
+ */
+ private static final Logger LOG = Logger.getLogger(AbstractPlugin.class);
+
+ /**
+ * It stores the properties defined in the configuration file. If the plugin
+ * doesn't use a configuration file, this map is empty.
+ */
+ protected Properties properties;
+
+ private String propertyFile;
+
+ /**
+ * Initialize a plugin with empty properties
+ */
+ public AbstractPlugin() {
+ properties = new Properties();
+ }
+
+ /**
+ * Initialize a plugin and assign a property file to it
+ *
+ * @param propertyFile
+ * the name of the property file. The base class will resolve the path to
+ * this file, which should be under the WEB-INF of axis' webapps.
+ */
+ public AbstractPlugin(String propertyFile) {
+ this();
+ this.propertyFile = propertyFile;
+ }
+
+ @Override
+ public void initialize(PluginRequest request) throws PluginModelException {
+ // load the properties
+ if (propertyFile != null) {
+ try {
+ loadConfiguration();
+ }
+ catch (IOException ex) {
+ LOG.error(ex);
+ throw new PluginModelException(ex);
+ }
+ }
+ }
+
+ @Override
+ public int invoke(PluginRequest request, PluginResponse response) throws PluginModelException,
+ PluginUserException, DelayedResultException {
+ try {
+ return execute(request, response);
+ }
+ catch (PluginModelException ex) {
+ throw ex;
+ }
+ }
+
+ private void loadConfiguration() throws InvalidPropertiesFormatException, IOException, PluginModelException {
+ String configDir = null;
+ String filePath;
+
+ String gusHome = GusHome.getGusHome();
+ if (gusHome != null)
+ configDir = gusHome + "/config/";
+
+ // if config is null, try loading the resource from class path root.
+ if (configDir == null) {
+ URL url = this.getClass().getResource("/" + propertyFile);
+ if (url == null)
+ throw new PluginModelException("property file cannot be found " + "in the class path: " +
+ propertyFile);
+
+ filePath = url.toString();
+ }
+ else {
+ if (!configDir.endsWith("/"))
+ configDir += "/";
+ String path = configDir + propertyFile;
+ File file = new File(path);
+ if (!file.exists() || !file.isFile())
+ throw new PluginModelException("property file cannot be found " + " in the configuration path: " +
+ path);
+
+ filePath = path;
+ }
+ LOG.debug("WSF Plugin prop file: " + filePath);
+
+ InputStream in = new FileInputStream(filePath);
+ properties.loadFromXML(in);
+ in.close();
+ }
+
+ protected String getProperty(String propertyName) {
+ return properties.getProperty(propertyName);
+ }
+
+ protected boolean hasProperty(String propertyName) {
+ return properties.containsKey(propertyName);
+ }
+
+ protected int invokeCommand(String[] command, StringBuffer result, long timeout)
+ throws PluginUserException, PluginModelException {
+ return invokeCommand(command, result, timeout, null);
+ }
+
+ /**
+ * @param command
+ * the command array. If you have param values with spaces in it, put the
+ * value into one cell to avoid the value to be splitted.
+ * @param timeout
+ * the maximum allowed time for the command to run, in seconds
+ * @param result
+ * Contains raw output of the command.
+ * @param env
+ * a string including env variables, as expected by exec. Useful to pass in
+ * a PATH
+ *
+ * @return the exit code of the invoked command
+ *
+ * @throws PluginUserException
+ * if user input is invalid
+ * @throws PluginModelException
+ * if something goes wrong during execution
+ */
+ protected int invokeCommand(String[] command, StringBuffer result, long timeout, String[] env)
+ throws PluginUserException, PluginModelException {
+ LOG.info("WsfPlugin.invokeCommand: " + FormatUtil.printArray(command));
+ // invoke the command
+ Process process;
+ try {
+ process = Runtime.getRuntime().exec(command, env);
+ }
+ catch (IOException ex) {
+ throw new PluginModelException(ex);
+ }
+
+ StringBuffer sbErr = new StringBuffer();
+ StringBuffer sbOut = new StringBuffer();
+
+ // any error message?
+ StreamGobbler errorGobbler = new StreamGobbler(process.getErrorStream(), "ERROR", sbErr);
+ // any output?
+ StreamGobbler outputGobbler = new StreamGobbler(process.getInputStream(), "OUTPUT", sbOut);
+ LOG.info("kicking off the stderr and stdout stream gobbling threads...");
+ errorGobbler.start();
+ outputGobbler.start();
+
+ long start = System.currentTimeMillis();
+ long limit = timeout * 1000;
+ // check the exit value of the process; if the process is not
+ // finished yet, an IllegalThreadStateException is thrown out
+ int signal = -1;
+ while (true) {
+ //LOG.debug("waiting for 1 second ...");
+ try {
+ Thread.sleep(1000);
+ }
+ catch (InterruptedException ex) {
+ // do nothing, keep looping
+ }
+
+ try {
+ signal = process.exitValue(); // throws IllegalThreadStateException if the process is still running.
+
+ // process is stopped.
+ result.append((signal == 0) ? sbOut : sbErr);
+ break;
+ }
+ catch (IllegalThreadStateException ex) {
+ // if the timeout is set to <= 0, keep waiting till the process
+ // is finished
+ if (timeout <= 0)
+ continue;
+
+ // otherwise, check if time's up
+ long time = System.currentTimeMillis() - start;
+ if (time > limit) {
+ // convert string array to string
+ StringBuilder buffer = new StringBuilder();
+ for (String piece : command) {
+ if (buffer.length() > 0)
+ buffer.append(" ");
+ buffer.append(piece);
+ }
+ LOG.warn("Time out, the command is cancelled: " + buffer);
+ outputGobbler.close();
+ errorGobbler.close();
+ process.destroy();
+ throw new PluginTimeoutException("Time out, " + timeout/60 + " minutes, the command is cancelled. We suggest you review the input parameters and try again.\n");
+ }
+ }
+ }
+ return signal;
+ }
+
+ class StreamGobbler extends Thread {
+
+ InputStream is;
+ String type;
+ StringBuffer sb;
+
+ StreamGobbler(InputStream is, String type, StringBuffer sb) {
+ this.is = is;
+ this.type = type;
+ this.sb = sb;
+ }
+
+ @Override
+ public void run() {
+ try {
+ BufferedReader br = new BufferedReader(new InputStreamReader(is));
+ String line;
+ while ((line = br.readLine()) != null) {
+ // sb.append(type + ">" + line);
+ sb.append(line + FormatUtil.NL);
+ }
+ }
+ catch (IOException ex) {
+ ex.printStackTrace();
+ }
+ finally {
+ try {
+ is.close();
+ }
+ catch (IOException ex) {
+ ex.printStackTrace();
+ }
+ }
+ }
+
+ public void close() {
+ try {
+ is.close();
+ }
+ catch (IOException ex) {
+ ex.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * Run an sql select statement to acquire a list value to use as a parameter.
+ *
+ * @return a list comprised of the values found in the first column of the sql
+ * result
+ */
+ protected List getParamValueFromSql(String sql, String queryDescrip, DataSource dataSource) throws PluginModelException {
+ try(Stream values = ResultSets.openStream(dataSource, sql, queryDescrip,
+ rs -> Optional.of(rs.getString(1)))) {
+ return values.collect(Collectors.toList());
+ }
+ catch (Exception ex) {
+ throw new PluginModelException(ex);
+ }
+ }
+}
diff --git a/WSF/src/main/java/org/gusdb/wsf/plugin/DelayedResultException.java b/WSF/src/main/java/org/gusdb/wsf/plugin/DelayedResultException.java
new file mode 100644
index 0000000000..7e88103b4e
--- /dev/null
+++ b/WSF/src/main/java/org/gusdb/wsf/plugin/DelayedResultException.java
@@ -0,0 +1,5 @@
+package org.gusdb.wsf.plugin;
+
+public class DelayedResultException extends Exception {
+
+}
diff --git a/WSF/src/main/java/org/gusdb/wsf/plugin/Plugin.java b/WSF/src/main/java/org/gusdb/wsf/plugin/Plugin.java
new file mode 100644
index 0000000000..c0d817533e
--- /dev/null
+++ b/WSF/src/main/java/org/gusdb/wsf/plugin/Plugin.java
@@ -0,0 +1,52 @@
+package org.gusdb.wsf.plugin;
+
+/**
+ * @author Jerric
+ * @since Feb 10, 2006
+ */
+public interface Plugin {
+
+ /**
+ * Invoke a plugin, using the parameters in the request, and save the result
+ * into response.
+ *
+ * @return the signal set by the plugin
+ */
+ int invoke(PluginRequest request, PluginResponse response)
+ throws PluginModelException, PluginUserException, DelayedResultException;
+
+ /**
+ * The Plugin needs to provide a list of required parameter names; the base
+ * class will use this template method in the input validation process.
+ *
+ * @return returns an array the names of the required parameters
+ */
+ String[] getRequiredParameterNames();
+
+ /**
+ * The Plugin needs to provides a list of the columns expected in the result;
+ * the base class will use this template method in the input validation
+ * process.
+ * @param request
+ *
+ * @return returns an array the columns expected in the result
+ * @throws PluginModelException
+ */
+ String[] getColumns(PluginRequest request) throws PluginModelException;
+
+ /**
+ * Initialize the plugin instance.
+ *
+ * @param request that spurred creation of this plugin
+ */
+ void initialize(PluginRequest request) throws PluginModelException;
+
+ /**
+ * Validate the parameters passed by the service. This validation confirms
+ * that the service (the wdk model) has parameter options that agree with this
+ * plugin's API.
+ */
+ void validateParameters(PluginRequest request)
+ throws PluginModelException, PluginUserException;
+
+}
diff --git a/WSF/src/main/java/org/gusdb/wsf/plugin/PluginExecutor.java b/WSF/src/main/java/org/gusdb/wsf/plugin/PluginExecutor.java
new file mode 100644
index 0000000000..d7a205a43f
--- /dev/null
+++ b/WSF/src/main/java/org/gusdb/wsf/plugin/PluginExecutor.java
@@ -0,0 +1,88 @@
+package org.gusdb.wsf.plugin;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.log4j.Logger;
+
+public class PluginExecutor {
+
+ private static final Logger LOG = Logger.getLogger(PluginExecutor.class);
+
+ public int execute(String pluginClassName, PluginRequest request, PluginResponse response)
+ throws PluginModelException, PluginUserException, DelayedResultException {
+ LOG.info("Invoking: " + pluginClassName + ", projectId: " + request.getProjectId());
+ LOG.debug("request: " + request.toString());
+
+ // use reflection to load the plugin object
+ LOG.debug("Loading object " + pluginClassName);
+
+ LOG.info("Creating plugin " + pluginClassName);
+
+ try {
+ Class extends Plugin> pluginClass = Class.forName(pluginClassName).asSubclass(Plugin.class);
+ Plugin plugin = pluginClass.getDeclaredConstructor().newInstance();
+
+ // initialize plugin
+ plugin.initialize(request);
+
+ // invoke the plugin
+ LOG.debug("Invoking Plugin " + pluginClassName);
+ return invokePlugin(plugin, request, response);
+ }
+ catch (ClassNotFoundException | InstantiationException | IllegalAccessException |
+ IllegalArgumentException | InvocationTargetException |
+ NoSuchMethodException | SecurityException ex) {
+ throw new PluginModelException(ex);
+ }
+ }
+
+ private int invokePlugin(Plugin plugin, PluginRequest request, PluginResponse response)
+ throws PluginModelException, PluginUserException, DelayedResultException {
+ // validate required parameters
+ LOG.debug("validing required params...");
+ validateRequiredParameters(plugin, request);
+
+ // validate columns
+ LOG.debug("validating columns...");
+ validateColumns(plugin, request);
+
+ // validate parameters
+ LOG.debug("validating params...");
+ plugin.validateParameters(request);
+
+ // execute the main function, and obtain result
+ LOG.debug("invoking plugin...");
+ return plugin.invoke(request, response);
+ }
+
+ private void validateRequiredParameters(Plugin plugin, PluginRequest request) throws PluginUserException {
+ String[] reqParams = plugin.getRequiredParameterNames();
+
+ // validate parameters
+ Map params = request.getParams();
+ for (String param : reqParams) {
+ if (!params.containsKey(param)) {
+ throw new PluginUserException("The required parameter is missing: " + param);
+ }
+ }
+ }
+
+ private void validateColumns(Plugin plugin, PluginRequest request) throws PluginUserException, PluginModelException {
+ String[] expectedColumns = request.getOrderedColumns();
+ String[] requiredColumns = plugin.getColumns(request);
+
+ Set colSet = new HashSet();
+ for (String col : expectedColumns) {
+ colSet.add(col);
+ }
+ for (String col : requiredColumns) {
+ if (!colSet.contains(col)) {
+ throw new PluginUserException("The required column is missing: " + col);
+ }
+ }
+ }
+
+}
diff --git a/WSF/src/main/java/org/gusdb/wsf/plugin/PluginModelException.java b/WSF/src/main/java/org/gusdb/wsf/plugin/PluginModelException.java
new file mode 100644
index 0000000000..82097ac4a9
--- /dev/null
+++ b/WSF/src/main/java/org/gusdb/wsf/plugin/PluginModelException.java
@@ -0,0 +1,31 @@
+package org.gusdb.wsf.plugin;
+
+/**
+ * @author Jerric
+ */
+public class PluginModelException extends Exception {
+
+ private static final long serialVersionUID = 2;
+
+ public PluginModelException() {
+ super();
+ }
+
+ public PluginModelException(String message) {
+ super(message);
+ }
+
+ public PluginModelException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public PluginModelException(Throwable cause) {
+ super(cause.getMessage(), cause);
+ }
+
+ public PluginModelException(String message, Throwable cause,
+ boolean enableSuppression, boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
+
+}
diff --git a/WSF/src/main/java/org/gusdb/wsf/plugin/PluginRequest.java b/WSF/src/main/java/org/gusdb/wsf/plugin/PluginRequest.java
new file mode 100644
index 0000000000..63801deb21
--- /dev/null
+++ b/WSF/src/main/java/org/gusdb/wsf/plugin/PluginRequest.java
@@ -0,0 +1,227 @@
+package org.gusdb.wsf.plugin;
+
+import java.time.Duration;
+import java.util.*;
+
+import org.gusdb.fgputil.json.JsonUtil;
+import org.gusdb.wsf.common.WsfRequest;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * @author jerric
+ */
+public class PluginRequest implements WsfRequest {
+
+ public static final String PROJECT_KEY = "project";
+ public static final String COLUMNS_ARRAY_KEY = "ordered-columns";
+ public static final String PARAMETER_MAP_KEY = "parameters";
+ public static final String CONTEXT_MAP_KEY = "context";
+
+ private String _projectId;
+ private Map _params;
+ private List _orderedColumns;
+ private Map _context = new HashMap<>();
+
+ public PluginRequest() {
+ this._params = new HashMap<>();
+ this._orderedColumns = new ArrayList<>();
+ this._context = new HashMap<>();
+ }
+
+ public PluginRequest(PluginRequest request) {
+ this._projectId = request.getProjectId();
+ this._params = new HashMap<>(request.getParams());
+ this._orderedColumns = new ArrayList<>(Arrays.asList(request.getOrderedColumns()));
+ this._context = new HashMap<>(request.getContext());
+ }
+
+ public PluginRequest(String jsonString) throws PluginModelException {
+ try {
+ parseJSON(new JSONObject(jsonString));
+ }
+ catch (JSONException ex) {
+ throw new PluginModelException(ex);
+ }
+ }
+
+ public int getChecksum() {
+ String content = toString();
+ int checksum = 0;
+ for (int i = 0; i < content.length(); i++) {
+ checksum ^= content.charAt(i);
+ }
+ return checksum;
+ }
+
+ protected JSONObject getJSON() throws JSONException {
+ JSONObject jsRequest = new JSONObject();
+ jsRequest.put(PROJECT_KEY, getProjectId());
+
+ // output columns
+ JSONArray jsColumns = new JSONArray();
+ for (String column : getOrderedColumns()) {
+ jsColumns.put(column);
+ }
+ jsRequest.put(COLUMNS_ARRAY_KEY, jsColumns);
+
+ // output params
+ JSONObject jsParams = new JSONObject();
+ Map params = getParams();
+ for (String paramName : params.keySet()) {
+ jsParams.put(paramName, params.get(paramName));
+ }
+ jsRequest.put(PARAMETER_MAP_KEY, jsParams);
+
+ // output request context
+ JSONObject jsContext = new JSONObject();
+ Map context = getContext();
+ for (String contextKey : context.keySet()) {
+ jsContext.put(contextKey, context.get(contextKey));
+ }
+ jsRequest.put(CONTEXT_MAP_KEY, jsContext);
+ return jsRequest;
+ }
+
+ @Override
+ public String toString() {
+ try {
+ return getJSON().toString();
+ }
+ catch (JSONException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ protected void parseJSON(JSONObject jsRequest) throws JSONException {
+ if (jsRequest.has(PROJECT_KEY))
+ setProjectId(jsRequest.getString(PROJECT_KEY));
+
+ JSONArray jsColumns = jsRequest.getJSONArray(COLUMNS_ARRAY_KEY);
+ List columns = new ArrayList<>();
+ for (int i = 0; i < jsColumns.length(); i++) {
+ columns.add(jsColumns.getString(i));
+ }
+ setOrderedColumns(columns.toArray(new String[0]));
+
+ Map params = new LinkedHashMap<>();
+ addToMap(params, jsRequest.getJSONObject(PARAMETER_MAP_KEY));
+ setParams(params);
+
+ Map context = new LinkedHashMap<>();
+ addToMap(context, jsRequest.getJSONObject(CONTEXT_MAP_KEY));
+ setContext(context);
+ }
+
+ private static void addToMap(Map map, JSONObject newValues) throws JSONException {
+ for (String key : JsonUtil.getKeys(newValues)) {
+ map.put(key, newValues.getString(key));
+ }
+ }
+
+ /**
+ * @return the projectId
+ */
+ @Override
+ public String getProjectId() {
+ return _projectId;
+ }
+
+ /**
+ * @param projectId
+ * the projectId to set
+ */
+ public void setProjectId(String projectId) {
+ this._projectId = projectId;
+ }
+
+ /**
+ * @return the params
+ */
+ @Override
+ public Map getParams() {
+ return new HashMap<>(_params);
+ }
+
+ /**
+ * @param params
+ * the params to set
+ */
+ public void setParams(Map params) {
+ this._params = new HashMap<>(params);
+ }
+
+ public void putParam(String name, String value) {
+ this._params.put(name, value);
+ }
+
+ /**
+ * @return the orderedColumns
+ */
+ @Override
+ public String[] getOrderedColumns() {
+ String[] array = new String[_orderedColumns.size()];
+ _orderedColumns.toArray(array);
+ return array;
+ }
+
+ /**
+ * @return a map of ordered columns, where the key is the column name, and the
+ * value is the zero-based order of that column.
+ */
+ @Override
+ public Map getColumnMap() {
+ Map map = new LinkedHashMap<>();
+ for (int i = 0; i < _orderedColumns.size(); i++) {
+ map.put(_orderedColumns.get(i), i);
+ }
+ return map;
+ }
+
+ /**
+ * @param orderedColumns
+ * the orderedColumns to set
+ */
+ public void setOrderedColumns(String[] orderedColumns) {
+ this._orderedColumns = new ArrayList<>(orderedColumns.length);
+ for (String column : orderedColumns) {
+ this._orderedColumns.add(column);
+ }
+ }
+
+ /**
+ * The context can be used to hold additional information, such as user id,
+ * calling query name, etc, which can be used by plugins.
+ *
+ * @return the context
+ */
+ @Override
+ public Map getContext() {
+ return new HashMap<>(_context);
+ }
+
+ /**
+ * @param context
+ * the context to set
+ */
+ public void setContext(Map context) {
+ this._context = new HashMap<>(context);
+ }
+
+ public void appendContext(String key, String value) {
+ this._context = new HashMap<>(_context);
+ this._context.put(key, value);
+ }
+
+ public void setContextTimeout(Duration value) {
+ this.appendContext(REMOTE_EXECUTE_TIMEOUT_ISO_8601_CONTEXT_KEY, value.toString());
+ }
+
+ public Optional getRemoteExecuteTimeout() {
+ return Optional.ofNullable(this._context.get(REMOTE_EXECUTE_TIMEOUT_ISO_8601_CONTEXT_KEY))
+ .map(Duration::parse)
+ .filter(duration -> !duration.isZero());
+ }
+
+}
diff --git a/WSF/src/main/java/org/gusdb/wsf/plugin/PluginResponse.java b/WSF/src/main/java/org/gusdb/wsf/plugin/PluginResponse.java
new file mode 100644
index 0000000000..365ebcd453
--- /dev/null
+++ b/WSF/src/main/java/org/gusdb/wsf/plugin/PluginResponse.java
@@ -0,0 +1,11 @@
+package org.gusdb.wsf.plugin;
+
+
+public interface PluginResponse {
+
+ void addRow(String[] row) throws PluginModelException, PluginUserException;
+
+ void addAttachment(String key, String content) throws PluginModelException, PluginUserException;
+
+ void setMessage(String message) throws PluginModelException, PluginUserException;
+}
diff --git a/WSF/src/main/java/org/gusdb/wsf/plugin/PluginTimeoutException.java b/WSF/src/main/java/org/gusdb/wsf/plugin/PluginTimeoutException.java
new file mode 100644
index 0000000000..194151ac03
--- /dev/null
+++ b/WSF/src/main/java/org/gusdb/wsf/plugin/PluginTimeoutException.java
@@ -0,0 +1,34 @@
+package org.gusdb.wsf.plugin;
+
+
+
+/**
+ * @author Cristina
+ * @since Feb 21, 2017
+ */
+public class PluginTimeoutException extends PluginModelException {
+
+ private static final long serialVersionUID = 2;
+
+ public PluginTimeoutException() {
+ super();
+ }
+
+ public PluginTimeoutException(String message) {
+ super(message);
+ }
+
+ public PluginTimeoutException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public PluginTimeoutException(Throwable cause) {
+ super(cause.getMessage(), cause);
+ }
+
+ public PluginTimeoutException(String message, Throwable cause,
+ boolean enableSuppression, boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
+
+}
diff --git a/WSF/src/main/java/org/gusdb/wsf/plugin/PluginUserException.java b/WSF/src/main/java/org/gusdb/wsf/plugin/PluginUserException.java
new file mode 100644
index 0000000000..6fcb2f80f5
--- /dev/null
+++ b/WSF/src/main/java/org/gusdb/wsf/plugin/PluginUserException.java
@@ -0,0 +1,30 @@
+package org.gusdb.wsf.plugin;
+
+/**
+ * @author Jerric
+ */
+public class PluginUserException extends Exception {
+
+ private static final long serialVersionUID = 2;
+
+ public PluginUserException() {
+ }
+
+ public PluginUserException(String message) {
+ super(message);
+ }
+
+ public PluginUserException(Throwable cause) {
+ super(cause);
+ }
+
+ public PluginUserException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public PluginUserException(String message, Throwable cause,
+ boolean enableSuppression, boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
+
+}
diff --git a/WSF/src/main/java/org/gusdb/wsf/service/ServiceModelException.java b/WSF/src/main/java/org/gusdb/wsf/service/ServiceModelException.java
new file mode 100644
index 0000000000..4bcc154b53
--- /dev/null
+++ b/WSF/src/main/java/org/gusdb/wsf/service/ServiceModelException.java
@@ -0,0 +1,27 @@
+package org.gusdb.wsf.service;
+
+public class ServiceModelException extends Exception {
+
+ private static final long serialVersionUID = -9161875379915097906L;
+
+ public ServiceModelException() {
+ }
+
+ public ServiceModelException(String message) {
+ super(message);
+ }
+
+ public ServiceModelException(Throwable cause) {
+ super(cause);
+ }
+
+ public ServiceModelException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public ServiceModelException(String message, Throwable cause,
+ boolean enableSuppression, boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
+
+}
diff --git a/WSF/src/main/java/org/gusdb/wsf/service/ServiceRequest.java b/WSF/src/main/java/org/gusdb/wsf/service/ServiceRequest.java
new file mode 100644
index 0000000000..40b89fc627
--- /dev/null
+++ b/WSF/src/main/java/org/gusdb/wsf/service/ServiceRequest.java
@@ -0,0 +1,60 @@
+package org.gusdb.wsf.service;
+
+import org.gusdb.wsf.plugin.PluginRequest;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * @author jerric
+ */
+public class ServiceRequest extends PluginRequest {
+
+ private static final String PLUGIN_KEY = "plugin";
+
+ private String pluginClass;
+
+ public ServiceRequest() {
+ super();
+ }
+
+ public ServiceRequest(ServiceRequest request) {
+ super(request);
+ this.pluginClass = request.pluginClass;
+ }
+
+ public ServiceRequest(String jsonString) throws ServiceModelException {
+ try {
+ JSONObject jsRequest = new JSONObject(jsonString);
+ parseJSON(jsRequest);
+ this.pluginClass = jsRequest.getString(PLUGIN_KEY);
+ }
+ catch (JSONException ex) {
+ throw new ServiceModelException(ex);
+ }
+ }
+
+ /**
+ * the full class name of the WSF plugin. The service will instantiate a
+ * plugin instance from this class name, and invoke it.
+ *
+ * @return the pluginClass
+ */
+ public String getPluginClass() {
+ return pluginClass;
+ }
+
+ /**
+ * @param pluginClass
+ * the pluginClass to set
+ */
+ public void setPluginClass(String pluginClass) {
+ this.pluginClass = pluginClass;
+ }
+
+ @Override
+ protected JSONObject getJSON() throws JSONException {
+ JSONObject jsRequest = super.getJSON();
+ jsRequest.put(PLUGIN_KEY, pluginClass);
+ return jsRequest;
+ }
+}
diff --git a/WSF/src/main/java/org/gusdb/wsf/service/StreamingPluginResponse.java b/WSF/src/main/java/org/gusdb/wsf/service/StreamingPluginResponse.java
new file mode 100644
index 0000000000..54289368a3
--- /dev/null
+++ b/WSF/src/main/java/org/gusdb/wsf/service/StreamingPluginResponse.java
@@ -0,0 +1,67 @@
+package org.gusdb.wsf.service;
+
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+
+import org.gusdb.wsf.common.ResponseAttachment;
+import org.gusdb.wsf.common.ResponseMessage;
+import org.gusdb.wsf.common.ResponseRow;
+import org.gusdb.wsf.plugin.PluginModelException;
+import org.gusdb.wsf.plugin.PluginResponse;
+
+public class StreamingPluginResponse implements PluginResponse {
+
+ private final ObjectOutputStream outStream;
+
+ private int rowCount;
+ private int attachmentCount;
+
+ public StreamingPluginResponse(ObjectOutputStream outStream) {
+ this.outStream = outStream;
+ }
+
+ public int getRowCount() {
+ return rowCount;
+ }
+
+ public int getAttachmentCount() {
+ return attachmentCount;
+ }
+
+ @Override
+ public void addRow(String[] row) throws PluginModelException {
+ ResponseRow responseRow = new ResponseRow(row);
+ try {
+ outStream.writeObject(responseRow);
+ rowCount++;
+ }
+ catch (IOException ex) {
+ throw new PluginModelException(ex);
+ }
+ }
+
+ @Override
+ public void addAttachment(String key, String content)
+ throws PluginModelException {
+ ResponseAttachment attachment = new ResponseAttachment(key, content);
+ try {
+ outStream.writeObject(attachment);
+ attachmentCount++;
+ }
+ catch (IOException ex) {
+ throw new PluginModelException(ex);
+ }
+ }
+
+ @Override
+ public void setMessage(String message) throws PluginModelException {
+ ResponseMessage responseMessage = new ResponseMessage(message);
+ try {
+ outStream.writeObject(responseMessage);
+ }
+ catch (IOException ex) {
+ throw new PluginModelException(ex);
+ }
+ }
+
+}
diff --git a/WSF/src/main/java/org/gusdb/wsf/service/WsfService.java b/WSF/src/main/java/org/gusdb/wsf/service/WsfService.java
new file mode 100644
index 0000000000..73a240031f
--- /dev/null
+++ b/WSF/src/main/java/org/gusdb/wsf/service/WsfService.java
@@ -0,0 +1,101 @@
+package org.gusdb.wsf.service;
+
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.FormParam;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.StreamingOutput;
+
+import org.apache.log4j.Logger;
+import org.gusdb.wsf.common.ResponseStatus;
+import org.gusdb.wsf.common.WsfRequest;
+import org.gusdb.wsf.plugin.PluginExecutor;
+
+/**
+ * The WSF Web service entry point.
+ *
+ * @author Jerric
+ * @since Nov 2, 2005
+ */
+@Path("/")
+public class WsfService {
+
+ public static final String VERSION = "3.0.0";
+
+ private static final Logger LOG = Logger.getLogger(WsfService.class);
+
+ public WsfService() {
+ // set up the config dir
+ // String gusHome = System.getProperty("GUS_HOME");
+ // if (gusHome != null) {
+ // String configPath = gusHome + "/config/";
+ // STATIC_CONTEXT.put(Plugin.CTX_CONFIG_PATH, configPath);
+ // }
+ LOG.debug("WsfService initialized");
+ }
+
+ @POST
+ @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+ @Produces(MediaType.APPLICATION_OCTET_STREAM)
+ public Response invoke(@FormParam(WsfRequest.PARAM_REQUEST) final String jsonRequest) {
+ long start = System.currentTimeMillis();
+
+ // open a StreamingOutput
+ StreamingOutput output = new StreamingOutput() {
+
+ @Override
+ public void write(OutputStream outStream) throws IOException {
+ // prepare to run the plugin
+ PluginExecutor executor = new PluginExecutor();
+ ResponseStatus status = new ResponseStatus();
+
+ // prepare response
+ ObjectOutputStream objectStream = new ObjectOutputStream(outStream);
+ StreamingPluginResponse pluginResponse = new StreamingPluginResponse(objectStream);
+ int checksum = 0;
+ try {
+ ServiceRequest request = new ServiceRequest(jsonRequest);
+ checksum = request.getChecksum();
+ LOG.debug("Invoking WSF: checksum=" + checksum + "\n" + jsonRequest);
+
+ // invoke plugin
+ int signal = executor.execute(request.getPluginClass(), request, pluginResponse);
+ status.setSignal(signal);
+ }
+ catch (Exception ex) {
+ status.setSignal(-1);
+ status.setException(ex);
+ }
+ finally {
+ // send signal back
+ objectStream.writeObject(status);
+ objectStream.flush();
+ objectStream.close();
+
+ LOG.debug("WSF Service finished: checksum=" + checksum + ", status=" + status + ", #rows=" +
+ pluginResponse.getRowCount() + ", #attch=" + pluginResponse.getAttachmentCount());
+ }
+ }
+ };
+
+ // get the response
+ Response response = Response.ok(output).build();
+ long end = System.currentTimeMillis();
+ LOG.info("WsfService call finished in " + ((end - start) / 1000D) + " seconds");
+ return response;
+ }
+
+ @GET
+ @Produces(MediaType.TEXT_PLAIN)
+ public String getInfo() {
+ return this.getClass().getSimpleName() + " version " + VERSION;
+ }
+}
diff --git a/WSF/src/main/java/org/gusdb/wsf/service/WsfServiceApplication.java b/WSF/src/main/java/org/gusdb/wsf/service/WsfServiceApplication.java
new file mode 100644
index 0000000000..77f566321e
--- /dev/null
+++ b/WSF/src/main/java/org/gusdb/wsf/service/WsfServiceApplication.java
@@ -0,0 +1,17 @@
+package org.gusdb.wsf.service;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.ws.rs.core.Application;
+
+public class WsfServiceApplication extends Application {
+
+ @Override
+ public Set> getClasses() {
+ Set> classes = new HashSet<>();
+ classes.add(WsfService.class);
+ return classes;
+ }
+
+}
diff --git a/build.xml b/build.xml
index cadd2edd55..073994cf33 100755
--- a/build.xml
+++ b/build.xml
@@ -1,18 +1,11 @@
-
-
-
-
-
-
-
+
-
@@ -20,13 +13,6 @@
-
-
-
-
-
-
-
@@ -43,6 +29,13 @@
+
+
+
+
+
+
+
@@ -74,6 +67,7 @@
+
-
+
4.0.0
Web Development Kit
@@ -8,6 +10,7 @@
pom
+ WSF
Model
Service
@@ -21,17 +24,7 @@
org.gusdb
- wsf-common
- ${project.version}
-
-
- org.gusdb
- wsf-plugin
- ${project.version}
-
-
- org.gusdb
- wsf-client
+ wdk-wsf
${project.version}
@@ -39,21 +32,9 @@
org.gusdb
- base-pom
+ gus-project-pom
1.0.0
- ../FgpUtil/Dependencies/org/gusdb/base-pom/1.0.0/base-pom-1.0.0.pom
+ ../install/pom.xml
-
-
- eupathdb
- EuPathDB Project Dependencies
- default
- https://raw.githubusercontent.com/EuPathDB/FgpUtil/master/Dependencies/
-
- false
-
-
-
-
diff --git a/psu.xml b/psu.xml
deleted file mode 100755
index f06a347eee..0000000000
--- a/psu.xml
+++ /dev/null
@@ -1,257 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-