[MOBY-guts] biomoby commit
Andreas Groscurth
groscurt at dev.open-bio.org
Wed Dec 3 15:21:13 UTC 2008
groscurt
Wed Dec 3 10:21:13 EST 2008
Update of /home/repository/moby/moby-live/Java/src/main/org/biomoby/client
In directory dev.open-bio.org:/tmp/cvs-serv13676/src/main/org/biomoby/client
Modified Files:
MobyRequest.java BaseClient.java MobyServiceLocator.java
Log Message:
changes made for the authentication... without formatting this time
moby-live/Java/src/main/org/biomoby/client MobyRequest.java,1.41,1.42 BaseClient.java,1.13,1.14 MobyServiceLocator.java,1.4,1.5
===================================================================
RCS file: /home/repository/moby/moby-live/Java/src/main/org/biomoby/client/MobyRequest.java,v
retrieving revision 1.41
retrieving revision 1.42
diff -u -r1.41 -r1.42
--- /home/repository/moby/moby-live/Java/src/main/org/biomoby/client/MobyRequest.java 2008/11/26 08:53:43 1.41
+++ /home/repository/moby/moby-live/Java/src/main/org/biomoby/client/MobyRequest.java 2008/12/03 15:21:13 1.42
@@ -1,1028 +1,1030 @@
-
-package org.biomoby.client;
-
-import java.io.*;
-import java.util.*;
-
-import javax.xml.namespace.QName;
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.rpc.Service;
-import javax.xml.transform.TransformerException;
-
-import org.apache.axis.client.Call;
-import org.apache.axis.message.MessageElement;
-import org.apache.xml.utils.PrefixResolver;
-import org.apache.xml.utils.PrefixResolverDefault;
-import org.apache.xpath.XPath;
-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.*;
-
-/**
- * This class handles the WSDL transaction to request a response from a remote SOAP Web service that handles the <a
- * href="http://www.biomoby.org">MOBY</a> format. It depends on having already retrieved the definition of the Web
- * service via the MOBY central registry using the <a href="http://www.biomoby.org/moby-live/Java/docs/index.html">jMOBY</a>
- * API, and for now it uses the <a href="http://ws.apache.org/axis/index.html">Apache Axis</a> Web services framework,
- * as well as Apache Xalan. There are code comments for the few lines that rely on Axis classes rather than the JAX-RPC
- * interfaces.
- *
- * @author Paul Gordon gordonp at ucalgary.ca
- */
-public class MobyRequest {
-
- protected MobyService mobyService = null;
- protected MobyContentInstance inputData = null;
- protected MobyContentInstance outputData = null;
- protected Central mobyCentral = null;
- protected PrefixResolver mobyPrefixResolver = null;
-
- protected Hashtable wsdlCache = null;
- protected String lastWsdlCacheKey = null;
- protected DocumentBuilder docBuilder = null;
- protected Service service = null;
-
- protected Class stringType;
- protected static boolean debug = false;
- protected PrintStream debugPS = System.err;
- protected XPathContext xpath_context;
- protected String responseString = null;
-
- private XPath stringEncodedXPath;
- private XPath base64EncodedXPath;
- private XPath simpleChildXPath;
- private XPath collectionChildXPath;
-
- private int autoID = 0;
-
- // Used as invocation callback if MobyRequest is acting as a server,
- // or is executing services as a client asynchronously
- private Vector< MobyRequestEventHandler > eventHandlers;
-
- private String user;
- private String password;
-
- /**
- * Default constructor. You should have a Central instance around since you're going to be retrieving MobyServices
- * to pass into here. Lets reuse it.
- *
- * @param central An instance of a Moby central object so we can make requests about object types, etc.
- * @throws ParserConfigurationException if JAXP doesn't have any valid DOM-building XML parsers set up for use
- */
- public MobyRequest( Central central ) throws ParserConfigurationException {
- mobyCentral = central;
- wsdlCache = new Hashtable();
-
- eventHandlers = new Vector< MobyRequestEventHandler >();
-
- DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
- dbf.setNamespaceAware( true );
- docBuilder = dbf.newDocumentBuilder();
-
- try {
- stringType = Class.forName( "java.lang.String" );
- }
- catch ( ClassNotFoundException classe ) {
- debugPS.println( "WARNING: Something is very wrong, could not find Class definition of String: " + classe );
- }
-
- xpath_context = new XPathContext();
- mobyPrefixResolver = new MobyPrefixResolver();
-
- // Now compile the XPath statements that will be used fetch data from the server response
- try {
- base64EncodedXPath = new XPath( "//*[starts-with(substring-after(@" + MobyPrefixResolver.XSI1999_PREFIX
- + ":type, ':'), \"base64\") or starts-with(substring-after(@" + MobyPrefixResolver.XSI2001_PREFIX
- + ":type, ':'), \"base64\")]", null, mobyPrefixResolver, XPath.SELECT );
- stringEncodedXPath = new XPath( "//*[substring-after(@" + MobyPrefixResolver.XSI1999_PREFIX
- + ":type, ':')=\"string\" or substring-after(@" + MobyPrefixResolver.XSI2001_PREFIX
- + ":type, ':')=\"string\"] | //" + MobyPrefixResolver.SOAP_ENC_PREFIX + ":string", null,
- mobyPrefixResolver, XPath.SELECT );
- simpleChildXPath = new XPath( "moby:Simple | Simple", null, mobyPrefixResolver, XPath.SELECT );
- collectionChildXPath = new XPath( "moby:Collection | Collection", null, mobyPrefixResolver, XPath.SELECT );
- }
- catch ( TransformerException te ) {
- debugPS.println( "Syntax error encountered while compiling XPath "
- + "statements for internal use (code bug?): " + te );
- }
- setDebugMode( System.getProperty( "moby.debug" ) != null );
- }
-
- /**
- * @param mode if true, debugging information is printed to the stream returned by getDebugOutputStream
- */
- public void setDebugMode(boolean mode) {
- debug = mode;
- }
-
- /**
- * Standard error is used unless this method is called.
- *
- * @param ps the OutputStream to which debugging information is sent.
- * @throws IllegalArgumentException if the stream is null
- */
- public void setDebugPrintStream(PrintStream ps) throws IllegalArgumentException {
- if ( ps == null ) {
- throw new IllegalArgumentException( "The OutputStream specified to MobyRequest was null" );
- }
- debugPS = ps;
- }
-
- /**
- * @param user the user for a possible service authentication
- * @param password the passoword for a possible service authentication
- */
- public void setAuthentication(String user, String password) {
- this.user = user;
- this.password = password;
- }
-
- /**
- * @return the instance of the class implementing Central that we are using
- */
- public Central getCentralImpl() {
- return mobyCentral;
- }
-
- /**
- * @param mobyservice the MobyService that should be executed when invokeService is called
- */
- public void setService(MobyService mobyservice) {
- if ( mobyservice == null ) {
- mobyService = null;
- }
- else if ( mobyService == null || !mobyservice.equals( mobyService ) ) {
- mobyService = mobyservice;
- }
- }
-
- /**
- * @return the MobyService that will be executed when invokeService is called
- */
- public MobyService getService() {
- return mobyService;
- }
-
- /**
- * @return the Raw MOBY XML response as a string
- */
- public String getResponseXML() {
- return responseString;
- }
-
- /**
- * Sets the input data for the MOBY service request. The length of the input array, and the number of input
- * parameters required by the service must be equal when invokeService() is called. This method strictly enforces
- * that the input be of the appropriate type for the service.
- *
- * Note that there is no requirement to use MobyDataInstance.setXmlMode() before passing in data, this class will
- * temporarily set the XML mode of the data when it is required.
- *
- * @throws IllegalArgumentException if the input does not fit the criteria of the service (e.g. wrong data type)
- */
- public void setInput(MobyContentInstance data) throws MobyException {
- inputData = data;
- }
-
- /**
- * Takes the data in the array, with their current articleNames, as input for the service
- */
- public void setInput(MobyDataInstance[] data) throws MobyException {
- MobyDataJob job = new MobyDataJob();
- for ( MobyDataInstance param : data ) {
- job.put( param.getName(), param );
- }
- inputData = new MobyContentInstance();
- inputData.put( job );
- }
-
- /**
- * Convenience method to run services that take one argument. If the service requires the input to have a name, it
- * will be automatically assigned.
- */
- public void setInput(MobyDataInstance datum) throws MobyException {
- setInput( datum, "" );
- }
-
- /**
- * Convenience method to run services that take one named argument.
- */
- public void setInput(MobyDataInstance datum, String paramName) throws MobyException {
- inputData = new MobyContentInstance( datum, paramName );
- }
-
- /**
- * @return the MobyService that will be executed when invokeService is called
- */
- public MobyContentInstance getInput() {
- return inputData;
- }
-
- /**
- * Same functionality as setSecondaryInput(MobyDataSecondaryInstance[])
- */
- public void setSecondaryInput(Collection< MobyDataSecondaryInstance > secondaryData) throws MobyException {
- setSecondaryInput( secondaryData.toArray( new MobyDataSecondaryInstance[ secondaryData.size() ] ) );
- }
-
- /**
- * This method will assign the provided secondary parameters to all primary input data currently in this object.
- * This is covenient if you are running 100 seqs through BLAST and only want to set the parameters once. If you
- * instead want to set secondary input differently for all primary inputs, you'll need to create a custom
- * MobyContentInstance as input to setInput().
- *
- * @throws MobyException if a parameter name is blank, or overrides a primary parameter
- */
- public void setSecondaryInput(MobyDataSecondaryInstance[] secondaryData) throws MobyException {
-
- Iterator queryNames = inputData.keySet().iterator();
- // For each query
- while (queryNames.hasNext()) {
- MobyDataJob queryParams = inputData.get( queryNames.next() );
- // Set all the secondary params (overwrites any old ones)
- for ( int i = 0; i < secondaryData.length; i++ ) {
- String secName = secondaryData[ i ].getName();
- if ( secName == null || secName.length() == 0 ) {
- throw new MobyException( "A secondary parameter cannot have a blank name (array index " + i + ")" );
- }
- if ( queryParams.containsKey( secName ) && queryParams.get( secName ) instanceof MobyPrimaryData ) {
- throw new MobyException( "A secondary parameter cannot override an existing primary parameter "
- + "with the same name (" + secName + ")" );
- }
- queryParams.put( secName, secondaryData[ i ] );
- }
- }
- }
-
- /**
- * @return a vector of MobyDataInstance[], each element of the vector is the collection of response objects for the
- * correspondingly indexed input request.
- *
- * @throws MobyException if you try to get the results before calling InvokeService
- */
- public MobyContentInstance getOutput() throws MobyException {
- if ( outputData == null ) {
- throw new MobyException( "Trying to access MOBY service results " + "before the service is invoked" );
- }
- else {
- return outputData;
- }
- }
-
- /**
- * The main method of the class. If all of the MOBY input objects are properly defined according to the Web service
- * definition, a SOAP request will be sent to the remote server, and the method will return one or more MOBY objects
- * (synchronous). Call this method after calling setService, and setInput. If you do not call setSecondaryInput, the
- * default secondary parameter values will be used.
- *
- * @return the results of the remote Web service in response to the give input
- *
- * @throws MobyException i.e. there was something wrong with the input, output or remote service's logic
- * @throws SOAPException i.e. there was a problem with the underlying transaction/transport layer
- */
- public MobyContentInstance invokeService() throws Exception, MobyException, SOAPException, NoSuccessException {
- return mobyService.isAsynchronous()
- ? invokeService( inputData, new StringBuffer() )
- : invokeService( inputData, ( StringBuffer ) null );
- }
-
- // 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(MobyContentInstance inData, StringBuffer contentsXML) throws Exception,
- MobyException, SOAPException, NoSuccessException {
- return invokeService( inData, contentsXML, null, 0 );
- }
-
- private MobyContentInstance invokeService(MobyContentInstance inData, 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)" );
- }
-
- Element mobyDOM = null;
- if ( mobyService.isAsynchronous() ) {
- // Async is "simpler", because it had to merge DOMs together into a single MobyContentInstance anyway
- MobyContentInstance mci = performAsyncSOAPRequest( mobyService, inData, handler, requestId );
- StringWriter writer = new StringWriter();
- MobyDataUtils.toXMLDocument( writer, mci );
- contentsXML.append( writer.toString() );
- return mci;
- }
- else {
- String mobyXML = convertMOBYDataToMOBYRequest( inData );
- Call call = getServiceFromWSDL();
- if ( user != null && password != null ) {
- call.setProperty( Call.USERNAME_PROPERTY, user );
- call.setProperty( Call.PASSWORD_PROPERTY, password );
- }
- 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, mobyService.getServiceType().getRegistry() );
- }
- }
-
- 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< String >( 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() );
- }
- }
-
- if ( newDataAvailable.size() > 0 ) {
- // 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,
- mservice.getServiceType()
- .getRegistry() );
- // 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 ( handler != null ) {
- MobyRequestEvent mre = new MobyRequestEvent( finalContents, this, mservice, null, requestId );
- StringWriter xmlWriter = new StringWriter();
- MobyDataUtils.toXMLDocument( xmlWriter, finalContents );
-
- mre.setContentsXML( xmlWriter.toString() );
- if ( !queryIDs.isEmpty() ) {
- // Send an update event only if we aren't finished yet.
- // If we are finished, the client is going to get this event as the
- // invocation thread finishes up (no need to double up).
- 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 );
- }
-
- /**
- * Asynchronous call to invokeService. A callback to the passed-in handler will be made when the response is ready,
- * or there is an exception.
- *
- * @return the id that the callback event will return from getID(), allowing a client to distinguish between
- * multiple concurrent invocation callbacks
- */
- public synchronized int invokeService(MobyRequestEventHandler handler) {
- int id = autoID++;
-
- Thread t = new InvocationThread( this, inputData, handler, id ); // see internal class definition below
- t.start();
-
- return id;
- }
-
- // This is the class that asynchronously calls the service and does a callback to
- // the handler specified in the invocation.
- class InvocationThread extends Thread {
- MobyContentInstance data;
- MobyService mservice;
- MobyRequest mobyRequest;
- MobyRequestEventHandler handler;
- int requestId;
-
- InvocationThread( MobyRequest mr, MobyContentInstance inData, MobyRequestEventHandler h, int id ) {
- data = inData;
- mobyRequest = mr;
- mservice = mobyRequest.getService();
- handler = h;
- requestId = id;
-
- // Name the thread after the service being run, mostly for ease of debugging
- setName( mservice.getName() + requestId );
- }
-
- public void run() {
- MobyRequestEvent requestEvent = new MobyRequestEvent( data, mobyRequest, mservice, null, requestId );
- // Tell the handler we're starting the request, with the given data
- handler.start( requestEvent );
-
- MobyRequestEvent responseEvent = null;
- MobyContentInstance content = null;
- StringBuffer contentsXML = new StringBuffer(); // to be filled in by the RPC call below
- try {
- content = mobyRequest.invokeService( data, contentsXML, handler, requestId ); // RPC call...
- }
- catch ( Exception e ) {
- responseEvent = new MobyRequestEvent( content, mobyRequest, mservice, e, requestId );
- }
- catch ( Error err ) {
- responseEvent = new MobyRequestEvent( content, mobyRequest, mservice, err, requestId );
- }
- if ( responseEvent == null ) {
- responseEvent = new MobyRequestEvent( content, mobyRequest, mservice, null, requestId );
- }
- // We've got the raw XML laying around, so why not provide it unmolested to the callback?
- responseEvent.setContentsXML( contentsXML.toString() );
- handler.processEvent( responseEvent );
- handler.stop( mobyRequest, requestId );
- }
- }
-
- public void addEventHandler(MobyRequestEventHandler h) {
- eventHandlers.add( h );
- }
-
- public void removeEventHandler(MobyRequestEventHandler h) {
- eventHandlers.remove( h );
- }
-
- public void sendResponse(MobyRequestEvent mre) {
- // Not yet implemented, need to conform to some web.xml specification here...
- }
-
- /**
- * 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.
- */
- protected Call getServiceFromWSDL() throws MobyException, NoSuccessException {
- // String wsdl = null;
-
- // // Since this is how we retrieve a service from Central, use the same values as the key to the hash
- // String wsdlCacheKey = mobyService.getName() + "@" + mobyService.getAuthority();
-
- // // This is the same call as last time, so we don't need to change the setup
- // if(wsdlCacheKey.equals(lastWsdlCacheKey)){
- // return setCallFromWSDL((String) wsdlCache.get(wsdlCacheKey));
- // }
- // // We haven't encountered this service yet
- // else if(!wsdlCache.containsKey(wsdlCacheKey)){
- // wsdl = mobyCentral.getServiceWSDL(mobyService.getName(), mobyService.getAuthority());
- // wsdlCache.put(wsdlCacheKey, wsdl);
- // }
- // // We've dealt with this one before
- // else{
- // wsdl = (String) wsdlCache.get(wsdlCacheKey);
- // }
-
- // lastWsdlCacheKey = wsdlCacheKey; // Keep track of the last invocation
-
- // Get ready to do SOAP
- return setCallFromWSDL( null );
- }
-
- /**
- * Creates the SOAP Call that will be invoked later. This should be based on the WSDL document and parameter
- * information from the MobyService, but these are currently not up to snuff.
- */
- protected Call setCallFromWSDL(String wsdl) throws MobyException, SOAPException {
- if ( service == null ) {
- service = new org.apache.axis.client.Service(); // AXIS SPECIFIC This acts as a factory for Calls
- }
-
- Call soapCall;
- try {
- soapCall = ( Call ) service.createCall();// create a fresh Call each time
- }
- catch ( javax.xml.rpc.ServiceException se ) {
- throw new SOAPException( "Could not instatiate call to SOAP Service: " + se );
- }
-
- // Should initialize endpoint, etc. This call is AXIS SPECIFIC, otherwise you'll
- // have to do the call's info setting manually.
- // ((org.apache.axis.client.Call) soapCall).setSOAPService(soapService);
- soapCall.removeAllParameters();
- soapCall.setTargetEndpointAddress( mobyService.getURL() );
- soapCall.setPortName( new QName( "http://biomoby.org/", mobyService.getName() + "PortType" ) );
- // soapCall.setOperationName(new QName("http://biomoby.org/",
- // mobyService.getName()));
- soapCall.setSOAPActionURI( "http://biomoby.org/#" + mobyService.getName() );
- return soapCall;
- }
-
- /**
- * Calls the invoke() method of the JAX-RPC Call interface.
- */
- protected Element performSOAPRequest(Call soapCall, String mobyInputXML, StringBuffer contentsXMLOutput)
- throws SOAPException {
- // First, turn the input objects into a MOBY XML request
- String[] mobyXMLInputData = new String[ 1 ];
-
- // Setup
- mobyXMLInputData[ 0 ] = mobyInputXML;
-
- if ( debug )
- debugPS.println( "returnType just before invoke call is " + soapCall.getReturnType() );
- Object returnedObject = null;
- try {
- returnedObject = soapCall.invoke( new QName( "http://biomoby.org/", mobyService.getName() ),
- mobyXMLInputData );
- }
- catch ( Exception e ) {
- e.printStackTrace();
- // System.err.println("Input: "+mobyInputXML);
- throw new SOAPException( "While invoking SOAP Call: " + e );
- }
-
- try {
- if ( debug ) {
- debugPS.println( "SOAP Response was:\n" );
- debugPS.println( soapCall.getResponseMessage().getSOAPPart().getEnvelope() );
- }
- Element resultDom = ( ( MessageElement ) soapCall.getResponseMessage().getSOAPPart().getEnvelope() ).getAsDOM();
- return decodeSOAPMessage( resultDom, contentsXMLOutput, mobyInputXML );
- }
- catch ( Exception e ) {
- e.printStackTrace();
- throw new SOAPException( "Could not get SOAP response as DOM Element: " + e );
- }
-
- }
-
- 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.
- *
- * @throws SOAPException if the MOBY payload cannot be found in the SOAP message
- * @throws MobyException if the MOBY message is not well-formed XML
- *
- * @return The root element of the MOBY response DOM
- */
- 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;
- XPath responseElementXPath = null;
- try {
- 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 );
- }
- try {
- node_list = runXPath( responseElementXPath, n );
- }
- catch ( TransformerException te ) {
- throw new SOAPException( "Cannot select SOAP nodes due to exception " + "while executing XPath statement:"
- + te );
- }
-
- if ( node_list == null || node_list.getLength() == 0 ) {
- throw new SOAPException( "Could not find a response element in SOAP payload (service "
- + mobyService.getName() + ")" );
- }
-
- if ( node_list.getLength() > 1 ) {
- throw new SOAPException( "Found more than one response element in SOAP payload, "
- + "unable to resolve ambiguity of the payload (service provider error?)" );
- }
-
- 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;
- try {
- node_list = runXPath( base64EncodedXPath, responseNode );
- }
- catch ( TransformerException te ) {
- throw new SOAPException( "Cannot select base64 encoded SOAP nodes due to exception "
- + "while executing XPath statement:" + te );
- }
- if ( debug && node_list != null ) {
- debugPS.println( "There were " + node_list.getLength() + " base64 encoded elements in the data" );
- }
-
- // Do decoding for each base64 part found
- for ( int i = 0; node_list != null && i < node_list.getLength(); i++ ) {
- org.w3c.dom.Node change = node_list.item( i );
- /* Make sure the text data is all put into one contiguous piece for decoding */
- change.normalize();
-
- byte[] decodedBytes = org.apache.axis.encoding.Base64.decode( change.getFirstChild().getNodeValue() );
- String newText = new String( decodedBytes );
- if ( debug ) {
- debugPS.println( "New decoded text is" + newText );
- }
-
- // Swap out this node for the decoded data
- change.getParentNode().replaceChild( n.getOwnerDocument().createTextNode( new String( decodedBytes ) ),
- change );
- }
-
- // Now see if there are any strings that need decoding
- node_list = null;
- try {
- node_list = runXPath( stringEncodedXPath, responseNode );
- }
- catch ( TransformerException te ) {
- throw new SOAPException( "Cannot select string encoded SOAP nodes due to exception "
- + "while executing XPath statement:" + te );
- }
-
- // Do concatenation for each plain string part found
- for ( int i = 0; node_list != null && i < node_list.getLength(); i++ ) {
- org.w3c.dom.Node change = node_list.item( i );
- /* Make sure the text data is all put into one contiguous piece for decoding */
- change.normalize();
- String plainString = "";
- int j = 0;
- for ( NodeList children = change.getChildNodes(); children != null && j < children.getLength(); j++ ) {
- Node child = children.item( j );
- if ( child instanceof CDATASection || child instanceof Text ) {
- plainString += child.getNodeValue();
- if ( debug ) {
- debugPS.println( "Plain string is now " + plainString );
- }
- }
- }
-
- // Swap out this node for the decoded data
- change.getParentNode().replaceChild( n.getOwnerDocument().createCDATASection( plainString ), change );
- }
- if ( debug && node_list != null ) {
- debugPS.println( "There were " + node_list.getLength()
- + " XML Schema string encoded elements in the data" );
- }
-
- // Parse the MOBY XML document payload
- responseNode.normalize();
- NodeList children = responseNode.getChildNodes();
- if ( children == null ) {
- throw new MobyException( "The MOBY payload has no contents at all" );
- }
- if ( children.getLength() != 1 ) {
- if ( debug ) {
- debugPS.println( "Warning: MOBY Payload appears to have more than "
- + "just text in it, skipping the non-text sections" );
- }
- }
-
- 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 ) {
- // Unescape XML special characters in the string, so we can later on
- // parse the payload as regular XML.
- // Ignore whitespace-only node
- if ( child.getNodeValue().matches( "^\\s+$" ) ) {
- continue;
- }
- if ( debug ) {
- debugPS.println( "Concatenating text in response " + child.getNodeValue() );
- }
- 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 != null ) {
- if ( localResponseString.length() == 0 ) {
- throw new MobyException( "The MOBY payload has no text contents at all" );
- }
- if ( Character.isWhitespace( localResponseString.charAt( 0 ) ) ) {
- localResponseString = localResponseString.trim();
- }
- }
-
- // 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 != null && !localResponseString.startsWith( "<?xml" ) ) {
- // Is the XML declaration missing?
- if ( localResponseString.startsWith( "<moby:MOBY" ) || localResponseString.startsWith( "<MOBY" ) ) {
- localResponseString = "<?xml version=\"1.0\"?>\n" + localResponseString;
- debugPS.println( "Warning: The MOBY contents was missing an XML declaration, but it is "
- + "required by the MOBY API, and may stop working in the future without it. Please "
- + "contact the client's provider to correct this." );
- }
- else {
- String oldResponse = localResponseString;
- localResponseString = new String( org.apache.axis.encoding.Base64.decode( localResponseString ) );
- if ( !localResponseString.startsWith( "<?xml" ) ) {
- throw new MobyException( "The SOAP payload defining the MOBY contents "
- + "does not start with the xml processing instruction, and is therefore not "
- + "an XML document, as specified in the MOBY API. "
- + "Please contact the service provider. Contents was: " + oldResponse );
- }
- debugPS.println( "Warning: The MOBY contents was needlessly base64 encoded (the SOAP "
- + "envelope does this for you). It has been decoded, but this is not "
- + "part of the MOBY API, and may stop working in the future. Please "
- + "contact the service provider to correct this." );
- }
- }
-
- // A bit of a hack: most MOBY data represented in string form
- // should have its formatting preserved (e.g. BLAST report), yet
- // no-one uses the xml:space="preserve" attribute. This causes problems
- // 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 != null && localResponseString.indexOf( "xml:space=\"preserve\"" ) == -1 ) {
- localResponseString = localResponseString.replaceAll( "<String", "<String xml:space=\"preserve\"" );
- }
-
- try {
- 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 " + "could not be parsed: " + saxe );
- }
- catch ( java.io.IOException ioe ) {
- throw new MobyException( "The SOAP payload defining the MOBY Result "
- + " could not be read (from a String!)" + ioe );
- }
-
- // Now, either save the xml we got in the class instance member (for synchronous calls to MobyRequest)
- // or in the StringBuffer given as a parameter to this function (async calls)
- if ( contentsXMLOutput != null ) {
- contentsXMLOutput.append( localResponseString );
- }
- 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 domRoot;
- }
-
- public String convertMOBYDataToMOBYRequest(MobyDataInstance data) throws MobyException {
- return convertMOBYDataToMOBYRequest( new MobyContentInstance( data, "" ) );
- }
-
- /**
- * Creates an XML representation of the data, renamed to fit the needs of the service if necessary, and adding any
- * secondary parameter default values if not already specified in the incoming data.
- *
- * @param data the array of input parameters to put in a MOBY XML request
- *
- * @return the XML representation of the input data
- */
- public String convertMOBYDataToMOBYRequest(MobyContentInstance data) throws MobyException {
-
- MobyData[] inputs = mobyService.getPrimaryInputs();
- MobySecondaryData[] secondaries = mobyService.getSecondaryInputs();
-
- // Make sure the number of input args is correct for each query being submitted
- for ( Map.Entry< String, MobyDataJob > entry : data.entrySet() ) {
- String queryName = entry.getKey();
- MobyDataJob query = entry.getValue();
-
- // Additionally, we check if they are MobyDataInstances below
- Map< String, MobyPrimaryData > primaryParams = new HashMap< String, MobyPrimaryData >();
- Map< String, MobySecondaryData > secondaryParams = new HashMap< String, MobySecondaryData >();
-
- // To store the primary input parameter name as given by the user,
- // in case we need it later on for parameter renaming...
- String primaryParamName = null;
-
- for ( Map.Entry< String, MobyDataInstance > subentry : query.entrySet() ) {
- String name = subentry.getKey();
- MobyDataInstance param = subentry.getValue();
- if ( param == null ) {
- throw new MobyException( "Query " + queryName + " contained a null input parameter (" + name + ")" );
- }
- else if ( param instanceof MobyPrimaryData ) {
- primaryParams.put( name, ( MobyPrimaryData ) param );
- primaryParamName = name;
- }
- else if ( param instanceof MobySecondaryData ) {
- secondaryParams.put( name, ( MobySecondaryData ) param );
- }
- else {
- System.err.println( "Input parameter " + name + " (query " + queryName
- + ") was not a MobyPrimaryData or MobySecondaryData "
- + "as expected, but rather was of class " + param.getClass().getName() );
- }
- }
-
- if ( inputs != null && inputs.length != primaryParams.size() ) {
- throw new MobyException( "Service " + mobyService.getName() + " was provided " + primaryParams.size()
- + " primary input parameter(s), but takes " + inputs.length + " (query " + queryName + ")" );
- }
- if ( secondaries != null ) {
- // If no secondaries provided, fill them in by default
- if ( secondaries.length != 0 ) {
- for ( MobySecondaryData secondary : secondaries ) {
- if ( !secondaryParams.containsKey( secondary.getName() ) ) {
- if ( debug ) {
- System.err.println( "Setting default secondary param value for missing param "
- + secondary );
- }
- query.put( secondary.getName(), new MobyDataSecondaryInstance( secondary ) );
- }
- }
- }
- if ( secondaries.length != secondaryParams.size() ) {
- throw new MobyException( "Service " + mobyService.getName() + " was provided "
- + secondaryParams.size() + " secondary input parameter(s), but takes " + secondaries.length
- + " (query " + queryName + "). Extra secondary" + " parameters must have been specified" );
- }
- }
-
- // If there was one anonymous input, assign the name automatically in
- // the case the service requires it to be named. This is the only
- // unambiguous case in which we can do this.
- if ( inputs.length == 1 ) {
- String serviceParamName = inputs[ 0 ].getName(); // name as req'd by the service
-
- // name isn't the same as required currently
- if ( serviceParamName != null && serviceParamName.length() > 0
- && !serviceParamName.equals( primaryParamName ) ) {
- // take out the old parameter
- MobyPrimaryData theInputToRename = ( MobyPrimaryData ) query.remove( primaryParamName );
-
- // Add in the same parameter, but with the appropriate name
- query.put( serviceParamName, ( MobyDataInstance ) theInputToRename );
- }
- }
- }
-
- ByteArrayOutputStream mobyRequest = new ByteArrayOutputStream();
- try {
- MobyDataUtils.toXMLDocument( mobyRequest, data );
- }
- catch ( MobyException me ) {
- throw me;
- }
- catch ( Exception e ) {
- e.printStackTrace();
- throw new MobyException( "Could not create MOBY payload XML from input data: " + e );
- }
-
- if ( debug ) {
- debugPS.println( "Input to MOBY Service is:" );
- debugPS.print( mobyRequest.toString() );
- }
-
- return mobyRequest.toString();
- }
-
- /**
- * A method that sets up the execution environment for and runs a compiled XPath statement against a DOM node You
- * should call releaseXPath when you're done with the results
- *
- * @return the list of Nodes that satisfy the XPath in this Node's context
- */
- protected NodeList runXPath(XPath xpath, Node n) throws TransformerException {
- NodeList result = null;
- int dtm_node_handle = xpath_context.getDTMHandleFromNode( n );
- PrefixResolver node_prefix_resolver = new PrefixResolverDefault( n );
- XObject xobject = xpath.execute( xpath_context, n, node_prefix_resolver );
- if ( xobject instanceof XNodeSet ) {
- result = ( ( XNodeSet ) xobject ).nodelist();
- }
- else if ( debug && xobject != null ) {
- debugPS.println( "Output of XPath was not a XNodeSet as expected, found " + xobject.getClass().getName() );
- debugPS.flush();
- }
- return result;
- }
-
- protected void releaseXPath(Node n) {
- xpath_context.release( xpath_context.getDTM( xpath_context.getDTMHandleFromNode( n ) ), false );
- }
-}
+package org.biomoby.client;
+
+import java.io.*;
+import java.util.*;
+
+import javax.xml.namespace.QName;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.rpc.Service;
+import javax.xml.transform.TransformerException;
+
+import org.apache.axis.client.Call;
+import org.apache.axis.message.MessageElement;
+import org.apache.xml.utils.PrefixResolver;
+import org.apache.xml.utils.PrefixResolverDefault;
+import org.apache.xpath.XPath;
+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.*;
+
+/**
+ * This class handles the WSDL transaction to request a response
+ * from a remote SOAP Web service that handles the
+ * <a href="http://www.biomoby.org">MOBY</a> format. It depends on
+ * having already retrieved the definition of the Web service via
+ * the MOBY central registry using the
+ * <a href="http://www.biomoby.org/moby-live/Java/docs/index.html">jMOBY</a> API,
+ * and for now it uses the
+ * <a href="http://ws.apache.org/axis/index.html">Apache
+ * Axis</a> Web services framework, as well as Apache Xalan. There are code comments for the
+ * few lines that rely on Axis classes rather than the JAX-RPC interfaces.
+ *
+ * @author Paul Gordon gordonp at ucalgary.ca
+ */
+public class MobyRequest{
+
+ protected MobyService mobyService = null;
+ protected MobyContentInstance inputData = null;
+ protected MobyContentInstance outputData = null;
+ protected Central mobyCentral = null;
+ protected PrefixResolver mobyPrefixResolver = null;
+
+ protected Hashtable wsdlCache = null;
+ protected String lastWsdlCacheKey = null;
+ protected DocumentBuilder docBuilder = null;
+ protected Service service = null;
+
+ protected Class stringType;
+ protected static boolean debug = false;
+ protected PrintStream debugPS = System.err;
+ protected XPathContext xpath_context;
+ protected String responseString = null;
+
+ private XPath stringEncodedXPath;
+ private XPath base64EncodedXPath;
+ private XPath simpleChildXPath;
+ private XPath collectionChildXPath;
+ private String user;
+ private String password;
+
+ private int autoID = 0;
+
+ // Used as invocation callback if MobyRequest is acting as a server,
+ // or is executing services as a client asynchronously
+ private Vector<MobyRequestEventHandler> eventHandlers;
+
+ /**
+ * Default constructor. You should have a Central instance around since you're going to
+ * be retrieving MobyServices to pass into here. Lets reuse it.
+ *
+ * @param central An instance of a Moby central object so we can make requests about object types, etc.
+ * @throws ParserConfigurationException if JAXP doesn't have any valid DOM-building XML parsers set up for use
+ */
+ public MobyRequest(Central central) throws ParserConfigurationException{
+ mobyCentral = central;
+ wsdlCache = new Hashtable();
+
+ eventHandlers = new Vector<MobyRequestEventHandler>();
+
+ DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+ dbf.setNamespaceAware(true);
+ docBuilder = dbf.newDocumentBuilder();
+
+ try{
+ stringType = Class.forName("java.lang.String");
+ }catch(ClassNotFoundException classe){
+ debugPS.println("WARNING: Something is very wrong, could not find Class definition of String: " + classe);
+ }
+
+ xpath_context = new XPathContext();
+ mobyPrefixResolver = new MobyPrefixResolver();
+
+ // Now compile the XPath statements that will be used fetch data from the server response
+ try{
+ base64EncodedXPath = new XPath("//*[starts-with(substring-after(@"+
+ MobyPrefixResolver.XSI1999_PREFIX+
+ ":type, ':'), \"base64\") or starts-with(substring-after(@"+
+ MobyPrefixResolver.XSI2001_PREFIX+
+ ":type, ':'), \"base64\")]", null,
+ mobyPrefixResolver, XPath.SELECT);
+ stringEncodedXPath = new XPath("//*[substring-after(@"+
+ MobyPrefixResolver.XSI1999_PREFIX+
+ ":type, ':')=\"string\" or substring-after(@"+
+ MobyPrefixResolver.XSI2001_PREFIX+
+ ":type, ':')=\"string\"] | //"+
+ MobyPrefixResolver.SOAP_ENC_PREFIX+":string", null,
+ mobyPrefixResolver, XPath.SELECT);
+ simpleChildXPath = new XPath("moby:Simple | Simple", null,
+ mobyPrefixResolver, XPath.SELECT);
+ collectionChildXPath = new XPath("moby:Collection | Collection", null,
+ mobyPrefixResolver, XPath.SELECT);
+ }
+ catch(TransformerException te){
+ debugPS.println("Syntax error encountered while compiling XPath " +
+ "statements for internal use (code bug?): " + te);
+ }
+ setDebugMode(System.getProperty("moby.debug") != null);
+ }
+
+ public void setAuthentication(String user, String password){
+ this.user = user;
+ this.password = password;
+ }
+
+ /**
+ * @param mode if true, debugging information is printed to the stream returned by getDebugOutputStream
+ */
+ public void setDebugMode(boolean mode){
+ debug = mode;
+ }
+
+ /**
+ * Standard error is used unless this method is called.
+ *
+ * @param ps the OutputStream to which debugging information is sent.
+ * @throws IllegalArgumentException if the stream is null
+ */
+ public void setDebugPrintStream(PrintStream ps) throws IllegalArgumentException{
+ if(ps == null){
+ throw new IllegalArgumentException("The OutputStream specified to MobyRequest was null");
+ }
+ debugPS = ps;
+ }
+
+ /**
+ * @return the instance of the class implementing Central that we are using
+ */
+ public Central getCentralImpl(){
+ return mobyCentral;
+ }
+
+ /**
+ * @param mobyservice the MobyService that should be executed when invokeService is called
+ */
+ public void setService(MobyService mobyservice){
+ if(mobyservice == null){
+ mobyService = null;
+ }
+ else if(mobyService == null || !mobyservice.equals(mobyService)){
+ mobyService = mobyservice;
+ }
+ }
+
+ /**
+ * @return the MobyService that will be executed when invokeService is called
+ */
+ public MobyService getService(){
+ return mobyService;
+ }
+
+ /**
+ * @return the Raw MOBY XML response as a string
+ */
+ public String getResponseXML(){
+ return responseString;
+ }
+
+ /**
+ * Sets the input data for the MOBY service request. The length of the input array, and the
+ * number of input parameters required by the service must be equal when invokeService() is called.
+ * This method strictly enforces that the input be of the appropriate type for the service.
+ *
+ * Note that there is no requirement to use MobyDataInstance.setXmlMode() before passing in
+ * data, this class will temporarily set the XML mode of the data when it is required.
+ *
+ * @throws IllegalArgumentException if the input does not fit the criteria of the service (e.g. wrong data type)
+ */
+ public void setInput(MobyContentInstance data) throws MobyException{
+ inputData = data;
+ }
+
+ /**
+ * Takes the data in the array, with their current articleNames, as input for the service
+ */
+ public void setInput(MobyDataInstance[] data) throws MobyException{
+ MobyDataJob job = new MobyDataJob();
+ for(MobyDataInstance param: data){
+ job.put(param.getName(), param);
+ }
+ inputData = new MobyContentInstance();
+ inputData.put(job);
+ }
+
+ /**
+ * Convenience method to run services that take one argument. If the service
+ * requires the input to have a name, it will be automatically assigned.
+ */
+ public void setInput(MobyDataInstance datum) throws MobyException{
+ setInput(datum, "");
+ }
+
+ /**
+ * Convenience method to run services that take one named argument.
+ */
+ public void setInput(MobyDataInstance datum, String paramName) throws MobyException{
+ inputData = new MobyContentInstance(datum, paramName);
+ }
+
+ /**
+ * @return the MobyService that will be executed when invokeService is called
+ */
+ public MobyContentInstance getInput(){
+ return inputData;
+ }
+
+ /**
+ * Same functionality as setSecondaryInput(MobyDataSecondaryInstance[])
+ */
+ public void setSecondaryInput(Collection<MobyDataSecondaryInstance> secondaryData) throws MobyException{
+ setSecondaryInput(secondaryData.toArray(new MobyDataSecondaryInstance[secondaryData.size()]));
+ }
+
+ /**
+ * This method will assign the provided secondary parameters to all primary input data currently
+ * in this object. This is covenient if you are running 100 seqs through BLAST and only want to set
+ * the parameters once. If you instead want to set secondary input differently for all primary inputs, you'll
+ * need to create a custom MobyContentInstance as input to setInput().
+ *
+ * @throws MobyException if a parameter name is blank, or overrides a primary parameter
+ */
+ public void setSecondaryInput(MobyDataSecondaryInstance[] secondaryData) throws MobyException{
+
+ Iterator queryNames = inputData.keySet().iterator();
+ // For each query
+ while(queryNames.hasNext()){
+ MobyDataJob queryParams = inputData.get(queryNames.next());
+ // Set all the secondary params (overwrites any old ones)
+ for(int i = 0; i < secondaryData.length; i++){
+ String secName = secondaryData[i].getName();
+ if(secName == null || secName.length() == 0){
+ throw new MobyException("A secondary parameter cannot have a blank name (array index " + i + ")");
+ }
+ if(queryParams.containsKey(secName) && queryParams.get(secName) instanceof MobyPrimaryData){
+ throw new MobyException("A secondary parameter cannot override an existing primary parameter " +
+ "with the same name (" + secName + ")");
+ }
+ queryParams.put(secName, secondaryData[i]);
+ }
+ }
+ }
+
+ /**
+ * @return a vector of MobyDataInstance[], each element of the vector is the collection of response objects for the correspondingly indexed input request.
+ *
+ * @throws MobyException if you try to get the results before calling InvokeService
+ */
+ public MobyContentInstance getOutput() throws MobyException{
+ if(outputData == null){
+ throw new MobyException("Trying to access MOBY service results " +
+ "before the service is invoked");
+ }
+ else{
+ return outputData;
+ }
+ }
+
+ /**
+ * The main method of the class. If all of the MOBY input objects
+ * are properly defined according to the Web service definition,
+ * a SOAP request will be sent to the remote server, and the method
+ * will return one or more MOBY objects (synchronous).
+ * Call this method after calling setService, and setInput. If you do not call
+ * setSecondaryInput, the default secondary parameter values will be used.
+ *
+ * @return the results of the remote Web service in response to the give input
+ *
+ * @throws MobyException i.e. there was something wrong with the input, output or remote service's logic
+ * @throws SOAPException i.e. there was a problem with the underlying transaction/transport layer
+ */
+ public MobyContentInstance invokeService() throws Exception, MobyException, SOAPException, NoSuccessException{
+ return mobyService.isAsynchronous() ? invokeService(inputData, new StringBuffer()) : invokeService(inputData, (StringBuffer) null);
+ }
+
+ // 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(MobyContentInstance inData, StringBuffer contentsXML)
+ throws Exception, MobyException, SOAPException, NoSuccessException{
+ return invokeService(inData, contentsXML, null, 0);
+ }
+
+ private MobyContentInstance invokeService(MobyContentInstance inData, 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)");
+ }
+
+ Element mobyDOM = null;
+ if(mobyService.isAsynchronous()){
+ // Async is "simpler", because it had to merge DOMs together into a single MobyContentInstance anyway
+ MobyContentInstance mci = performAsyncSOAPRequest(mobyService, inData, handler, requestId);
+ StringWriter writer = new StringWriter();
+ MobyDataUtils.toXMLDocument(writer, mci);
+ contentsXML.append(writer.toString());
+ return mci;
+ }
+ else{
+ String mobyXML = convertMOBYDataToMOBYRequest(inData);
+ Call call = getServiceFromWSDL();
+ if(user != null && password != null) {
+ call.setProperty( Call.USERNAME_PROPERTY, user );
+ call.setProperty( Call.PASSWORD_PROPERTY, password );
+ }
+ 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, mobyService.getServiceType().getRegistry());
+ }
+ }
+
+ 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<String>(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());
+ }
+ }
+
+ if(newDataAvailable.size() > 0){
+ // 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, mservice.getServiceType().getRegistry());
+ // 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(handler != null){
+ MobyRequestEvent mre = new MobyRequestEvent(finalContents, this, mservice, null, requestId);
+ StringWriter xmlWriter = new StringWriter();
+ MobyDataUtils.toXMLDocument(xmlWriter, finalContents);
+
+ mre.setContentsXML(xmlWriter.toString());
+ if(!queryIDs.isEmpty()){
+ // Send an update event only if we aren't finished yet.
+ // If we are finished, the client is going to get this event as the
+ // invocation thread finishes up (no need to double up).
+ 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);
+ }
+
+ /**
+ * Asynchronous call to invokeService. A callback to the passed-in handler will be made when
+ * the response is ready, or there is an exception.
+ *
+ * @return the id that the callback event will return from getID(), allowing a client to distinguish between multiple concurrent invocation callbacks
+ */
+ public synchronized int invokeService(MobyRequestEventHandler handler){
+ int id = autoID++;
+
+ Thread t = new InvocationThread(this, inputData, handler, id); // see internal class definition below
+ t.start();
+
+ return id;
+ }
+
+ // This is the class that asynchronously calls the service and does a callback to
+ // the handler specified in the invocation.
+ class InvocationThread extends Thread {
+ MobyContentInstance data;
+ MobyService mservice;
+ MobyRequest mobyRequest;
+ MobyRequestEventHandler handler;
+ int requestId;
+
+ InvocationThread(MobyRequest mr, MobyContentInstance inData, MobyRequestEventHandler h, int id){
+ data = inData;
+ mobyRequest = mr;
+ mservice = mobyRequest.getService();
+ handler = h;
+ requestId = id;
+
+ // Name the thread after the service being run, mostly for ease of debugging
+ setName(mservice.getName()+requestId);
+ }
+
+ public void run() {
+ MobyRequestEvent requestEvent = new MobyRequestEvent(data, mobyRequest, mservice, null, requestId);
+ // Tell the handler we're starting the request, with the given data
+ handler.start(requestEvent);
+
+ MobyRequestEvent responseEvent = null;
+ MobyContentInstance content = null;
+ StringBuffer contentsXML = new StringBuffer(); //to be filled in by the RPC call below
+ try{
+ content = mobyRequest.invokeService(data, contentsXML, handler, requestId); //RPC call...
+ }
+ catch(Exception e){
+ responseEvent = new MobyRequestEvent(content, mobyRequest, mservice, e, requestId);
+ }
+ catch(Error err){
+ responseEvent = new MobyRequestEvent(content, mobyRequest, mservice, err, requestId);
+ }
+ if(responseEvent == null){
+ responseEvent = new MobyRequestEvent(content, mobyRequest, mservice, null, requestId);
+ }
+ // We've got the raw XML laying around, so why not provide it unmolested to the callback?
+ responseEvent.setContentsXML(contentsXML.toString());
+ handler.processEvent(responseEvent);
+ handler.stop(mobyRequest, requestId);
+ }
+ }
+
+ public void addEventHandler(MobyRequestEventHandler h){
+ eventHandlers.add(h);
+ }
+
+ public void removeEventHandler(MobyRequestEventHandler h){
+ eventHandlers.remove(h);
+ }
+
+ public void sendResponse(MobyRequestEvent mre){
+ // Not yet implemented, need to conform to some web.xml specification here...
+ }
+
+ /**
+ * 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.
+ */
+ protected Call getServiceFromWSDL() throws MobyException, NoSuccessException{
+// String wsdl = null;
+
+// // Since this is how we retrieve a service from Central, use the same values as the key to the hash
+// String wsdlCacheKey = mobyService.getName() + "@" + mobyService.getAuthority();
+
+// // This is the same call as last time, so we don't need to change the setup
+// if(wsdlCacheKey.equals(lastWsdlCacheKey)){
+// return setCallFromWSDL((String) wsdlCache.get(wsdlCacheKey));
+// }
+// // We haven't encountered this service yet
+// else if(!wsdlCache.containsKey(wsdlCacheKey)){
+// wsdl = mobyCentral.getServiceWSDL(mobyService.getName(), mobyService.getAuthority());
+// wsdlCache.put(wsdlCacheKey, wsdl);
+// }
+// // We've dealt with this one before
+// else{
+// wsdl = (String) wsdlCache.get(wsdlCacheKey);
+// }
+
+// lastWsdlCacheKey = wsdlCacheKey; // Keep track of the last invocation
+
+ // Get ready to do SOAP
+ return setCallFromWSDL(null);
+ }
+
+ /**
+ * Creates the SOAP Call that will be invoked later. This should be based on the WSDL document
+ * and parameter information from the MobyService, but these are currently not up to snuff.
+ */
+ protected Call setCallFromWSDL(String wsdl) throws MobyException, SOAPException{
+ if(service == null){
+ service = new org.apache.axis.client.Service(); // AXIS SPECIFIC This acts as a factory for Calls
+ }
+
+ Call soapCall;
+ try{
+ soapCall = (Call) service.createCall();//create a fresh Call each time
+ }catch(javax.xml.rpc.ServiceException se){
+ throw new SOAPException("Could not instatiate call to SOAP Service: " + se);
+ }
+
+ // Should initialize endpoint, etc. This call is AXIS SPECIFIC, otherwise you'll
+ // have to do the call's info setting manually.
+ //((org.apache.axis.client.Call) soapCall).setSOAPService(soapService);
+ soapCall.removeAllParameters();
+ soapCall.setTargetEndpointAddress(mobyService.getURL());
+ soapCall.setPortName(new QName("http://biomoby.org/",
+ mobyService.getName() + "PortType"));
+ //soapCall.setOperationName(new QName("http://biomoby.org/",
+ // mobyService.getName()));
+ soapCall.setSOAPActionURI("http://biomoby.org/#" + mobyService.getName());
+ return soapCall;
+ }
+
+ /**
+ * Calls the invoke() method of the JAX-RPC Call interface.
+ */
+ protected Element performSOAPRequest(Call soapCall, String mobyInputXML, StringBuffer contentsXMLOutput) throws SOAPException{
+ // First, turn the input objects into a MOBY XML request
+ String[] mobyXMLInputData = new String[1];
+
+ //Setup
+ mobyXMLInputData[0] = mobyInputXML;
+
+ if(debug)
+ debugPS.println("returnType just before invoke call is " + soapCall.getReturnType());
+ Object returnedObject = null;
+ try{
+ returnedObject = soapCall.invoke(new QName("http://biomoby.org/",
+ mobyService.getName()), mobyXMLInputData);
+ }
+ catch(Exception e){
+ e.printStackTrace();
+ //System.err.println("Input: "+mobyInputXML);
+ throw new SOAPException("While invoking SOAP Call: " + e);
+ }
+
+ try{
+ if(debug){
+ debugPS.println("SOAP Response was:\n");
+ debugPS.println(soapCall.getResponseMessage().getSOAPPart().getEnvelope());
+ }
+ Element resultDom = ((MessageElement) soapCall.getResponseMessage().getSOAPPart().getEnvelope()).getAsDOM();
+ return decodeSOAPMessage(resultDom, contentsXMLOutput, mobyInputXML);
+ } catch(Exception e){
+ e.printStackTrace();
+ throw new SOAPException("Could not get SOAP response as DOM Element: "+ e);
+ }
+
+ }
+
+ 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.
+ *
+ * @throws SOAPException if the MOBY payload cannot be found in the SOAP message
+ * @throws MobyException if the MOBY message is not well-formed XML
+ *
+ * @return The root element of the MOBY response DOM
+ */
+ 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;
+ XPath responseElementXPath = null;
+ try{
+ 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);
+ }
+ try{
+ node_list = runXPath(responseElementXPath, n);
+ }catch(TransformerException te){
+ throw new SOAPException("Cannot select SOAP nodes due to exception "+
+ "while executing XPath statement:" +te);
+ }
+
+ if(node_list == null || node_list.getLength() == 0){
+ throw new SOAPException("Could not find a response element in SOAP payload (service " +
+ mobyService.getName() + ")");
+ }
+
+ if(node_list.getLength() > 1){
+ throw new SOAPException("Found more than one response element in SOAP payload, " +
+ "unable to resolve ambiguity of the payload (service provider error?)");
+ }
+
+ 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;
+ try{
+ node_list = runXPath(base64EncodedXPath, responseNode);
+ }
+ catch(TransformerException te){
+ throw new SOAPException("Cannot select base64 encoded SOAP nodes due to exception "+
+ "while executing XPath statement:" +te);
+ }
+ if(debug && node_list != null){
+ debugPS.println("There were " + node_list.getLength() +
+ " base64 encoded elements in the data");
+ }
+
+ // Do decoding for each base64 part found
+ for(int i = 0; node_list != null && i < node_list.getLength(); i++){
+ org.w3c.dom.Node change = node_list.item(i);
+ /* Make sure the text data is all put into one contiguous piece for decoding*/
+ change.normalize();
+
+ byte[] decodedBytes = org.apache.axis.encoding.Base64.decode(change.getFirstChild().getNodeValue());
+ String newText = new String(decodedBytes);
+ if(debug){
+ debugPS.println("New decoded text is" + newText);
+ }
+
+ // Swap out this node for the decoded data
+ change.getParentNode().replaceChild(n.getOwnerDocument().createTextNode(new String(decodedBytes)),
+ change);
+ }
+
+ // Now see if there are any strings that need decoding
+ node_list = null;
+ try{
+ node_list = runXPath(stringEncodedXPath, responseNode);
+ }
+ catch(TransformerException te){
+ throw new SOAPException("Cannot select string encoded SOAP nodes due to exception "+
+ "while executing XPath statement:" +te);
+ }
+
+ // Do concatenation for each plain string part found
+ for(int i = 0; node_list != null && i < node_list.getLength(); i++){
+ org.w3c.dom.Node change = node_list.item(i);
+ /* Make sure the text data is all put into one contiguous piece for decoding*/
+ change.normalize();
+ String plainString = "";
+ int j = 0;
+ for(NodeList children = change.getChildNodes();
+ children != null && j < children.getLength();
+ j++){
+ Node child = children.item(j);
+ if(child instanceof CDATASection || child instanceof Text){
+ plainString += child.getNodeValue();
+ if(debug){
+ debugPS.println("Plain string is now " + plainString);
+ }
+ }
+ }
+
+ // Swap out this node for the decoded data
+ change.getParentNode().replaceChild(n.getOwnerDocument().createCDATASection(plainString), change);
+ }
+ if(debug && node_list != null){
+ debugPS.println("There were " + node_list.getLength() +
+ " XML Schema string encoded elements in the data");
+ }
+
+ // Parse the MOBY XML document payload
+ responseNode.normalize();
+ NodeList children = responseNode.getChildNodes();
+ if(children == null){
+ throw new MobyException("The MOBY payload has no contents at all");
+ }
+ if(children.getLength() != 1){
+ if(debug){
+ debugPS.println("Warning: MOBY Payload appears to have more than " +
+ "just text in it, skipping the non-text sections");
+ }
+ }
+
+ 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){
+ // Unescape XML special characters in the string, so we can later on
+ // parse the payload as regular XML.
+ // Ignore whitespace-only node
+ if(child.getNodeValue().matches("^\\s+$")){
+ continue;
+ }
+ if(debug){
+ debugPS.println("Concatenating text in response " + child.getNodeValue());
+ }
+ 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 != null){
+ if(localResponseString.length() == 0){
+ throw new MobyException("The MOBY payload has no text contents at all");
+ }
+ if(Character.isWhitespace(localResponseString.charAt(0))){
+ localResponseString = localResponseString.trim();
+ }
+ }
+
+ // 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 != null && !localResponseString.startsWith("<?xml")){
+ // Is the XML declaration missing?
+ if(localResponseString.startsWith("<moby:MOBY") || localResponseString.startsWith("<MOBY")){
+ localResponseString = "<?xml version=\"1.0\"?>\n"+localResponseString;
+ debugPS.println("Warning: The MOBY contents was missing an XML declaration, but it is " +
+ "required by the MOBY API, and may stop working in the future without it. Please " +
+ "contact the client's provider to correct this.");
+ }
+ else{
+ String oldResponse = localResponseString;
+ localResponseString = new String(org.apache.axis.encoding.Base64.decode(localResponseString));
+ if(!localResponseString.startsWith("<?xml")){
+ throw new MobyException("The SOAP payload defining the MOBY contents " +
+ "does not start with the xml processing instruction, and is therefore not " +
+ "an XML document, as specified in the MOBY API. " +
+ "Please contact the service provider. Contents was: " +
+ oldResponse);
+ }
+ debugPS.println("Warning: The MOBY contents was needlessly base64 encoded (the SOAP " +
+ "envelope does this for you). It has been decoded, but this is not " +
+ "part of the MOBY API, and may stop working in the future. Please " +
+ "contact the service provider to correct this.");
+ }
+ }
+
+ // A bit of a hack: most MOBY data represented in string form
+ // should have its formatting preserved (e.g. BLAST report), yet
+ // no-one uses the xml:space="preserve" attribute. This causes problems
+ // 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 != null && localResponseString.indexOf("xml:space=\"preserve\"") == -1){
+ localResponseString = localResponseString.replaceAll("<String", "<String xml:space=\"preserve\"");
+ }
+
+ try{
+ 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 " +
+ "could not be parsed: " + saxe);
+ } catch(java.io.IOException ioe){
+ throw new MobyException("The SOAP payload defining the MOBY Result " +
+ " could not be read (from a String!)" + ioe);
+ }
+
+ // Now, either save the xml we got in the class instance member (for synchronous calls to MobyRequest)
+ // or in the StringBuffer given as a parameter to this function (async calls)
+ if(contentsXMLOutput != null){
+ contentsXMLOutput.append(localResponseString);
+ }
+ 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 domRoot;
+ }
+
+ public String convertMOBYDataToMOBYRequest(MobyDataInstance data) throws MobyException{
+ return convertMOBYDataToMOBYRequest(new MobyContentInstance(data, ""));
+ }
+
+ /**
+ * Creates an XML representation of the data, renamed to fit the needs of the service if necessary,
+ * and adding any secondary parameter default values if not already specified in the incoming data.
+ *
+ * @param data the array of input parameters to put in a MOBY XML request
+ *
+ * @return the XML representation of the input data
+ */
+ public String convertMOBYDataToMOBYRequest(MobyContentInstance data) throws MobyException{
+
+ MobyData[] inputs = mobyService.getPrimaryInputs();
+ MobySecondaryData[] secondaries = mobyService.getSecondaryInputs();
+
+ // Make sure the number of input args is correct for each query being submitted
+ for(Map.Entry<String,MobyDataJob> entry: data.entrySet()){
+ String queryName = entry.getKey();
+ MobyDataJob query = entry.getValue();
+
+ // Additionally, we check if they are MobyDataInstances below
+ Map<String,MobyPrimaryData> primaryParams = new HashMap<String,MobyPrimaryData>();
+ Map<String,MobySecondaryData> secondaryParams = new HashMap<String,MobySecondaryData>();
+
+ // To store the primary input parameter name as given by the user,
+ // in case we need it later on for parameter renaming...
+ String primaryParamName = null;
+
+ for(Map.Entry<String,MobyDataInstance> subentry: query.entrySet()){
+ String name = subentry.getKey();
+ MobyDataInstance param = subentry.getValue();
+ if(param == null){
+ throw new MobyException("Query " + queryName +
+ " contained a null input parameter (" + name + ")");
+ }
+ else if(param instanceof MobyPrimaryData){
+ primaryParams.put(name, (MobyPrimaryData) param);
+ primaryParamName = name;
+ }
+ else if(param instanceof MobySecondaryData){
+ secondaryParams.put(name, (MobySecondaryData) param);
+ }
+ else{
+ System.err.println("Input parameter " + name + " (query " + queryName +
+ ") was not a MobyPrimaryData or MobySecondaryData " +
+ "as expected, but rather was of class " + param.getClass().getName());
+ }
+ }
+
+ if(inputs != null && inputs.length != primaryParams.size()){
+ throw new MobyException("Service " + mobyService.getName() + " was provided " +
+ primaryParams.size() +
+ " primary input parameter(s), but takes " + inputs.length +
+ " (query " + queryName + ")");
+ }
+ if(secondaries != null){
+ // If no secondaries provided, fill them in by default
+ if(secondaries.length != 0){
+ for(MobySecondaryData secondary: secondaries){
+ if(!secondaryParams.containsKey(secondary.getName())){
+ if(debug){
+ System.err.println("Setting default secondary param value for missing param " + secondary);
+ }
+ query.put(secondary.getName(), new MobyDataSecondaryInstance(secondary));
+ }
+ }
+ }
+ if(secondaries.length != secondaryParams.size()){
+ throw new MobyException("Service " + mobyService.getName() + " was provided " +
+ secondaryParams.size() +
+ " secondary input parameter(s), but takes " + secondaries.length +
+ " (query " + queryName + "). Extra secondary" +
+ " parameters must have been specified");
+ }
+ }
+
+ // If there was one anonymous input, assign the name automatically in
+ // the case the service requires it to be named. This is the only
+ // unambiguous case in which we can do this.
+ if(inputs.length == 1){
+ String serviceParamName = inputs[0].getName(); // name as req'd by the service
+
+ // name isn't the same as required currently
+ if(serviceParamName != null && serviceParamName.length() > 0 &&
+ !serviceParamName.equals(primaryParamName)){
+ // take out the old parameter
+ MobyPrimaryData theInputToRename = (MobyPrimaryData) query.remove(primaryParamName);
+
+ // Add in the same parameter, but with the appropriate name
+ query.put(serviceParamName, (MobyDataInstance) theInputToRename);
+ }
+ }
+ }
+
+ ByteArrayOutputStream mobyRequest = new ByteArrayOutputStream();
+ try{
+ MobyDataUtils.toXMLDocument(mobyRequest, data);
+ }
+ catch(MobyException me){
+ throw me;
+ }
+ catch(Exception e){
+ e.printStackTrace();
+ throw new MobyException("Could not create MOBY payload XML from input data: " +e);
+ }
+
+ if(debug){
+ debugPS.println("Input to MOBY Service is:");
+ debugPS.print(mobyRequest.toString());
+ }
+
+ return mobyRequest.toString();
+ }
+
+ /**
+ * A method that sets up the execution environment for and runs a compiled XPath statement against a DOM node
+ * You should call releaseXPath when you're done with the results
+ * @return the list of Nodes that satisfy the XPath in this Node's context
+ */
+ protected NodeList runXPath(XPath xpath, Node n) throws TransformerException{
+ NodeList result = null;
+ int dtm_node_handle = xpath_context.getDTMHandleFromNode(n);
+ PrefixResolver node_prefix_resolver = new PrefixResolverDefault(n);
+ XObject xobject = xpath.execute(xpath_context, n, node_prefix_resolver);
+ if(xobject instanceof XNodeSet){
+ result = ((XNodeSet) xobject).nodelist();
+ }
+ else if(debug && xobject != null){
+ debugPS.println("Output of XPath was not a XNodeSet as expected, found " + xobject.getClass().getName());
+ debugPS.flush();
+ }
+ return result;
+ }
+
+ protected void releaseXPath(Node n){
+ xpath_context.release(xpath_context.getDTM(xpath_context.getDTMHandleFromNode(n)), false);
+ }
+}
===================================================================
RCS file: /home/repository/moby/moby-live/Java/src/main/org/biomoby/client/BaseClient.java,v
retrieving revision 1.13
retrieving revision 1.14
diff -u -r1.13 -r1.14
--- /home/repository/moby/moby-live/Java/src/main/org/biomoby/client/BaseClient.java 2008/11/26 08:53:43 1.13
+++ /home/repository/moby/moby-live/Java/src/main/org/biomoby/client/BaseClient.java 2008/12/03 15:21:13 1.14
@@ -1,429 +1,487 @@
-// BaseClient.java
-//
-// Created: August 2005
-//
-// This file is a component of the BioMoby project.
-// Copyright Martin Senger (martin.senger at gmail.com).
-//
-
-package org.biomoby.client;
-
-import java.io.StringReader;
-import java.net.MalformedURLException;
-import java.net.URL;
-
-import org.apache.axis.client.Call;
-import org.apache.commons.lang.StringUtils;
-import org.biomoby.shared.Central;
-import org.biomoby.shared.MobyException;
-import org.biomoby.shared.MobyService;
-import org.biomoby.shared.parser.JDOMUtils;
-import org.biomoby.shared.parser.MobyJob;
-import org.biomoby.shared.parser.MobyPackage;
-import org.biomoby.shared.parser.MobyTags;
-import org.biomoby.shared.parser.ServiceException;
-import org.jdom.Document;
-import org.jdom.Element;
-import org.jdom.input.SAXBuilder;
-import org.tulsoft.shared.GException;
-import org.tulsoft.tools.soap.axis.AxisCall;
-
-/**
- * This is a base class for Biomoby clients. It takes care about converting user input into Biomoby XML, sending it in a
- * SOAP message to a Biomoby service, waiting for the response and parsing it from Biomoby XML. It also divides requests
- * and responses into so-called <em>jobs</em> - each of them corresponds to a Biomoby query (a Biomoby single network
- * request may contain more queries/jobs).
- * <p>
- *
- * Any client can override various methods - but the ones she/he <em>must</em> override in a subclass are those
- * telling what service to call ({@link #getServiceLocator}), what data to put in a request ({@link #fillRequest(MobyJob,MobyPackage) fillRequest}),
- * and what to do with a response ({@link #useResponse(MobyJob,MobyPackage) useResponse}.
- * <p>
- *
- * @author <A HREF="mailto:martin.senger at gmail.com">Martin Senger</A>
- * @version $Id$
- */
-
-abstract public class BaseClient {
- private static org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog( BaseClient.class );
-
- /*******************************************************************************************************************
- *
- ******************************************************************************************************************/
- static protected boolean notEmpty(String value) {
- return StringUtils.isNotBlank( value );
- }
- static protected boolean isEmpty(String value) {
- return StringUtils.isBlank( value );
- }
-
- /*******************************************************************************************************************
- * The main method that packs input data, invokes a BioMoby service and uses its response. Use this method if the
- * input data should have just one job (which is a usual case) - otherwise use method {@link #process(int)}.
- * <p>
- *
- * @throws MobyException if (a) a sub-class throws it during the filling data or using response, or (b) a Biomoby
- * service invocation fails
- ******************************************************************************************************************/
- public void process() throws MobyException {
- process( 1 );
- }
-
- /*******************************************************************************************************************
- * The main method that packs input data, invokes a BioMoby service and uses its response. The input data may
- * consist from more than one job (query) - the 'jobCount' is a suggestion how many jobs will be included, but this
- * can be changed by the implementing sub-class.
- * <p>
- *
- * Usually a client developer does not need to overwrite this method. She or he makes the real input data filling in
- * the {@link #fillRequest} method, and uses the response in the {@link #useResponse} method.
- * <p>
- *
- * @throws MobyException if (a) a sub-class throws it during the filling data or using response, or (b) a Biomoby
- * service invocation fails
- ******************************************************************************************************************/
- public void process(int jobCount) throws MobyException {
-
- String xmlResponse = null;
-
- // input: raw-level processing
- String xmlInput = fillRequest();
- if ( xmlInput != null ) {
- if ( ( xmlInput = interceptRequest( xmlInput ) ) == null )
- return;
- }
-
- // input: usual processing (i.e. collect XML in iterations)
- else {
- MobyPackage mobyInput = new MobyPackage();
- if ( !fillRequest( mobyInput, jobCount ) )
- return;
- xmlInput = mobyInput.toXML();
- if ( ( xmlInput = interceptRequest( xmlInput ) ) == null )
- return;
- }
-
- // calling service
- xmlResponse = callRemoteService( xmlInput );
-
- // output: raw-level processing
- if ( !useResponse( xmlResponse ) )
- return;
-
- // output: usual processing (again by iterations)
- MobyPackage mobyResponse = MobyPackage.createFromXML( xmlResponse );
- useResponse( mobyResponse );
- }
-
- public String interceptRequest(String xmlInput) throws MobyException {
- return xmlInput;
- }
-
- /*******************************************************************************************************************
- * Create raw XML input. Override this method if you have already an input XML, or you want to create it yourself.
- * <p>
- *
- * @return a full XML input for a Biomoby service (in this case no other <tt>fillRequest</tt> methods will
- * called); return null if no XML was created and a usual process to gather it will be used
- *
- * @throws MobyException if an XML cannot be created
- ******************************************************************************************************************/
- public String fillRequest() throws MobyException {
- return null;
- }
-
- /*******************************************************************************************************************
- *
- ******************************************************************************************************************/
- protected String filterMobyResponseType(Object result) throws MobyException {
- if ( result instanceof String )
- return ( String ) result;
- else if ( result instanceof byte[] )
- return new String( ( byte[] ) result );
- else
- throw new MobyException(
- "The Biomoby data should be sent/received either as type String or base64/byte[]. "
- + "But they are of type '" + result.getClass().getName() + "'." );
- }
-
- /*******************************************************************************************************************
- *
- ******************************************************************************************************************/
- protected String callBiomobyService(MobyServiceLocator locator, String xmlInput) throws MobyException {
-
- MobyService service = locator.getService();
- String serviceName = service.getName();
- boolean asBytes = locator.isAsBytes();
- String serviceEndpoint = service.getURL();
- int timeout = locator.getSuggestedTimeout();
-
- try {
- URL target = new URL( serviceEndpoint );
- AxisCall call = new AxisCall( target, timeout );
- // to allow service calls which need authentication
- setAuthentication( call.getCall() );
- call.getCall().setSOAPActionURI( MobyService.BIOMOBY_SERVICE_URI + "#" + serviceName );
-
- return filterMobyResponseType( call.doCall( MobyService.BIOMOBY_SERVICE_URI, serviceName,
- new Object[]{sendingFilter( xmlInput, asBytes )} ) );
- }
- catch ( MalformedURLException e ) {
- throw new MobyException( "Service endpoint '" + serviceEndpoint + "' is not a valid URL." );
- }
- catch ( GException e ) {
- throw new MobyException( e.getMessage(), e );
- }
- }
-
- /**
- * Sets the authentication for the call. The default implementation does not do anything !
- *
- * @param call
- */
- protected void setAuthentication(Call call) {
- return;
- }
-
- //
- protected Object sendingFilter(String input, boolean asBytes) {
- if ( asBytes ) {
- log.debug( "Data sent as a byte array" );
- return input.getBytes();
- }
- else {
- return input;
- }
- }
-
- /*******************************************************************************************************************
- * Call a SOAP-based BioMoby service. In order to find what service to call and what are its characteristics (such
- * as its endpoint) it will call method {@link #getServiceLocator} that should be implemented by a sub-class.
- * <p>
- *
- * Once it has the service locator, this class does one of the following, in this order:
- * <ul>
- *
- * <li> The locator must contain at least a service name. If it does not, an exception is raised.
- *
- * <li> If the locator contains a service endpoint, a call is made to this endpoint, using also the service name as
- * a method name.
- *
- * <li> If the locator has a registry endpoint, an enquiry to the registry is made to find an endpoint of a service
- * corresponding with the given service name. Once found, the service is called.
- *
- * <li> The same as the previous one but using a default registry.
- *
- * @param xmlInput data will be sent to the called Biomoby service
- *
- * @return service response (still in XML)
- *
- * @throws MobyException (a) if service call (or a call to registry; for example because the registry does not know
- * given service) fails, or (b) if the used {@link MobyServiceLocator} does not contain a service name.
- *
- ******************************************************************************************************************/
- public String callRemoteService(String xmlInput) throws MobyException {
-
- // 1) service name is a must
- MobyServiceLocator locator = getServiceLocator();
- MobyService service = locator.getService();
- if ( service == null )
- throw new MobyException( "MobyService locator returned an empty service object.\n"
- + "I do not know what service to call..." );
- String serviceName = service.getName();
- if ( isEmpty( serviceName ) || MobyService.DUMMY_NAME.equals( serviceName ) )
- throw new MobyException( "MobyService locator returned an empty service name.\n"
- + "I do not know what service to call..." );
-
- // 2) try service endpoint
- String serviceEndpoint = service.getURL();
- if ( notEmpty( serviceEndpoint ) )
- return callBiomobyService( locator, xmlInput );
-
- // 3) find service endpoint in a Biomoby registry
- Central worker = new CentralImpl( locator.getRegistryEndpoint(), locator.getRegistryNamespace() );
- MobyService[] services = worker.findService( service );
- if ( services == null || services.length == 0 )
- throw new MobyException( "Service " + service.toShortString() + " is not known in Biomoby registry: \n"
- + "\t" + worker.getRegistryEndpoint() + "\n" + "\t" + worker.getRegistryNamespace() );
- // ...and call the service
- serviceEndpoint = services[ 0 ].getURL();
- if ( notEmpty( serviceEndpoint ) ) {
- service.setURL( serviceEndpoint );
- return callBiomobyService( locator, xmlInput );
- }
-
- // what else can I do?
- throw new MobyException( "Registry has not returned any URL for service " + service.toShortString() );
- }
-
- /*******************************************************************************************************************
- * Fill the whole 'mobyInput' - put there any number of jobs (queries) as you wish (you do not need to follow the
- * 'jobCount' hint suggesting how many jobs should be put there).
- * <p>
- *
- * Usually there is not need to overwrite this method. It serves as an inter-mediator between the main
- * {@link #process} method and the individual request fillings (done by a sub-class in method
- * {@link #fillRequest(MobyJob,MobyPackage)}).
- * <p>
- *
- * @return false if you wish to cancel whole request (nothing will be sent to a Biomoby service); otherwise return
- * true.
- *
- * @param mobyInput is an empty shell that you are supposed to fill with the input data for a Biomoby service
- * execution
- *
- * @param jobCount is only a suggestion how many requests/job should be created (it comes from the {@link #process}
- * method) - but it does not need to be obeyed
- *
- ******************************************************************************************************************/
- public boolean fillRequest(MobyPackage mobyInput, int jobCount) throws MobyException {
-
- if ( jobCount < 0 )
- jobCount = 0;
- for ( int i = 0; i < jobCount; i++ ) {
- MobyJob request = new MobyJob( "job_" + i );
- if ( !fillRequest( request, mobyInput ) )
- break;
- mobyInput.addJob( request );
- }
- return ( mobyInput.size() > 0 );
- }
-
- /*******************************************************************************************************************
- * A raw-level processing. Use it if you need access to raw XML coming from a service.
- * <p>
- *
- * @param xmlResponse is a raw XML response returned from a BioMoby service
- *
- * @return false if the response should be considered fully processed (in this case no other 'useResponse' will be
- * called); true indicates that normal processing of the response will follow; by default, this class (<tt>BaseClient</tt>)
- * returns true
- *
- * @throws MobyException if you are not satisfied with a response data, or from whatever reasons; it also throws
- * this exception if the 'mobyResponse' is broken
- ******************************************************************************************************************/
- public boolean useResponse(String xmlResponse) throws MobyException {
- return true;
- }
-
- /*******************************************************************************************************************
- * A high-level processing. Use it if you need access to all jobs (queries) - that returned from a service - in the
- * same time. Otherwise use the processing on the job level (method {@link #useResponse(MobyJob,MobyPackage)}.
- * <p>
- *
- * @param mobyResponse is a full response returned from a BioMoby service
- *
- * @throws MobyException if you are not satisfied with a response data, or from whatever reasons; it also throws
- * this exception if the 'mobyResponse' is broken
- ******************************************************************************************************************/
- public void useResponse(MobyPackage mobyResponse) throws MobyException {
-
- // iterate over all input jobs
- for ( int i = 0; i < mobyResponse.size(); i++ ) {
- if ( !useResponse( mobyResponse.getJob( i ), mobyResponse ) )
- return;
- }
- }
-
- /*******************************************************************************************************************
- * Extracts errors from a raw service response. It is iseful when one does not want to create a whole
- * <tt>MobyPackage</tt> from a response, but just find whether a response is good or bad.
- * <p>
- *
- * @param xmlResponse is a full response returned from a BioMoby service
- *
- * @return a slightly formatted list of exceptions (of severity <em>error</em>) extracted from the response; or
- * null if there are no errors there
- ******************************************************************************************************************/
- public String errorsInResponse(String xmlResponse) {
- try {
- StringBuffer buf = new StringBuffer();
- SAXBuilder builder = new SAXBuilder();
- Document doc = builder.build( new StringReader( xmlResponse ) );
- Element root = doc.getRootElement();
- Element mobyContent = JDOMUtils.getChild( root, MobyTags.MOBYCONTENT );
- Element serviceNotes = JDOMUtils.getChild( mobyContent, MobyTags.SERVICENOTES );
- ServiceException[] exceptions = ServiceException.extractExceptions( serviceNotes );
- for ( int i = 0; i < exceptions.length; i++ ) {
- if ( exceptions[ i ].getSeverity() != ServiceException.ERROR )
- continue;
- if ( buf.length() > 0 )
- buf.append( "\n" );
- buf.append( exceptions[ i ].getErrorCodeAsString() );
- buf.append( ": " );
- buf.append( exceptions[ i ].getMessage() );
- }
- return ( buf.length() == 0 ? null : new String( buf ) );
-
- }
- catch ( Exception e ) {
- return e.toString();
- }
- }
-
- //
- // Abstract methods
- //
-
- /*******************************************************************************************************************
- * Return characteristics of a BioMoby service that will be called, and that reveal where to find such service.
- * <p>
- *
- * @see #callRemoteService
- *
- * @return an object defining a location of a BioMoby service
- * @throws MobyException if service locator cannot be returned/created (e.g. because there is not enough information
- * about what service to call)
- ******************************************************************************************************************/
- abstract public MobyServiceLocator getServiceLocator() throws MobyException;
-
- /*******************************************************************************************************************
- * Crate data (fill them into 'request') for one Moby job (query). The request will be sent within given
- * 'inputContext' - but it is not yet there (because you may wish not to put it there - see the return value), and
- * it is not the task of this method to put it there (just fill the 'request').
- * <p>
- *
- * This is a method that should be implemented by a client developer, and it is the place where the client's
- * business logic sits.
- * <p>
- *
- * @return true if this request should be included into the input data (package) that will be sent to a biomoby
- * service; or return false if you do not wish to create any more requests for this particular package (also
- * this 'request' will not be used)
- *
- * @param request is an object that you are supposed to fill with input data for one service invocation; it already
- * has a name (so called 'query id' in the BioMoby speak) but you are free to change it
- *
- * @param inputContext is an envelope where all requests will be stored and sent to a Biomoby service; you do not
- * need to do anything with it here unless you wish; note that all already created requests are there,
- * but not the one you are just creating in this method
- *
- * @throws MobyException if you need so (from whatever reason in your business logic); if thrown then nothing will
- * be sent to a Biomoby service
- *
- ******************************************************************************************************************/
- abstract public boolean fillRequest(MobyJob request, MobyPackage inputContext) throws MobyException;
-
- /*******************************************************************************************************************
- * Process a single job returned from a BioMoby service.
- * <p>
- *
- * This is a method that should be implemented by a client developer, and it is the place where the client's
- * business logic using the response sits.
- * <p>
- *
- * @param response is an object that you are supposed to use
- *
- * @param responseContext is an envelope where the full response (all its jobs) is located; you do not need to do
- * anything with it here unless you wish (e.g. it gives you knowledge about how many jobs are in the full
- * response, or it gives you access to the so-called 'service notes')
- *
- * @return false if you do not wish to get any more responses/jobs; otherwise return true
- *
- * @throws MobyException if you are not satisfied with a response data, or from whatever reasons; it also throws
- * this exception if the 'response' is broken
- *
- ******************************************************************************************************************/
- abstract public boolean useResponse(MobyJob response, MobyPackage responseContext) throws MobyException;
-
-}
+// BaseClient.java
+//
+// Created: August 2005
+//
+// This file is a component of the BioMoby project.
+// Copyright Martin Senger (martin.senger at gmail.com).
+//
+
+package org.biomoby.client;
+
+import org.biomoby.shared.MobyException;
+import org.biomoby.shared.MobyService;
+import org.biomoby.shared.parser.JDOMUtils;
+import org.biomoby.shared.Central;
+import org.biomoby.client.CentralImpl;
+import org.biomoby.shared.parser.MobyPackage;
+import org.biomoby.shared.parser.MobyJob;
+import org.biomoby.shared.parser.ServiceException;
+import org.biomoby.shared.parser.MobyTags;
+
+import org.tulsoft.tools.soap.axis.AxisCall;
+import org.tulsoft.shared.GException;
+
+import org.apache.axis.client.Call;
+import org.apache.commons.lang.StringUtils;
+
+import org.jdom.input.SAXBuilder;
+import org.jdom.Document;
+import org.jdom.Element;
+import org.jdom.Namespace;
+
+import java.net.URL;
+import java.net.MalformedURLException;
+import java.io.StringReader;
+
+/**
+ * This is a base class for Biomoby clients. It takes care about
+ * converting user input into Biomoby XML, sending it in a SOAP
+ * message to a Biomoby service, waiting for the response and parsing
+ * it from Biomoby XML. It also divides requests and responses into
+ * so-called <em>jobs</em> - each of them corresponds to a Biomoby
+ * query (a Biomoby single network request may contain more
+ * queries/jobs). <p>
+ *
+ * Any client can override various methods - but the ones she/he
+ * <em>must</em> override in a subclass are those telling what service
+ * to call ({@link #getServiceLocator}), what data to put in a request
+ * ({@link #fillRequest(MobyJob,MobyPackage) fillRequest}), and what
+ * to do with a response ({@link #useResponse(MobyJob,MobyPackage)
+ * useResponse}. <p>
+ *
+ * @author <A HREF="mailto:martin.senger at gmail.com">Martin Senger</A>
+ * @version $Id$
+ */
+
+abstract public class BaseClient {
+
+ private static org.apache.commons.logging.Log log =
+ org.apache.commons.logging.LogFactory.getLog (BaseClient.class);
+
+ /**************************************************************************
+ *
+ *************************************************************************/
+ static protected boolean notEmpty (String value) {
+ return StringUtils.isNotBlank (value);
+ }
+ static protected boolean isEmpty (String value) {
+ return StringUtils.isBlank (value);
+ }
+
+ /**************************************************************************
+ * The main method that packs input data, invokes a BioMoby
+ * service and uses its response. Use this method if the input
+ * data should have just one job (which is a usual case) -
+ * otherwise use method {@link #process(int)}. <p>
+ *
+ * @throws MobyException if (a) a sub-class throws it during the
+ * filling data or using response, or (b) a Biomoby service
+ * invocation fails
+ *************************************************************************/
+ public void process()
+ throws MobyException {
+ process (1);
+ }
+
+ /**************************************************************************
+ * The main method that packs input data, invokes a BioMoby
+ * service and uses its response. The input data may consist from
+ * more than one job (query) - the 'jobCount' is a suggestion how
+ * many jobs will be included, but this can be changed by the
+ * implementing sub-class. <p>
+ *
+ * Usually a client developer does not need to overwrite this
+ * method. She or he makes the real input data filling in the
+ * {@link #fillRequest} method, and uses the response in the
+ * {@link #useResponse} method. <p>
+ *
+ * @throws MobyException if (a) a sub-class throws it during the
+ * filling data or using response, or (b) a Biomoby service
+ * invocation fails
+ *************************************************************************/
+ public void process (int jobCount)
+ throws MobyException {
+
+ String xmlResponse = null;
+
+ // input: raw-level processing
+ String xmlInput = fillRequest();
+ if (xmlInput != null) {
+ if ( (xmlInput = interceptRequest (xmlInput)) == null )
+ return;
+ }
+
+ // input: usual processing (i.e. collect XML in iterations)
+ else {
+ MobyPackage mobyInput = new MobyPackage();
+ if (! fillRequest (mobyInput, jobCount)) return;
+ xmlInput = mobyInput.toXML();
+ if ( (xmlInput = interceptRequest (xmlInput)) == null )
+ return;
+ }
+
+ // calling service
+ xmlResponse = callRemoteService (xmlInput);
+
+ // output: raw-level processing
+ if (! useResponse (xmlResponse)) return;
+
+ // output: usual processing (again by iterations)
+ MobyPackage mobyResponse = MobyPackage.createFromXML (xmlResponse);
+ useResponse (mobyResponse);
+ }
+
+ public String interceptRequest (String xmlInput)
+ throws MobyException {
+ return xmlInput;
+ }
+
+ /**************************************************************************
+ * Create raw XML input. Override this method if you have already
+ * an input XML, or you want to create it yourself. <p>
+ *
+ * @return a full XML input for a Biomoby service (in this case no
+ * other <tt>fillRequest</tt> methods will called); return null if
+ * no XML was created and a usual process to gather it will be used
+ *
+ * @throws MobyException if an XML cannot be created
+ *************************************************************************/
+ public String fillRequest()
+ throws MobyException {
+ return null;
+ }
+
+ /**************************************************************************
+ *
+ *************************************************************************/
+ protected String filterMobyResponseType (Object result)
+ throws MobyException {
+ if (result instanceof String)
+ return (String)result;
+ else if (result instanceof byte[])
+ return new String ((byte[])result);
+ else
+ throw new MobyException
+ ("The Biomoby data should be sent/received either as type String or base64/byte[]. " +
+ "But they are of type '" + result.getClass().getName() + "'.");
+ }
+
+ /**************************************************************************
+ *
+ *************************************************************************/
+ protected String callBiomobyService (MobyServiceLocator locator,
+ String xmlInput)
+ throws MobyException {
+
+
+ MobyService service = locator.getService();
+ String serviceName = service.getName();
+ boolean asBytes = locator.isAsBytes();
+ String serviceEndpoint = service.getURL();
+ int timeout = locator.getSuggestedTimeout();
+
+ try {
+ URL target = new URL (serviceEndpoint);
+ AxisCall call = new AxisCall (target, timeout);
+ // if the locator has authentication information.
+ if(locator.hasAuthentication()) {
+ call.getCall().setProperty( Call.USERNAME_PROPERTY, locator.getUser() );
+ call.getCall().setProperty( Call.PASSWORD_PROPERTY, locator.getPassword() );
+ }
+ call.getCall().setSOAPActionURI (MobyService.BIOMOBY_SERVICE_URI + "#" + serviceName);
+ return filterMobyResponseType
+ (call.doCall (MobyService.BIOMOBY_SERVICE_URI,
+ serviceName,
+ new Object[] { sendingFilter (xmlInput, asBytes) }));
+ } catch (MalformedURLException e) {
+ throw new MobyException ("Service endpoint '" + serviceEndpoint +
+ "' is not a valid URL.");
+ } catch (GException e) {
+ throw new MobyException (e.getMessage(), e);
+ }
+ }
+
+ //
+ protected Object sendingFilter (String input, boolean asBytes) {
+ if (asBytes) {
+ log.debug ("Data sent as a byte array");
+ return input.getBytes();
+ } else {
+ return input;
+ }
+ }
+
+ /**************************************************************************
+ * Call a SOAP-based BioMoby service. In order to find what
+ * service to call and what are its characteristics (such as its
+ * endpoint) it will call method {@link #getServiceLocator} that
+ * should be implemented by a sub-class. <p>
+ *
+ * Once it has the service locator, this class does one of the
+ * following, in this order: <ul>
+ *
+ * <li> The locator must contain at least a service name. If it
+ * does not, an exception is raised.
+ *
+ * <li> If the locator contains a service endpoint, a call is
+ * made to this endpoint, using also the service name as a
+ * method name.
+ *
+ * <li> If the locator has a registry endpoint, an enquiry to
+ * the registry is made to find an endpoint of a service
+ * corresponding with the given service name. Once found, the
+ * service is called.
+ *
+ * <li> The same as the previous one but using a default
+ * registry.
+ *
+ * @param xmlInput data will be sent to the called Biomoby service
+ *
+ * @return service response (still in XML)
+ *
+ * @throws MobyException (a) if service call (or a call to
+ * registry; for example because the registry does not know given
+ * service) fails, or (b) if the used {@link MobyServiceLocator}
+ * does not contain a service name.
+ *
+ *************************************************************************/
+ public String callRemoteService (String xmlInput)
+ throws MobyException {
+
+ // 1) service name is a must
+ MobyServiceLocator locator = getServiceLocator();
+ MobyService service = locator.getService();
+ if (service == null)
+ throw new MobyException ("MobyService locator returned an empty service object.\n" +
+ "I do not know what service to call...");
+ String serviceName = service.getName();
+ if (isEmpty (serviceName) ||
+ MobyService.DUMMY_NAME.equals (serviceName))
+ throw new MobyException ("MobyService locator returned an empty service name.\n" +
+ "I do not know what service to call...");
+
+ // 2) try service endpoint
+ String serviceEndpoint = service.getURL();
+ if (notEmpty (serviceEndpoint))
+ return callBiomobyService (locator, xmlInput);
+
+ // 3) find service endpoint in a Biomoby registry
+ Central worker = new CentralImpl (locator.getRegistryEndpoint(),
+ locator.getRegistryNamespace());
+ MobyService[] services = worker.findService (service);
+ if (services == null || services.length == 0)
+ throw new MobyException ("Service " + service.toShortString() +
+ " is not known in Biomoby registry: \n" +
+ "\t" + worker.getRegistryEndpoint() + "\n" +
+ "\t" + worker.getRegistryNamespace());
+ // ...and call the service
+ serviceEndpoint = services[0].getURL();
+ if (notEmpty (serviceEndpoint)) {
+ service.setURL (serviceEndpoint);
+ return callBiomobyService (locator, xmlInput);
+ }
+
+ // what else can I do?
+ throw new MobyException ("Registry has not returned any URL for service " +
+ service.toShortString());
+ }
+
+ /**************************************************************************
+ * Fill the whole 'mobyInput' - put there any number of jobs
+ * (queries) as you wish (you do not need to follow the 'jobCount'
+ * hint suggesting how many jobs should be put there). <p>
+ *
+ * Usually there is not need to overwrite this method. It serves
+ * as an inter-mediator between the main {@link #process} method
+ * and the individual request fillings (done by a sub-class in
+ * method {@link #fillRequest(MobyJob,MobyPackage)}). <p>
+ *
+ * @return false if you wish to cancel whole request (nothing will
+ * be sent to a Biomoby service); otherwise return true.
+ *
+ * @param mobyInput is an empty shell that you are supposed to
+ * fill with the input data for a Biomoby service execution
+ *
+ * @param jobCount is only a suggestion how many requests/job
+ * should be created (it comes from the {@link #process} method) -
+ * but it does not need to be obeyed
+ *
+ *************************************************************************/
+ public boolean fillRequest (MobyPackage mobyInput, int jobCount)
+ throws MobyException {
+
+ if (jobCount < 0) jobCount = 0;
+ for (int i = 0; i < jobCount; i++) {
+ MobyJob request = new MobyJob ("job_" + i);
+ if (! fillRequest (request, mobyInput))
+ break;
+ mobyInput.addJob (request);
+ }
+ return (mobyInput.size() > 0);
+ }
+
+ /**************************************************************************
+ * A raw-level processing. Use it if you need access to raw XML
+ * coming from a service. <p>
+ *
+ * @param xmlResponse is a raw XML response returned from a
+ * BioMoby service
+ *
+ * @return false if the response should be considered fully
+ * processed (in this case no other 'useResponse' will be called);
+ * true indicates that normal processing of the response will
+ * follow; by default, this class (<tt>BaseClient</tt>) returns true
+ *
+ * @throws MobyException if you are not satisfied with a response
+ * data, or from whatever reasons; it also throws this exception
+ * if the 'mobyResponse' is broken
+ *************************************************************************/
+ public boolean useResponse (String xmlResponse)
+ throws MobyException {
+ return true;
+ }
+
+ /**************************************************************************
+ * A high-level processing. Use it if you need access to all jobs
+ * (queries) - that returned from a service - in the same time.
+ * Otherwise use the processing on the job level (method {@link
+ * #useResponse(MobyJob,MobyPackage)}. <p>
+ *
+ * @param mobyResponse is a full response returned from a BioMoby
+ * service
+ *
+ * @throws MobyException if you are not satisfied with a response
+ * data, or from whatever reasons; it also throws this exception
+ * if the 'mobyResponse' is broken
+ *************************************************************************/
+ public void useResponse (MobyPackage mobyResponse)
+ throws MobyException {
+
+ // iterate over all input jobs
+ for (int i = 0; i < mobyResponse.size(); i++) {
+ if (! useResponse (mobyResponse.getJob (i),
+ mobyResponse))
+ return;
+ }
+ }
+
+ /**************************************************************************
+ * Extracts errors from a raw service response. It is iseful when
+ * one does not want to create a whole <tt>MobyPackage</tt> from a
+ * response, but just find whether a response is good or bad. <p>
+ *
+ * @param xmlResponse is a full response returned from a BioMoby
+ * service
+ *
+ * @return a slightly formatted list of exceptions (of severity
+ * <em>error</em>) extracted from the response; or null if there
+ * are no errors there
+ *************************************************************************/
+ public String errorsInResponse (String xmlResponse) {
+ try {
+ StringBuffer buf = new StringBuffer();
+ SAXBuilder builder = new SAXBuilder();
+ Document doc =
+ builder.build (new StringReader (xmlResponse));
+ Element root = doc.getRootElement();
+ Element mobyContent = JDOMUtils.getChild (root, MobyTags.MOBYCONTENT);
+ Element serviceNotes = JDOMUtils.getChild (mobyContent, MobyTags.SERVICENOTES);
+ ServiceException[] exceptions =
+ ServiceException.extractExceptions (serviceNotes);
+ for (int i = 0; i < exceptions.length; i++) {
+ if (exceptions[i].getSeverity() != ServiceException.ERROR)
+ continue;
+ if (buf.length() > 0)
+ buf.append ("\n");
+ buf.append (exceptions[i].getErrorCodeAsString());
+ buf.append (": ");
+ buf.append (exceptions[i].getMessage());
+ }
+ return (buf.length() == 0 ? null : new String (buf));
+
+ } catch (Exception e) {
+ return e.toString();
+ }
+ }
+
+
+ //
+ // Abstract methods
+ //
+
+ /**************************************************************************
+ * Return characteristics of a BioMoby service that will be
+ * called, and that reveal where to find such service. <p>
+ *
+ * @see #callRemoteService
+ *
+ * @return an object defining a location of a BioMoby service
+ * @throws MobyException if service locator cannot be
+ * returned/created (e.g. because there is not enough information
+ * about what service to call)
+ *************************************************************************/
+ abstract public MobyServiceLocator getServiceLocator()
+ throws MobyException;
+
+ /**************************************************************************
+ * Crate data (fill them into 'request') for one Moby job
+ * (query). The request will be sent within given 'inputContext' -
+ * but it is not yet there (because you may wish not to put it
+ * there - see the return value), and it is not the task of this
+ * method to put it there (just fill the 'request'). <p>
+ *
+ * This is a method that should be implemented by a client
+ * developer, and it is the place where the client's business
+ * logic sits. <p>
+ *
+ * @return true if this request should be included into the input
+ * data (package) that will be sent to a biomoby service; or
+ * return false if you do not wish to create any more requests for
+ * this particular package (also this 'request' will not be used)
+ *
+ * @param request is an object that you are supposed to fill with
+ * input data for one service invocation; it already has a name
+ * (so called 'query id' in the BioMoby speak) but you are free to change it
+ *
+ * @param inputContext is an envelope where all requests will be
+ * stored and sent to a Biomoby service; you do not need to do
+ * anything with it here unless you wish; note that all already
+ * created requests are there, but not the one you are just
+ * creating in this method
+ *
+ * @throws MobyException if you need so (from whatever reason in
+ * your business logic); if thrown then nothing will be sent to a
+ * Biomoby service
+ *
+ *************************************************************************/
+ abstract public boolean fillRequest (MobyJob request, MobyPackage inputContext)
+ throws MobyException;
+
+ /**************************************************************************
+ * Process a single job returned from a BioMoby service. <p>
+ *
+ * This is a method that should be implemented by a client
+ * developer, and it is the place where the client's business
+ * logic using the response sits. <p>
+ *
+ * @param response is an object that you are supposed to use
+ *
+ * @param responseContext is an envelope where the full response
+ * (all its jobs) is located; you do not need to do anything with
+ * it here unless you wish (e.g. it gives you knowledge about how
+ * many jobs are in the full response, or it gives you access to
+ * the so-called 'service notes')
+ *
+ * @return false if you do not wish to get any more
+ * responses/jobs; otherwise return true
+ *
+ * @throws MobyException if you are not satisfied with a response
+ * data, or from whatever reasons; it also throws this exception
+ * if the 'response' is broken
+ *
+ *************************************************************************/
+ abstract public boolean useResponse (MobyJob response,
+ MobyPackage responseContext)
+ throws MobyException;
+
+}
===================================================================
RCS file: /home/repository/moby/moby-live/Java/src/main/org/biomoby/client/MobyServiceLocator.java,v
retrieving revision 1.4
retrieving revision 1.5
diff -u -r1.4 -r1.5
--- /home/repository/moby/moby-live/Java/src/main/org/biomoby/client/MobyServiceLocator.java 2006/02/19 18:42:55 1.4
+++ /home/repository/moby/moby-live/Java/src/main/org/biomoby/client/MobyServiceLocator.java 2008/12/03 15:21:13 1.5
@@ -33,6 +33,8 @@
protected String registryNamespace;
protected boolean asBytes = false;
protected int timeout = 0;
+ protected String user;
+ protected String password;
/**************************************************************************
* Default constructor.
@@ -131,6 +133,44 @@
public void setAsBytes (boolean enabled) {
asBytes = enabled;
}
-
-
+
+ /*****************************************
+ * Sets the user for authentication
+ * @param user
+ ***************************************/
+ public void setUser(String user) {
+ this.user = user;
+ }
+
+ /***************************************
+ * Sets the password for authentication
+ * @param password
+ ***************************************/
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ /******************************************
+ * Returns the user for the authentication
+ * @return user
+ ******************************************/
+ public String getUser() {
+ return user;
+ }
+
+ /**********************************************
+ * Returns the password for the authentication
+ * @return password
+ **********************************************/
+ public String getPassword() {
+ return password;
+ }
+
+ /***************************************************
+ * Returns true if the user AND the password is set
+ * @return
+ ***************************************************/
+ public boolean hasAuthentication() {
+ return user != null && password != null;
+ }
}
More information about the MOBY-guts
mailing list