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 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 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -