ServiceCall.java [src/java/crp/utils] Revision: default Date:
/*
* $Id$
*
* This file is part of the Cloud Services Integration Platform (CSIP),
* a Model-as-a-Service framework, API, and application suite.
*
* 2012-2017, OMSLab, Colorado State University.
*
* OMSLab licenses this file to you under the MIT license.
* See the LICENSE file in the project root for more information.
*/
package crp.utils;
import csip.ServiceException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.Callable;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
/**
*
* @author <a href="mailto:shaun.case@colostate.edu">Shaun Case</a>
*/
abstract public class ServiceCall extends csip.Client implements Callable<String> {
protected JSONObject request;
protected JSONObject results = null;
protected JSONObject requestMetainfoObject = null;
protected JSONObject resultMetaInfoObject = null;
protected JSONArray callResultSection = null;
protected String metaError = "";
protected String URI;
protected String errorPrefix = "";
protected boolean asyncCall = false;
protected boolean finished = false;
protected String suid = "";
protected String status = "";
private String path;
private String context;
private String pollURI;
private String cancelURI;
protected JSONObject requestJSON = null;
protected CheckedServiceResult<ServiceCallData> serviceCallback = null;
protected ServiceCallData serviceCallData = null;
public ServiceCall(String URI) {
super();
this.URI = URI;
breakoutURI();
}
public ServiceCall(String URI, Object data, CheckedServiceResult<ServiceCallData> serviceCallback) {
super();
this.URI = URI;
this.serviceCallback = serviceCallback;
serviceCallData = new ServiceCallData(data, this);
breakoutURI();
}
public ServiceCall(String URI, CheckedServiceResult<ServiceCallData> serviceCallback) {
super();
this.URI = URI;
this.serviceCallback = serviceCallback;
breakoutURI();
}
public void setCallData(Object data) {
synchronized (this) {
this.serviceCallData = new ServiceCallData(data, this);;
}
}
public Object getCallData() {
return serviceCallData;
}
public String call() throws ServiceException {
execPost();
if (null != serviceCallback) {
serviceCallback.accept(serviceCallData);
}
return status;
}
public JSONObject requestJSON() {
return requestJSON;
}
public void cancel() throws ServiceException {
if ((null == suid) || (suid.isEmpty())) {
throwServiceCallException("Error attempting to cancel asynchronous call status. No SUID is present in this class to check for with remote endpoint.");
}
// TODO Poll service endpoit with suid
if ((null != context) && (!context.isEmpty())) {
try {
results = new JSONObject(super.doGET(cancelURI + suid));
String unknownSUID = results.optString("error");
if (!unknownSUID.isEmpty()) {
if (unknownSUID.contains("suid unknown")) {
throwServiceCallException("The SUID for this service call are no longer available for retrieval. Cannot cancel it, it may be already finished and archived by the time the cancel operation was attempted.");
}
}
} catch (Exception ex) {
throwServiceCallException("Results from cancelling the service call were not JSON. Cannot check on results for suid: " + suid + ", Error: " + ex.getMessage(), ex);
}
} else {
throwServiceCallException("Cannot cancel on this suid, " + suid + ", because there was no context location found in the URI string.");
}
}
protected void parseResults() throws ServiceException {
// Error checking has already been done on these results.
// If we get to this function, no further error
// checking is necessary, except to look at the "results" array in the output, if desired.
if (null == callResultSection) {
throwServiceCallException("Cannot find results in the call to " + this.URI + " .");
}
}
protected void parseAsyncResult() throws ServiceException {
// Error checking has already been done on these results.
// If we get to this function, no further error
// checking is necessary, except to look at the "results" array in the output, if desired.
// No results section is returned on a status of "Running".
// if (null == callResultSection){
// throwServiceCallException("Cannot find results in the call to " + this.URI + " .");
// }
// When checkErrors() is called, the status is saved and finished is set.
// if (finished) {
// parseResults();
// } else {
if (!status.equalsIgnoreCase("running") && !status.equalsIgnoreCase("submitted")) {
throwServiceCallException("Error encountered checking status of remote service call for suid: " + suid + ". Metainfo status value was neither Finished nor Running, and no error message was found.");
}
//}
}
abstract protected void createRequest() throws ServiceException;
public boolean isFinished() {
return finished;
}
/**
* Polls the service endpoint for an asynchronous call. If the service failed,
* this call will throw an exception with the service failure results
* specified.
*
* @return Returns true on finished and false on still pending results.
*/
public boolean asyncDone() throws ServiceException {
if ((null == suid) || (suid.isEmpty())) {
throwServiceCallException("Error attempting to poll asynchronous call status. No SUID is present in this class to check for with remote endpoint.");
}
// TODO Poll service endpoit with suid
if ((null != context) && (!context.isEmpty())) {
try {
results = new JSONObject(super.doGET(pollURI + suid));
String unknownSUID = results.optString("error");
if (!unknownSUID.isEmpty()) {
if (unknownSUID.contains("suid unknown")) {
throwServiceCallException("The results for this service call are no longer available for retrieval. Please increase the TTL value for that service's session data.");
}
}
checkErrors();
if (finished) {
parseResults();
}
} catch (Exception ex) {
throwServiceCallException("Results from polling the service endpoint were not JSON. Cannot check on results for suid: " + suid + ", Error: " + ex.getMessage(), ex);
}
} else {
throwServiceCallException("Cannot check on this suid, " + suid + ", because there was no context location found in the URI string.");
}
return finished;
}
public String getMetaError() {
return metaError;
}
public JSONObject getRequest() {
return request;
}
public JSONObject getResults() {
return results;
}
public JSONObject getReturnMetainfo() {
return resultMetaInfoObject;
}
public JSONArray getResultSection() {
return callResultSection;
}
public void setAsync(boolean value) {
asyncCall = value;
}
public void setRequest(String request) throws JSONException, ServiceException {
if ((null != request) && (!request.isEmpty())) {
this.request = new JSONObject(request);
} else {
throwServiceCallException("Attempt to set request data with empty string.");
}
}
public void setRequest(JSONObject request) throws ServiceException {
if ((null != request) && (request.length() > 0)) {
this.request = request;
} else {
throwServiceCallException("Attempt to set request data with empty JSONObject.");
}
}
public void setURI(String URI) throws ServiceException {
if ((null != URI) && (!URI.isEmpty())) {
this.URI = URI;
breakoutURI();
} else {
throwServiceCallException("Attempt to set URI with empty string.");
}
}
public String getSUID() {
return suid;
}
private void breakoutURI() {
try {
URL tURL = new URL(URI);
path = tURL.getPath();
String[] tokens = path.split("/");
if ((null != tokens) && (tokens.length > 2)) {
context = tokens[1];
pollURI = tURL.getProtocol() + "://" + tURL.getHost() + ":" + tURL.getPort() + "/" + context + "/q/";
cancelURI = tURL.getProtocol() + "://" + tURL.getHost() + ":" + tURL.getPort() + "/" + context + "/cancel/";
} else {
context = "";
}
} catch (MalformedURLException ex) {
Logger.getLogger(ServiceCall.class.getName()).log(Level.SEVERE, "Error trying to get path and context from URI String.", ex);
}
}
private void checkErrors() throws ServiceException {
try {
if ((null == results) || (results.length() <= 0)) {
throwServiceCallException("No data was returned from " + URI + " for this request. ");
}
resultMetaInfoObject = results.getJSONObject("metainfo");
status = resultMetaInfoObject.optString("status");
if ((null != status) && (!status.isEmpty())) {
if (status.equalsIgnoreCase("finished")) {
finished = true;
}
suid = resultMetaInfoObject.optString("suid");
if ((null == suid) || suid.isEmpty()) {
throwServiceCallException("No SUID provided by csip service callled.");
}
} else {
throwServiceCallException("No status value was found in the returned csip service call metainfo section.");
}
metaError = resultMetaInfoObject.optString("error", "");
if ((null != metaError) && (!metaError.isEmpty())) {
JSONArray stackTrace = resultMetaInfoObject.optJSONArray("stacktrace");
String stackString = "No trace available";
if (null != stackTrace) {
stackString = "";
for (int i = 0; i < stackTrace.length(); i++) {
stackString += stackTrace.getString(i) + System.lineSeparator();
}
}
throwServiceCallException(metaError + "StackTrace: " + stackString);
}
callResultSection = this.results.optJSONArray("result");
} catch (JSONException ex) {
throwServiceCallException("Missing metainfo in return data. ", ex);
}
}
protected void throwServiceCallException(String message) throws ServiceException {
throw new ServiceException((errorPrefix.isEmpty() ? "" : (errorPrefix + ": ")) + message);
}
protected void throwServiceCallException(String message, Exception ex) throws ServiceException {
throw new ServiceException((errorPrefix.isEmpty() ? "" : (errorPrefix + ": ")) + message + ex.getMessage(), ex);
}
protected final void execPost() throws ServiceException {
createRequest();
finished = false;
try {
results = super.doPOST(URI, request);
} catch (Exception ex) {
throwServiceCallException("Error making a connection to that location: " + URI + ". " + ex.getMessage(), ex);
}
if (!asyncCall) {
checkErrors();
parseResults();
} else {
checkErrors();
if (!finished) {
parseAsyncResult();
} else {
parseResults();
}
}
}
}