[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("&lt;", "<").replaceAll("&gt;",
-					// ">").replaceAll("(&amp;|&#x46;)", "&");
-				}
-				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("&lt;", "<").replaceAll("&gt;", ">").replaceAll("(&amp;|&#x46;)", "&");
+	    }
+	    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