[MOBY-guts] biomoby commit
Paul Gordon
gordonp at dev.open-bio.org
Fri Mar 30 21:17:27 UTC 2007
gordonp
Fri Mar 30 17:17:27 EDT 2007
Update of /home/repository/moby/moby-live/Java/src/main/org/biomoby/client
In directory dev.open-bio.org:/tmp/cvs-serv17813/src/main/org/biomoby/client
Modified Files:
MobyRequest.java
Added Files:
AsyncClient.java
Log Message:
Initial commit of asynchronous client code, somewhat tested (single jobs)
moby-live/Java/src/main/org/biomoby/client AsyncClient.java,NONE,1.1 MobyRequest.java,1.22,1.23
===================================================================
RCS file: /home/repository/moby/moby-live/Java/src/main/org/biomoby/client/MobyRequest.java,v
retrieving revision 1.22
retrieving revision 1.23
diff -u -r1.22 -r1.23
--- /home/repository/moby/moby-live/Java/src/main/org/biomoby/client/MobyRequest.java 2007/02/08 16:59:58 1.22
+++ /home/repository/moby/moby-live/Java/src/main/org/biomoby/client/MobyRequest.java 2007/03/30 21:17:27 1.23
@@ -1,9 +1,6 @@
package org.biomoby.client;
-// Defines Web service input, output, access
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.PrintStream;
+import java.io.*;
import java.util.*;
import javax.xml.namespace.QName;
@@ -21,9 +18,13 @@
import org.apache.xpath.XPathContext;
import org.apache.xpath.objects.XNodeSet;
import org.apache.xpath.objects.XObject;
+
import org.biomoby.shared.*;
import org.biomoby.shared.data.*;
import org.biomoby.shared.parser.MobyTags; // defined the Moby XML element names
+import org.biomoby.w3c.addressing.EndpointReference;
+import org.omg.lsae.notifications.AnalysisEvent;
+
import org.w3c.dom.*;
/**
@@ -150,14 +151,14 @@
}
/**
- * @param service the MobyService that should be executed when invokeService is called
+ * @param mobyservice the MobyService that should be executed when invokeService is called
*/
- public void setService(MobyService service){
- if(service == null){
+ public void setService(MobyService mobyservice){
+ if(mobyservice == null){
mobyService = null;
}
- else if(mobyService == null || !service.equals(mobyService)){
- mobyService = service;
+ else if(mobyService == null || !mobyservice.equals(mobyService)){
+ mobyService = mobyservice;
}
}
@@ -263,19 +264,94 @@
// Used internally for asynchronous thread calls that all need the XML data
// and can't rely on the answer from thread-insensitive getResponseXML()
- private MobyContentInstance invokeService(StringBuffer contentsXML) throws Exception, MobyException, SOAPException, NoSuccessException{
+ private MobyContentInstance invokeService(StringBuffer contentsXML)
+ throws Exception, MobyException, SOAPException, NoSuccessException{
+ return invokeService(contentsXML, null, 0);
+ }
+
+ private MobyContentInstance invokeService(StringBuffer contentsXML, MobyRequestEventHandler handler, int requestId)
+ throws Exception, MobyException, SOAPException, NoSuccessException{
if(mobyService == null){
throw new MobyException("Tried to invoke null service from MobyRequest (call setService first)");
}
- Call call = getServiceFromWSDL();
- verifyInput();
- String mobyXML = convertMOBYDataToMOBYRequest(inputData);
- Element mobyDOM = performSOAPRequest(call, mobyXML, contentsXML);
- // The following parses the DOM and extracts all the appropriate jMOBY objects to represent the XML in Java
- outputData = MobyDataUtils.fromXMLDocument(mobyDOM);
- return outputData;
+ Element mobyDOM = null;
+ if(mobyService.isAsynchronous()){
+ // Async is "simpler", because it had to merge DOMs together into a single MobyContentInstance anyway
+ return performAsyncSOAPRequest(mobyService, inputData, handler, requestId);
+ }
+ else{
+ String mobyXML = convertMOBYDataToMOBYRequest(inputData);
+ Call call = getServiceFromWSDL();
+ mobyDOM = performSOAPRequest(call, mobyXML, contentsXML);
+ // The following parses the DOM and extracts all the appropriate jMOBY objects to represent the XML in Java
+ return MobyDataUtils.fromXMLDocument(mobyDOM);
+ }
+ }
+
+ protected MobyContentInstance performAsyncSOAPRequest(MobyService mservice, MobyContentInstance inData,
+ MobyRequestEventHandler handler, int requestId)
+ throws Exception{
+ String mobyXML = convertMOBYDataToMOBYRequest(inData);
+ EndpointReference epr = AsyncClient.sendRequest(mservice, mobyXML);
+
+ // Essentially cloning, so removing ids doesn't change the
+ // MobyContentInstance "data" (which we will use again later on)
+ MobyContentInstance finalContents = new MobyContentInstance();
+ Set<String> queryIDs = new HashSet(inData.keySet());
+ try {
+ // Should add some timeout here...
+ while(!queryIDs.isEmpty()){
+ // todo: make this setable
+ Thread.sleep(5000);
+
+ AnalysisEvent[] events =
+ AsyncClient.poll(epr, queryIDs);
+
+ Vector<String> newDataAvailable = new Vector<String>();
+ for(AnalysisEvent event: events){
+ if(event != null && event.isCompleted()){
+ queryIDs.remove(event.getQueryId());
+ newDataAvailable.add(event.getQueryId());
+ }
+ }
+
+ // Parse and merge the new data into the existing contents
+ InputStream resultStream = AsyncClient.getResultStream(epr, newDataAvailable);
+ Element mobyDOM = asyncSoapTextToMobyDOM(resultStream);
+ MobyContentInstance newResults = MobyDataUtils.fromXMLDocument(mobyDOM);
+ // The merge
+ for(String jobid: newResults.keySet()){
+ finalContents.put(jobid, newResults.get(jobid));
+ }
+
+ // Inform the handler that some data has been added to the response (for incremental display?)
+ if(newDataAvailable.size() > 0 && handler != null){
+ MobyRequestEvent mre = new MobyRequestEvent(finalContents, this, null, requestId);
+ StringWriter xmlWriter = new StringWriter();
+ MobyDataUtils.toXMLDocument(xmlWriter, finalContents);
+
+ mre.setContentsXML(xmlWriter.toString());
+ handler.processEvent(mre);
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ AsyncClient.destroy(epr);
+ throw new Exception("Exception occured while polling the service invocation: " + e);
+ }
+
+ return finalContents;
+ }
+
+ private Element asyncSoapTextToMobyDOM(InputStream inStream) throws Exception{
+ Element soapDOM = null;
+ synchronized(docBuilder){
+ soapDOM = docBuilder.parse(inStream).getDocumentElement();
+ }
+ final boolean IS_ASYNC_SERVICE_CALL = true;
+ return decodeSOAPMessage(soapDOM, null, null, IS_ASYNC_SERVICE_CALL);
}
/**
@@ -296,19 +372,19 @@
// This is the class that asynchronously calls the service and does a callback to
// the handler specified in the invocation.
class InvocationThread extends Thread {
- MobyService service;
+ MobyService mservice;
MobyRequest mobyRequest;
MobyRequestEventHandler handler;
int requestId;
InvocationThread(MobyRequest mr, MobyRequestEventHandler h, int id){
mobyRequest = mr;
- service = mobyRequest.getService();
+ mservice = mobyRequest.getService();
handler = h;
requestId = id;
// Name the thread after the service being run, mostly for ease of debugging
- setName(service.getName()+requestId);
+ setName(mservice.getName()+requestId);
}
public void run() {
@@ -317,7 +393,7 @@
handler.start(mobyRequest, requestId);
StringBuffer contentsXML = new StringBuffer(); //to be filled in by the RPC call below
try{
- content = mobyRequest.invokeService(contentsXML); //RPC call...
+ content = mobyRequest.invokeService(contentsXML, handler, requestId); //RPC call...
}
catch(Exception e){
responseEvent = new MobyRequestEvent(content, mobyRequest, e, requestId);
@@ -348,33 +424,6 @@
}
/**
- * @throws MobyException if the number of input parameters was wrong, or the paramters were of the wrong type.
- */
- protected void verifyInput() throws MobyException{
- /*MobyData[] requiredInputData = mobyService.getPrimaryInputs();
-
- if(requiredInputData == null){
- if(inputData != null && inputData.size() != 0){
- throw new MobyException("Service invocation expects no input parameters, " +
- "but setInput has not been called with a null or zero-length array argument");
- }
- return; // otherwise there are no parameters to check
- }
- if(inputData == null || inputData.size() == 0){
- throw new MobyException("Service invocation expects " + requiredInputData.length + " input " +
- "parameters, but setInput has not been called to provide any input");
- }
- if (requiredInputData.length != inputData.length){
- throw new MobyException("Wrong number of input parameters to service invocation method: expected " +
- requiredInputData.length + ", but setInput was provided " + inputData.length);
- }*/
-
- // BUG WORKAROUND: Since the Moby-generated WSDL doesn't define the input, we assume the
- // data types are right, and we just have to check the number of them.
-
- }
-
- /**
* This method retrieves from Moby Central a copy of the WSDL document for the service
* (or uses an internally cached copy from a previous invocation), and sets the variables for the
* SOAP call appropriately so you can consequently call performSOAPRequest.
@@ -469,6 +518,11 @@
}
+ public Element decodeSOAPMessage(Element n, StringBuffer contentsXMLOutput, String inputXML)
+ throws SOAPException, MobyException{
+ return decodeSOAPMessage(n, contentsXMLOutput, inputXML, false);
+ }
+
/**
* Isolates the MOBY Data from the SOAP message returned by the remote service host.
*
@@ -477,22 +531,29 @@
*
* @return The root element of the MOBY response DOM
*/
- public Element decodeSOAPMessage(Element n, StringBuffer contentsXMLOutput, String inputXML) throws SOAPException, MobyException{
+ public Element decodeSOAPMessage(Element n, StringBuffer contentsXMLOutput, String inputXML, boolean async)
+ throws SOAPException, MobyException{
if(n == null){
throw new SOAPException("SOAP Message given to decode is null");
}
NodeList node_list = null;
- Node responseNode = null;
XPath responseElementXPath = null;
try{
- responseElementXPath = new XPath("//"+ MobyPrefixResolver.MOBY_TRANSPORT_PREFIX+
- ":"+mobyService.getName()+"Response | //" +
- mobyService.getName()+"Response | " +
- "//"+ MobyPrefixResolver.MOBY_TRANSPORT_PREFIX+
- ":"+mobyService.getName() + " | //" +
- mobyService.getName(),
- null, mobyPrefixResolver, XPath.SELECT);
+ if(async){
+ responseElementXPath = new XPath("//"+MobyPrefixResolver.WSRP_PREFIX +
+ ":"+AsyncClient.WSRP_MULTI_PROPERTY_TAG_NAME+"Response",
+ null, mobyPrefixResolver, XPath.SELECT);
+ }
+ else{
+ responseElementXPath = new XPath("//"+ MobyPrefixResolver.MOBY_TRANSPORT_PREFIX+
+ ":"+mobyService.getName()+"Response | //" +
+ mobyService.getName()+"Response | " +
+ "//"+ MobyPrefixResolver.MOBY_TRANSPORT_PREFIX+
+ ":"+mobyService.getName() + " | //" +
+ mobyService.getName(),
+ null, mobyPrefixResolver, XPath.SELECT);
+ }
}catch(TransformerException te){
throw new SOAPException("Cannot select SOAP nodes due to exception "+
"while compiling XPath statement (code bug?):" +te);
@@ -513,8 +574,29 @@
throw new SOAPException("Found more than one response element in SOAP payload, " +
"unable to resolve ambiguity of the payload (service provider error?)");
}
- responseNode = node_list.item(0);
+ Node[] responseNodes = null;
+ if(async){
+ Vector<Node> nodes = new Vector<Node>();
+ NodeList resultNodeList = node_list.item(0).getChildNodes();
+ for(int i = 0; resultNodeList != null && i < resultNodeList.getLength(); i++){
+ if(!(resultNodeList.item(i) instanceof Element)){
+ continue;
+ }
+ Element resultElement = (Element) resultNodeList.item(i);
+ if(resultElement.getLocalName().startsWith(AsyncClient.MOBY_RESULT_PROPERTY_PREFIX)){
+ nodes.add(resultElement);
+ }
+ }
+ responseNodes = nodes.toArray(new Node[nodes.size()]);
+ }
+ else{
+ responseNodes = new Node[]{node_list.item(0)};
+ }
+
+ Element domRoot = null; // Where the result will be put
+
+ for(Node responseNode: responseNodes){
// Find base64 encoded elements in the SOAP message using XPath and
// replace them with the real decoded contents
node_list = null;
@@ -597,7 +679,8 @@
}
}
- String localResponseString = "";
+ Element predefinedDOM = null; // Choice of ripping DOM Element for moby payload out of SOAP DOM
+ String localResponseString = ""; // or storing raw XML strings, to be converted to a DOM later
for(int j = 0; j < children.getLength(); j++){
Node child = children.item(j);
if(child instanceof CDATASection || child instanceof Text){
@@ -612,16 +695,27 @@
}
localResponseString += child.getNodeValue();//.replaceAll("<", "<").replaceAll(">", ">").replaceAll("(&|F)", "&");
}
+ if(child instanceof Element && child.getLocalName().equals(MobyTags.MOBY)){
+ debugPS.println("Warning: The MOBY contents was found as raw XML inside the SOAP response!\n" +
+ "This is illegal according to the MOBY-API, please inform the service\n " +
+ " provider, as parsing such text may not be supported in the future");
+ localResponseString = null;
+ // Store the moby payload root element's DOM represntation, so we don't
+ // have to serialize it to the localResponseString and then parse it out
+ // again (that would be wasteful).
+ predefinedDOM = (Element) child;
+ break;
+ }
}
- if(localResponseString.length() == 0){
+ if(localResponseString != null && localResponseString.length() == 0){
throw new MobyException("The MOBY payload has no text contents at all");
}
// Check if the payload is an XML document. If not, try a last ditch effort
// by base64 decoding the contents. This is technically not allowable in the
// MOBY spec, but we are being lenient.
- if(!localResponseString.startsWith("<?xml")){
+ if(localResponseString != null && !localResponseString.startsWith("<?xml")){
// Is the XML declaration missing?
if(localResponseString.startsWith("<moby:MOBY") || localResponseString.startsWith("<MOBY")){
localResponseString = "<?xml version=\"1.0\"?>\n"+localResponseString;
@@ -652,15 +746,58 @@
// later on because once the document is parsed, there is no way to get the
// spaces back! I set the attribute explicitly at the top level of each data
// element to compensate. Unless of course this was already done.
- if(localResponseString.indexOf("xml:space=\"preserve\"") == -1){
+ if(localResponseString != null && localResponseString.indexOf("xml:space=\"preserve\"") == -1){
localResponseString = localResponseString.replaceAll("<String", "<String xml:space=\"preserve\"");
}
- Document domDoc = null;
try{
- // Synchronized to avoid Xerces exception FWK005: concurrent parsing is disallowed
- synchronized(docBuilder){
- domDoc = docBuilder.parse(new ByteArrayInputStream(localResponseString.getBytes()));
+ if(async && domRoot != null){
+ // We will actually be appending several full MOBY messages together, so
+ // we need to do a DOM-meld at the mobyData tag level.
+ Element newContentsTag = null;
+ if(predefinedDOM != null){ // we have the MOBY element as a DOM Element already from the SOAP DOM
+ newContentsTag = MobyPrefixResolver.getChildElement(predefinedDOM, MobyTags.MOBYCONTENT);
+ }
+ else{
+ synchronized(docBuilder){
+ Document newDoc = docBuilder.parse(new ByteArrayInputStream(localResponseString.getBytes()));
+ newContentsTag = MobyPrefixResolver.getChildElement(newDoc.getDocumentElement(), MobyTags.MOBYCONTENT);
+ }
+ }
+ // Find the mobyContents tag under the root MOBY tag...
+ Element existingContentsTag = MobyPrefixResolver.getChildElement(domRoot, MobyTags.MOBYCONTENT);
+ NodeList newJobTags = newContentsTag.getChildNodes();
+ for(int i = 0; newJobTags != null && i < newJobTags.getLength(); i++){
+ // Service notes blocks must be merged
+ if(newJobTags.item(i) instanceof Element &&
+ newJobTags.item(i).getLocalName().equals(MobyTags.SERVICENOTES)){
+ Element existingServiceNotes =
+ MobyPrefixResolver.getChildElement(existingContentsTag, MobyTags.SERVICENOTES);
+ if(existingServiceNotes == null){
+ existingContentsTag.appendChild(newJobTags.item(i));
+ }
+ else{
+ NodeList newServiceData = newJobTags.item(i).getChildNodes();
+ for(int j = 0; newServiceData != null && j < newServiceData.getLength(); j++){
+ existingServiceNotes.appendChild(newServiceData.item(j));
+ }
+ }
+ }
+ else{ //everything else is at the same level (i.e. mobyData blocks)
+ existingContentsTag.appendChild(newJobTags.item(i));
+ }
+ }
+ }
+ else{ //synchronous service call, or first async call (which needs to create a doc anyway)
+ // Synchronized to avoid Xerces exception FWK005: concurrent parsing is disallowed
+ if(predefinedDOM != null){ // we have the MOBY element as a DOM Element already from the SOAP DOM
+ domRoot = predefinedDOM;
+ }
+ else{
+ synchronized(docBuilder){
+ domRoot = docBuilder.parse(new ByteArrayInputStream(localResponseString.getBytes())).getDocumentElement();
+ }
+ }
}
} catch(org.xml.sax.SAXException saxe){
throw new MobyException("The SOAP payload defining the MOBY Result " +
@@ -678,11 +815,12 @@
else{
responseString = localResponseString;
}
+ } // end for responseNode in responseNodes
// release resources related to the Xpath execution, since we won't be using this doc anymore
releaseXPath(n);
- return domDoc.getDocumentElement();
+ return domRoot;
}
public String convertMOBYDataToMOBYRequest(MobyDataInstance data) throws MobyException{
@@ -690,6 +828,8 @@
}
/**
+ * Creates an XML representation of the data, renamed to fit the needs of the service if necessary.
+ *
* @param data the array of input parameters to put in a MOBY XML request
*
* @return the XML representation of the input data
@@ -718,7 +858,7 @@
Object param = query.get(name);
if(param == null){
throw new MobyException("Query " + queryName +
- "contained a null input parameter (" + name + ")");
+ " contained a null input parameter (" + name + ")");
}
else if(param instanceof MobyPrimaryData){
primaryParams.put(name, param);
@@ -767,7 +907,7 @@
ByteArrayOutputStream mobyRequest = new ByteArrayOutputStream();
try{
- MobyDataUtils.toXMLDocument(mobyRequest, inputData);
+ MobyDataUtils.toXMLDocument(mobyRequest, data);
}
catch(MobyException me){
throw me;
More information about the MOBY-guts
mailing list