// BaseService.java
//
//    Created: August 2005
//
// This file is a component of the BioMoby project.
// Copyright Martin Senger (martin.senger@gmail.com).
//

package org.biomoby.service;

import org.tulsoft.tools.soap.SOAPToolkit;
import org.tulsoft.tools.soap.SoapUtils;
import org.tulsoft.shared.GException;

import org.biomoby.shared.MobyException;
import org.biomoby.shared.parser.MobyPackage;
import org.biomoby.shared.parser.MobyJob;
import org.biomoby.shared.parser.ServiceException;

import org.apache.axis.transport.http.HTTPConstants;
import org.apache.axis.MessageContext;
import javax.servlet.http.HttpServletRequest;

import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.Enumeration;
import java.util.Properties;

/**
 * A base class for all Biomoby services (if their providers choose to
 * write their services this way, of course). It contains basic
 * features expected from any SOAP-based Web service (such as an
 * access to the initialization parameters - see methods {@link
 * #getParamNames} and {@link #getParam}), and also features expected
 * from any Biomoby Web service (such as parsing input XML data and
 * creating smoothly XML output data). <p>
 *
 * This base class does not contain any service-specific entities. It
 * serves all Biomoby services. The service-specific features (notably
 * the method that is called when a service is invoked) are in the
 * generated service skeletons (a separate skeleton for every
 * service). A service provider extends a service skeleton by her/his
 * own class that implements service business logic but does not need
 * to deal with the Biomoby protocol/envelope at all. <p>
 *
 * Here is the basic inheritance picture of involved classes for a
 * Biomoby service registerd (in a Biomoby registry) under the name
 * "A_Service": <p> <img src="doc-files/service-inheritance.png"
 * border=0 alt="service inheritance" align="center"/> <p>
 *
 * A typical generated skeleton (for service "A_Service") would look
 * like this: <p>
 *
 * <table border=1 cellpadding=10><tr bgcolor="#CCFFFF"><td>
<pre>abstract public class A_ServiceSkel
    extends BaseService {

    public String A_Service (Object data) {

	try {
	    // reading the whole input
	    MobyPackage mobyInput = MobyPackage.createFromXML (data);

	    // prepare an output object
	    MobyPackage mobyOutput = prepareOutput (mobyInput);

	    // do the main job
	    processIt (mobyInput, mobyOutput);

	    // and return an XML back
	    return mobyOutput.toXML();

	} catch (MobyException e) {
	    return error (e.getMessage());
	}
    }
}</pre>
 * </td></tr></table><p>
 *
 * While a service provider class (implemented manually) could look
 * like this (its name is arbitrary): <p>
 *
 * <table border=1 cellpadding=10><tr bgcolor="#CCFFFF"><td>
<pre> public class AServiceImpl
    extends A_ServiceSkel {

    public void processIt (MobyJob request,
		           MobyJob response,
			   MobyPackage outputContext)
	throws MobyException {

        // this is how to get input data
	GenericSequence input = get_GenericSequence (request);
	String seq = input.get_SequenceString();

        // this is an example of a trivial "business logic"
	response.setData (new MobyInteger (seq.length()));
    }
}</pre>
 * </td></tr></table><p>
 *
 * Note that this particular service uses an input object of type
 * <tt>GenericSequence</tt> and an output object of type
 * <tt>Integer</tt> - but if a more specific object, such as a
 * <tt>NucleotideSequence</tt> comes from a client, it will handle it
 * properly. <p>
 *
 * @author <A HREF="mailto:martin.senger@gmail.com">Martin Senger</A>
 * @version $Id: BaseService.java,v 1.9 2008/03/02 12:45:26 senger Exp $
 */


abstract public class BaseService {

    // communication with the surrounding SOAP toolkit (shared by all
    // instances); if not used in a servlet context, this toolkit
    // finds parameters in system properties
    protected static SOAPToolkit toolkit;

    /**************************************************************************
     * Default constructor.
     **************************************************************************/
    public BaseService() {

	try {
	    // toolkit may be already initialized by a previous instance
	    // (we need only one instance - but someone may have deployed
	    // this with a different scope, such as "request" or
	    // "session") which would lead to more instances of this
	    // (which I hope does not do any harm)
	    if (toolkit == null)
		toolkit = SoapUtils.loadSOAPToolkit();
	} catch (GException e) {
	    // TBD: substitute with a message to a log
	    System.err.println ("Cannot load SOAP toolkit: " + e.getMessage()); 
	}
    }

    /**************************************************************************
     * Return a list containing the parameter names available within
     * the context of the invoked web service together with the global
     * context. <p>
     *
     * Any web service (deployed within a reasonable environment, such
     * as Apache Axis) can be deployed with some initialization
     * parameters that are static (they do not change during the Web
     * Service) and they are, of course, not changeable not even
     * visible to the service clients. Typical example would be
     * parameters needed for a JDBC connection (JDBC URL, user name,
     * password etc.). <p>
     *
     * @return a list of available parameter names. If this class is
     * not used within a servlet container (which may be during its
     * testing) it return names of system properties.
     *
     * @see #getParam
     **************************************************************************/
    public String[] getParamNames() {
	// toolkit can be null if the constructor failed to load it -
	// but I want to keep the constructor of this class without
	// any throwing exception, so I simulate here what toolkit can do
	if (toolkit == null) {
	    List<String> v = new ArrayList<String>();
	    Properties props = System.getProperties();
	    for (Enumeration en = props.propertyNames(); en.hasMoreElements(); )
		v.add ((String)en.nextElement());
	    return v.toArray (new String[] {});
	}
	return toolkit.getAttributeNames();
    }

    /**************************************************************************
     * Get value of a parameter 'name'. See explanation what a
     * parameter is in {@link #getParamNames}. <p>
     *
     * @param name of a parameter
     * @return a value of the named parameter, or null if the
     * parameter does not exist; note that when run not in a servlet
     * container the returned value is looked for in the system
     * properties instead
     **************************************************************************/
    public String getParam (String name) {
	// toolkit can be null if the constructor failed to load it -
	// but I want to keep the constructor of this class without
	// any throwing exception, so I simulate here what toolkit can do
	if (toolkit == null) {
	    return System.getProperty (name);
	}
	return toolkit.getAttribute (name);
    }

    /**************************************************************************
     * Return a toolkit that gives you broader access to the
     * environment where this service is deployed in. Especially it is
     * useful for session management. <p>
     *
     * See details what a toolkit can provide in
     * <tt>org.tulsoft.tools.soap.SOAPToolkit</tt>.
     *************************************************************************/
    public SOAPToolkit getToolkit() {
	return toolkit;
    }

    /**************************************************************************
     * Returns a servlet request instance - the one that delivered a
     * request to this service. <p>
     *
     * @return a servlet request, or null if the request is not
     * available
     *************************************************************************/
    public HttpServletRequest getServletRequest() {
	MessageContext ctx = MessageContext.getCurrentContext();
	if (ctx == null)
	    return null;
  	HttpServletRequest req =
	    (HttpServletRequest)ctx.getProperty (HTTPConstants.MC_HTTP_SERVLETREQUEST);
	return req;
    }

    /**************************************************************************
     * Returns the Internet Protocol (IP) address of the client or
     * last proxy that sent the request. <p>
     *
     * @return the caller address or null if the address is unknown
     *************************************************************************/
    public String getCallerAddr() {
	MessageContext ctx = MessageContext.getCurrentContext();
	if (ctx == null)
	    return null;
  	HttpServletRequest req =
	    (HttpServletRequest)ctx.getProperty (HTTPConstants.MC_HTTP_SERVLETREQUEST);
	return req.getRemoteAddr();
    }

    /**************************************************************************
     * Returns HTTP headers that were used when delivering a request
     * to this services. <p>
     *
     * @return a set of name-value pairs representinh HTTP headers; if
     * the headers are not accessible, an empty set is returned (never
     * null)
     *************************************************************************/
    public Properties getHTTPHeaders() {
	Properties headers = new Properties();
	MessageContext ctx = MessageContext.getCurrentContext();
	if (ctx == null)
	    return headers;
	HttpServletRequest req =
	    (HttpServletRequest)ctx.getProperty (HTTPConstants.MC_HTTP_SERVLETREQUEST);
	if (req == null)
	    return headers;
	for (Enumeration en = req.getHeaderNames(); en.hasMoreElements(); ) {
	    String headerName = (String)en.nextElement();
	    String headerValue = req.getHeader (headerName);
	    if (headerValue != null)
		headers.put (headerName, headerValue);
	}
	return headers;
    }

    /**************************************************************************
     * Returns length (in bytes) of the request coming to this service. <p>
     *
     * The length is taken from the HTTP headers. This is only a
     * convenient method extracting one header from those available by
     * the {@link #getHTTPHeaders getHTTPHeaders}. >p>
     *
     * @return a length of the request, or zero if the request length
     * is not available
     *************************************************************************/
    public int getRequestLength() {
	MessageContext ctx = MessageContext.getCurrentContext();
	if (ctx == null)
	    return 0;
	HttpServletRequest req =
	    (HttpServletRequest)ctx.getProperty (HTTPConstants.MC_HTTP_SERVLETREQUEST);
	if (req == null)
	    return 0;
	try {
	    return new Integer (req.getHeader ("content-length")).intValue();
	} catch (Exception e) {
	    return 0;
	}
    }

    /**************************************************************************
     * A utility method returning true if 'value' is neither null nor
     * a string consisting only from whitespaces.
     *************************************************************************/
    public static boolean notEmpty (String value) {
	return ( value != null && ! "".equals (value.trim()) );
    }

    /**************************************************************************
     * A utility method returning true if 'value' is either null or a
     * string consisting only from whitespaces.
     *************************************************************************/
    public static boolean isEmpty (String value) {
	return ! notEmpty (value);
    }

    /**************************************************************************
     * Error handling. <p>
     *
     * This method produces an empty (no data) response with the
     * 'message' in its service notes tag. Whatever is returned by
     * this method is send back to the client. So if you override it,
     * you must return back an XML (if you wish the Biomoby client to
     * understand/parse it). <p>
     *
     * @param message is a reason why this method was called in the
     * first place; it is a good practise to incorporate it (somehow)
     * in the return value
     *
     * @param outputSoFar is a so-far-filled response (it may be
     * null); you may consider to use it and to fill its service notes
     * tag with the 'message', or you can ignore it and create your
     * own error response
     *
     * @return an XML (hopefully Biomoby compliant) response
     *************************************************************************/
    public String error (String message, MobyPackage outputSoFar) {
	if (outputSoFar == null)
	    outputSoFar = new MobyPackage();
	outputSoFar.addException
	    (ServiceException.error ("AN ERROR OCCURED DURING THE SERVICE EXECUTION: "
				     + message));
	return outputSoFar.toXML();
    }

    /**************************************************************************
     * Return a package that has the same number of jobs
     * (and named the same) as the given input. But otherwise it is empty. <p>
     *
     * Usually, there is no need to override this method. It is called
     * by a service skeleton, once an input XML is parsed and before a
     * service implementation class is called to process it. <p>
     *
     * @param mobyInput contains all data coming from a client
     * @return an empty package that will later go back to the client
     * @throws MobyException if input data package is corrupted
     *************************************************************************/
    public MobyPackage prepareOutput (MobyPackage mobyInput)
	throws MobyException {

	MobyPackage mobyOutput = new MobyPackage();

	// iterate over all input jobs
	for (int i = 0; i < mobyInput.size(); i++) {
	    mobyOutput.addJob
		(new MobyJob (mobyInput.getJob (i).getId()));
	}

	return mobyOutput;
    }

    /**************************************************************************
     * A high-level processing. Override this method if you need
     * access to all jobs in the same time. Otherwise use the {@link
     * #processIt(MobyJob,MobyJob,MobyPackage) processessig on the job
     * level}. <p>
     *
     * @param mobyInput contain all data coming from a client
     * @param mobyOutput is an empty package that will go later to the
     * client; this method should fill it with a response
     *
     * @throws MobyException if processing failed; in which case the
     * caller of this method (which is usually a generated skeleton)
     * will probably call the {@link #error} method
     *************************************************************************/
    public void processIt (MobyPackage mobyInput, MobyPackage mobyOutput)
	throws MobyException {

	// iterate over all input jobs
	for (int i = 0; i < mobyInput.size(); i++) {
	    processIt (mobyInput.getJob (i),
		       mobyOutput.getJob (i),
		       mobyOutput);
	}
    }

    /**************************************************************************
     * A job-level processing: <b>This is the main method to be
     * overriden by a service provider!</b>. Here all the business
     * logic belongs. <p>
     *
     * This method is called once for each <em>job</em> in a client
     * request. A {@link org.biomoby.shared.parser.MobyJob job} is a
     * BioMoby query (in a client request), or a result of one query
     * (in a service response). There can be more queries (jobs) in
     * one network request to a BioMoby service. If a network request
     * contains more jobs, also the corresponding service response
     * must contain the same number of jobs. <p>
     *
     * @param request contain data coming from one client's job
     *
     * @param response is an empty object (except its name that is
     * already filled in - because it must correspond with the same
     * name in the 'request'); this method should fill it with a
     * response
     *
     * @param outputContext is a package that will be, at the end,
     * delivered to the client; it is here not to be filled - that is
     * taken care of by some other methods - but you may use it to see
     * how other (previous) jobs have been made, and also to add
     * things to the package envelope (e.g. service notes)
     *
     * @throws MobyException if a complete processing failed; after
     * this exception the client will not get any data back (only an
     * error message). If you wish just to indicate that only this
     * particular job failed you have to add an exception to the
     * <tt>outputContext</tt> - see {@link
     * MobyPackage#addException(ServiceException,MobyJob)}. <p>
     *************************************************************************/
    abstract public void processIt (MobyJob request,
				    MobyJob response,
				    MobyPackage outputContext)
	throws MobyException;

}

