FileDocCategorySizeDatePackage
InvocationImpl.javaAPI DocphoneME MR2 API (J2ME)32117Wed May 02 18:00:44 BST 2007com.sun.midp.content

InvocationImpl.java

/*
 *
 *
 * Copyright  1990-2007 Sun Microsystems, Inc. All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 only, as published by the Free Software Foundation.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License version 2 for more details (a copy is
 * included at /legal/license.txt).
 * 
 * You should have received a copy of the GNU General Public License
 * version 2 along with this work; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 * 
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
 * Clara, CA 95054 or visit www.sun.com if you need additional
 * information or have any questions.
 */

package com.sun.midp.content;

import javax.microedition.content.Registry;
import javax.microedition.content.Invocation;
import javax.microedition.content.ContentHandler;
import javax.microedition.content.ContentHandlerException;

import java.io.IOException;
import java.io.DataInputStream;
import java.io.DataOutputStream;

import javax.microedition.io.Connection;
import javax.microedition.io.Connector;
import javax.microedition.io.HttpConnection;

import com.sun.midp.midlet.MIDletSuite;

/**
 * Implementation of Invocation class.
 * <p>
 * This class MUST NOT have any public methods that are not also
 * public in Invocation (the superclass).  The sensistive methods
 * of the class MUST be package private.
 */
public final class InvocationImpl {
    /**
     * The Invocation delegating to this instance.
     * This field is public to Invocation can set it.
     * This allows the implementation to pass a InvocationImpl to
     * back to the Invocation class and it can wrap it in an Invocation
     * before passing it to the application.
     */
    public Invocation invocation;

    /**
     * The URL of the content; may be <code>null</code>.
     * URLs of up to and including 256 characters in length MUST be
     * supported. A URL with a length of zero is treated as
     * <code>null</code> and is ignored..
     */
    String url;

    /** The content type; may be <code>null</code>. */
    String type;

    /** The content handler ID; may be <code>null</code> */
    String ID;

    /** The action to perform on the content; may be <code>null</code> */
    String action;

    /** The array of arguments; may be <code>null</code> */
    String[] arguments;

    /** The length (returned by get0) of the argument array. */
    int argsLen;

    /** The data array; may be <code>null</code>. */
    byte[] data;

    /** The length (returned by get0) needed for the data array. */
    int dataLen;

    /**
     * Set to <code>true</code> if the invoker must be notified of
     * completion.
     */
    boolean responseRequired;

    /** The username in case it is needed for authentication. */
    String username;

    /** The password in case it is needed for authentication. */
    String password;

    /** Transaction Identifier. */
    int tid;

    /** The MIDlet suite that should handle this Invocation. */
    int suiteId;

    /** The classname of the MIDlet to deliver to. */
    String classname;

    /**
     * The status of the request; one of
     * {@link Invocation#ACTIVE},
     * {@link Invocation#WAITING},
     * {@link Invocation#ERROR},
     * {@link Invocation#OK}, or
     * {@link Invocation#CANCELLED}.
     */
    int status;

    /** The authority that authenticated this Invocation. */
    String invokingAuthority;

    /** The ID that authenticated this Invocation. */
    String invokingID;

    /** The MIDlet suite of the invoking application. */
    int invokingSuiteId;

    /** The classname in the invoking MIDlet suite for the response. */
    String invokingClassname;

    /** The application name of the invoking MIDlet suite. */
    String invokingAppName;

    /** The previous invocation, if any. */
    InvocationImpl previous;

    /** The tid of the previous Invocation, if any. */
    int previousTid;

    /** A zero length array of strings to re-use when needed.  */
    private static final byte[] ZERO_BYTES = new byte[0];

    /**
     * The DISPOSE status is used with {@link #setStatus setStatus}
     * to discard the native Invocation. It must not overlap with
     * Status values defined in the Invocation class and must match
     * STATUS_DISPOSE defined in invocStore.c.
     */
    static final int DISPOSE = 100;

    /**
     * Create a fresh InvocationImpl.
     */
    InvocationImpl() {
        status = Invocation.INIT;
        responseRequired = true;
        arguments = ContentHandlerImpl.ZERO_STRINGS;
        data = ZERO_BYTES;
    }

    /**
     * Create a fresh InvocationImpl that is being delegated to byte
     * an Invocation instance created by an application.
     * @param invocation the Invocation delegating to this implementation
     */
    public InvocationImpl(Invocation invocation) {
        this();
        this.invocation = invocation;
    }

    /**
     * Sets the argument list to a new array of Strings.  The arguments
     * are used by the application to communicate to the content
     * handler and return results from the content handler.
     * The values of the arguments are not checked when they are set.
     * Instead, they are checked during
     * {@link Registry#invoke Registry.invoke} to
     * check that none of the values are <code>null</code>.
     * @param args the String array; may be <code>null</code>.
     * A <code>null</code>
     * argument is treated the same as a zero-length array
     * @see #getArgs
     */
    public void setArgs(String[] args) {
        this.arguments =
            (args == null) ? ContentHandlerImpl.ZERO_STRINGS : args;
    }

    /**
     * Gets the argument list as an array of Strings. These values
     * are passed to the content handler and are returned from
     * the content handler.
     * The array is not copied; modifications to array elements
     * will be visible.
     * @return the arguments array, which MUST NOT be <code>null</code>
     * @see #setArgs
     */
    public String[] getArgs() {
        return arguments;
    }

    /**
     * Sets the data used for the Invocation.  The data
     * is used by the application to communicate to the content
     * handler and return data from the content handler.
     * @param data the byte data array; may be <code>null</code>.
     * A <code>null</code> is treated the same as a zero-length array
     * @see #getData
     */
    public void setData(byte[] data) {
        this.data = (data == null) ? ZERO_BYTES : data;
    }

    /**
     * Gets the data for the Invocation. The data
     * is passed to the content handler.
     * The content handler may modify and return the data
     * if it returns a response.
     * The array is not copied; modifications to array elements
     * will be visible.
     * @return the data array, which MUST NOT be <code>null</code>
     * @see #setData
     */
    public byte[] getData() {
        return data;
    }

    /**
     * Gets the URL for the invocation.
     * The URL must be equal to the value set with {@link #setURL setURL}.
     * @return the URL or <code>null</code> if it has not been set
     * @see #setURL
     */
    public String getURL() {
        return url;
    }

    /**
     * Sets the URL for the invocation.
     * @param url the URL to be set; may be <code>null</code>
     * @see #getURL
     */
    public void setURL(String url) {
        this.url = url;
    }


    /**
     * Gets the content type for the Invocation.
     * @return the content type or <code>null</code> if it has not been set
     * @see #setType
     * @see #findType
     */
    public String getType() {
        return type;
    }

    /**
     * Sets the type for the Invocation.
     * @param type the type to be set for the content; may be <code>null</code>
     * @see #getType
     */
    public void setType(String type) {
        this.type = type;
    }


    /**
     * Gets the action to be performed on the content.
     * @return the content action or <code>null</code> if it has not been set
     * @see #setAction
     */
    public String getAction() {
        return action;
    }

    /**
     * Sets the action to be performed on the content.
     * @param action the action to be performed on the content;
     *  may be <code>null</code>
     * @see #getAction
     */
    public void setAction(String action) {
        this.action = action;
    }


    /**
     * Gets the <code>responseRequired</code> mode for
     * this Invocation.
     * If <code>true</code>, then the invoking application requires a
     * response to the Invocation.
     * @return the current value of the <code>responseRequired</code>
     * mode. If
     * <code>true</code>, then a response must be returned to the
     * invoking application.
     * @see #setResponseRequired
     */
    public boolean getResponseRequired() {
        return responseRequired;
    }

    /**
     * Sets the <code>responseRequired</code> mode for
     * this Invocation.
     * If <code>true</code>, then the invoking application requires a
     * response to the Invocation.
     * The value in the request can be changed only if the status is
     * <code>INIT</code>.
     * @param responseRequired
     * <code>true</code> to require a response,
     * <code>false</code> otherwise
     * @exception IllegalStateException is thrown if the status is not
     *        <code>INIT</code>
     * @see #getResponseRequired
     */
    public void setResponseRequired(boolean responseRequired) {
        if (getStatus() != Invocation.INIT) {
            throw new IllegalStateException();
        }
        this.responseRequired = responseRequired;
    }

    /**
     * Gets the content handler ID for this Invocation.
     * @see Registry#forID
     * @return the ID of the ContentHandler; may be
     * <code>null</code>
     * @see #setID
     */
    public String getID() {
        return ID;
    }

    /**
     * Sets the ID of the content handler for this Invocation.
     * @param ID of the content handler; may be <code>null</code>
     * @see #getID
     */
    public void setID(String ID) {
        this.ID = ID;
    }


    /**
     * Checks this Invocation and uses the ID, type, URL, and action
     * find a matching ContentHandler and queue this request to it.
     * The actual launching of the application is done in the Registry.
     *
     * If the <code>previous</code> Invocation is <code>null</code> then
     * a new transaction is created; otherwise, this
     * Invocation will use the same transaction as the
     * <code>previous</code> Invocation.
     * <p>
     * The status of this Invocation must be <code>INIT</code>.
     * If there is a previous Invocation, that Invocation must
     * have a status of <code>ACTIVE</code>.
     * <p>
     * Candidate content handlers are found as described in
     * {@link Registry#findHandler Registry.findHandler}.
     * If any handlers are
     * found, one is arbitrarily selected for this Invocation.
     * <p>
     * The status of this Invocation is set to <code>WAITING</code>.
     * If there is a non-null previous Invocation,
     * its status is set to <code>HOLD</code>.
     * A copy of the Invocation is made, the status is set to
     * <code>ACTIVE</code> and then queued to the
     * target content handler.
     * If the invoked content handler is not running, it must be started
     * as described in <a href="#execution">Invocation Processing</a>.
     *
     * <p>
     * The calling thread blocks while the content handler is being determined.
     * If a network access is needed there may be an associated delay.
     *
     * @param previous a previous Invocation for this Invocation;
     *  may be <code>null</code>
     * @param handler the ContentHandlerImpl that is the target
     *
     * @return <code>true</code> if the application MUST first
     *  voluntarily exit before the content handler can be started;
     *  <code>false</code> otherwise
     *
     * @exception IllegalArgumentException is thrown if:
     *  <ul>
     *     <li> the <code>classname</code> does not implement the
     *          lifecycle required by the Java runtime, or </li>
     *     <li> the ID, type, and URL are all <code>null</code>, or </li>
     *     <li> the argument array contains any <code>null</code>
     *          references</li>
     *  </ul>
     * @exception IOException is thrown if the URL to be accessed is
     *   not available
     * @exception ContentHandlerException is thrown with a reason of:
     *  <ul>
     *      <li><code>TYPE_UNKNOWN</code> if the type
     *          is not set and cannot be determined from the URL, or</li>
     *      <li><code>NO_REGISTERED_HANDLER</code> if
     *          there is no registered content handler for the type or
     *          ID</li>
     * </ul>
     * @exception IllegalStateException is thrown if the status of this
     *     Invocation is not <code>INIT</code> or if the status of the previous
     *     Invocation, if any, is not <code>ACTIVE</code>
     * @exception SecurityException if an invoke operation is not permitted
     */
    boolean invoke(InvocationImpl previous, ContentHandlerImpl handler)
        throws IllegalArgumentException, IOException,
               ContentHandlerException
    {
        /*
         * Check all of the arguments for validity.
         */
        for (int i = 0; i < arguments.length; i++) {
            if (arguments[i] == null) {
                throw new IllegalArgumentException("argument is null");
            }
        }

        if (previous != null) {
            this.previous = previous;
            this.previousTid = previous.tid;
        }

        // Fill information about the target content handler.
        setStatus(Invocation.INIT);
        setID(handler.ID);
        suiteId = handler.storageId;
        classname = handler.classname;

        // Queue this Invocation
        InvocationStore.put(this);

        // Launch the target application if necessary.
        boolean shouldExit = false;

        try {
            if (handler.registrationMethod ==
                                ContentHandlerImpl.REGISTERED_NATIVE) {
                shouldExit = RegistryStore.launch(handler);
                finish(Invocation.INITIATED);
            } else {
                try {
                    AppProxy appl = AppProxy.getCurrent().
                        forApp(suiteId, classname);
                    shouldExit = appl.launch(handler.getAppName());
                    // Set the status of this Invocation to WAITING
                    status = Invocation.WAITING;
                } catch (ClassNotFoundException cnfe) {
                    throw new ContentHandlerException(
                                "Invoked handler has been removed",
                                ContentHandlerException.NO_REGISTERED_HANDLER);
                }
            }
        } catch (ContentHandlerException che) {
            // remove this invocation from the queue before throwing
            setStatus(DISPOSE);
            throw che;
        }

        // Set the status of the previous invocation
        if (previous != null) {
            previous.setStatus(Invocation.HOLD);
        }
        return shouldExit;
    }

    /**
     * Execute the User Environment Policy to select the next
     * application to run.
     * Check for and select the next MIDlet suite to run
     * based on the contents of the Invocation queue.
     *
     * From the most recently queued Invocation that is an Invocation
     * in INIT.
     * If none, find the most recently queued Invocation that is
     * a response.
     */
    static void invokeNext() {
        InvocationImpl invoc = null;
        int tid;

        // Look for a recently queued Invocation to launch
        tid = 0;
        while ((invoc = InvocationStore.getByTid(tid, -1)) != null) {
            if (invoc.status == Invocation.INIT) {
                AppProxy.getCurrent().logInfo("invokeNext has request: " +
                                              invoc);
                if (invoc.suiteId != MIDletSuite.UNUSED_SUITE_ID &&
                        invoc.classname != null) {
                    try {
                        AppProxy appl = AppProxy.getCurrent().
                            forApp(invoc.suiteId, invoc.classname);
                        appl.launch("Application");
                        return;
                    } catch (ClassNotFoundException cnfe) {
                        // Ignore
                    }
                } else if (invoc.ID != null) {
                    // check if it is native handler
                    ContentHandlerImpl handler = RegistryStore.getHandler(null, 
                                        invoc.ID, RegistryStore.SEARCH_EXACT);
                    if (handler != null && 
                        handler.registrationMethod == 
                                        ContentHandlerImpl.REGISTERED_NATIVE) {
                        try {
                            RegistryStore.launch(handler);
                            invoc.finish(Invocation.INITIATED);
                            continue;
                        } catch (ContentHandlerException che) {
                            // Ignore
                        }
                    }
                }

                // can't process this invocation - remove it
                invoc.setStatus(DISPOSE);
            } else if (invoc.status == Invocation.ERROR) {
                AppProxy.getCurrent().logInfo("invokeNext has response: " +
                                              invoc);
                if (invoc.suiteId != MIDletSuite.UNUSED_SUITE_ID &&
                        invoc.classname != null) {
                    try {
                        AppProxy appl = AppProxy.getCurrent().
                            forApp(invoc.suiteId, invoc.classname);
                        appl.launch("Application");
                        return;
                    } catch (ClassNotFoundException cnfe) {
                        // Ignore
                    }
                }

                // can't process this invocation - remove it
                invoc.setStatus(DISPOSE);
            }
            tid = invoc.tid;
        }
    }

    /**
     * Finish this Invocation and set the status for the response.
     *
     * @param status the new status of the Invocation. This MUST be either
     *         <code>OK</code> or <code>CANCELLED</code>.
     *
     * @return <code>true</code> if the MIDlet suite MUST
     *   voluntarily exit before the response can be returned to the
     *   invoking application
     *
     * @exception IllegalArgumentException if the new
     *   <code>status</code> of the Invocation
     *    is not <code>OK</code> or <code>CANCELLED</code>
     */
    boolean finish(int status) {
        if (status != Invocation.OK &&
            status != Invocation.CANCELLED &&
            status != Invocation.INITIATED) {
            throw new IllegalArgumentException();
        }

        /*
         * If a response is required by the invoking application,
         * the native code requeues it.
         * The application mutable parameters are saved to the
         * native invocation.
         */
        if (getResponseRequired()) {
            if (tid != 0) {
                InvocationStore.setParams(this);
            }
        }

        setStatus(status);

        if (getResponseRequired()) {
            // Launch the target application if necessary.
            try {
                AppProxy appl = AppProxy.getCurrent().
                    forApp(suiteId, classname);
                return appl.launch(invokingAppName);
            } catch (ClassNotFoundException cnfe) {
                AppProxy.getCurrent().logInfo(
                        "Unable to launch invoking application "
                        + invokingAppName + "; classname = "
                        + classname + " from suite = "
                        + suiteId);
            }
        }
        return false;
    }

    /**
     * Creates and opens a Connection to the content accessable by
     * using the URL. This method is
     * equivalent to
     * {@link javax.microedition.io.Connector#open Connector.open}
     * with the URL provided.
     * The application should use this method to access the
     * content of the URL
     * so that any type or content information cached by the
     * implementation can be fully utilized. The content is opened
     * in read only mode.
     *
     * @param timeouts         a flag to indicate that the caller
     *                         wants timeout exceptions
     * @return                 a Connection object
     *
     * @exception ConnectionNotFoundException is thrown if:
     *   <ul>
     *      <li>there is no URL, or</li>
     *      <li>the target URL cannot be found, or</li>
     *      <li>the requested protocol type is not supported</li>
     *   </ul>
     * @exception IOException  if some other kind of I/O error occurs
     * @exception SecurityException  may be thrown if access to the
     *   protocol handler is prohibited
     */
    public Connection open(boolean timeouts)
        throws IOException
    {
        Connection conn = Connector.open(getURL(), Connector.READ, timeouts);
        return conn;
    }

    /**
     * Provide the credentials needed to access the content.
     * @param username the username; may be <code>null</code>
     * @param password the password for the username;
     *   may be <code>null</code>
     */
    public void setCredentials(String username, char[] password) {
        this.username = username;
        this.password = (password == null) ? null : new String(password);

    }

    /**
     * Returns the status of this Invocation, which can be
     * <code>INIT</code>, <code>WAITING</code>, <code>HOLD</code>,
     * <code>ACTIVE</code>, <code>OK</code>,
     * <code>CANCELLED</code>, or <code>ERROR</code>.
     * The application uses the status to determine how
     * to process an Invocation returned from
     * <code>getInvocation</code>.
     *
     * @see javax.microedition.content.Registry#invoke
     *
     * @return the current status of this Invocation
     */
    public int getStatus() {
        return status;
    }

    /**
     * Set the status of this InvocationImpl.
     * If the invocation is still active in the native code
     * set the status in native also.
     * @param status the new status
     */
    void setStatus(int status) {
        this.status = status;
        if (tid != 0) {
            InvocationStore.setStatus(this);
        }
    }

    /**
     * Finds the type of the content in this Invocation.
     * If the <tt>getType</tt> method return value is
     * <code>non-null</code>, then the type is returned.
     * <p>
     * If the type is <code>null</code> and the URL is non-<code>null</code>,
     * then the content type will be found by accessing the content
     * through the URL.
     * When found, the type is set as if the <code>setType</code> method
     * was called;  subsequent calls to
     * {@link #getType getType} and {@link #findType findType}
     * will return the type.
     * If an exception is thrown, the <code>getType</code> method will
     * return <code>null</code>.
     * <p>
     * The calling thread blocks while the type is being determined.
     * If a network access is needed there may be an associated delay.
     *
     * @return the <code>non-null</code> content type
     * @exception IOException if access to the content fails
     *
     * @exception ContentHandlerException is thrown with a reason of
     * {@link ContentHandlerException#TYPE_UNKNOWN}
     *  if the type is <code>null</code> and cannot be found from the
     *  content either because the URL is <code>null</code> or the type is
     *  not available from the content
     * @exception IllegalArgumentException if the content is accessed via
     *  the URL and the URL is invalid
     * @exception SecurityException is thrown if access to the content
     *  is required and is not permitted
     */
    public String findType()
        throws IOException, ContentHandlerException
    {
        if (type != null) {
            return type;
        }
        if (url == null) {
            // No URL to examine, leave the type null
            throw new ContentHandlerException(
                                "URL is null",
                                ContentHandlerException.TYPE_UNKNOWN);
        }

        // Open a connection to the content.
        Connection conn = null;
        int rc = 0;
        try {
            while (true) {
                // Loop to enable redirects.
                conn = Connector.open(url);
                if (conn instanceof HttpConnection) {
                    HttpConnection httpc = (HttpConnection)conn;
                    httpc.setRequestMethod(httpc.HEAD);

                    // Get the response code
                    rc = httpc.getResponseCode();
                    if (rc == HttpConnection.HTTP_OK) {
                        type = httpc.getType();
                        if (type != null) {
                            // Check for and remove any parameters (rfc2616)
                            int ndx = type.indexOf(';');
                            if (ndx >= 0) {
                                type = type.substring(0, ndx);
                            }
                            type = type.trim();
                        }
                        if (type == null || type.length() == 0) {
                            type = null;
                            throw new ContentHandlerException(
                                "unable to determine type",
                                ContentHandlerException.TYPE_UNKNOWN);
                        }
                        break;
                    } else if (rc == HttpConnection.HTTP_TEMP_REDIRECT ||
                               rc == HttpConnection.HTTP_MOVED_TEMP ||
                               rc == HttpConnection.HTTP_MOVED_PERM) {
                        // Get the new location and close the connection
                        url = httpc.getHeaderField("location");

                        conn.close();
                        conn = null;
                        continue; // restart with the new url
                    } else {
                        throw new IOException("http status: " + rc);
                    }
                } else {
                    // Not HTTP, this isn't going to work
                    // TBD: Check suffixes
                    throw new ContentHandlerException(
                                "URL scheme not supported",
                                ContentHandlerException.TYPE_UNKNOWN);
                }
            }

        } finally {
            if (conn != null) {
                try {
                    conn.close();
                } catch (Exception ex) {
                }
            }
        }
        return type;
    }


    /**
     * Returns the previous Invocation linked to this
     * Invocation by this application's previous call to
     * {@link Registry#invoke(Invocation invoc, Invocation previous)}.
     *
     * @return the previous Invocation, if any, set when this
     *        Invocation was invoked;
     *        <code>null</code> is returned if the Invocation was not
     *  invoked with a previous Invocation.
     */
    public InvocationImpl getPrevious() {
        return previous;
    }

    /**
     * Gets the authority, if any, used to authenticate the
     * application that invoked this request.
     * This value MUST be <code>null</code> unless the device has been
     * able to authenticate this application.
     * If <code>non-null</code>, it is the string identifiying the
     * authority.  For example,
     * if the application was a signed MIDlet, then this is the
     * "subject" of the certificate used to sign the application.
     *
     * <p>The format of the authority for X.509 certificates is defined
     * by the MIDP Printable Representation of X.509 Distinguished
     * Names as defined in class
     *
     * <code>javax.microedition.pki.Certificate</code>. </p>
     * @return the authority used to authenticate this application
     * or <code>null</code> otherwise
     *
     * @exception IllegalStateException if the current status is not
     * <code>ACTIVE</code> or <code>HOLD</code>
     *
     * @see ContentHandler#getAuthority
     */
    public String getInvokingAuthority() {
        if (status != Invocation.ACTIVE &&
            status != Invocation.HOLD) {
            return null;
        }
        return invokingAuthority;
    }

    /**
     * Get the user-friendly name of the application that invoked
     * the content handler. This information is available only if the status is
     * <code>ACTIVE</code> or <code>HOLD</code>.
     *
     * This information has been authenticated only if
     * <code>getInvokingAuthority</code> is non-null.
     *
     * @return the application's name if status is <code>ACTIVE</code>
     * or <code>HOLD</code>; <code>null</code> otherwise
     *
     * @see ContentHandler#getID
     */
    public String getInvokingAppName() {
        if (status != Invocation.ACTIVE &&
            status != Invocation.HOLD) {
            return null;
        }
        return invokingAppName;
    }

    /**
     * Gets the ID of the application that invoked the content
     * handler. This information is available only if the status is
     * <code>ACTIVE</code> or <code>HOLD</code>.
     *
     * This information has been authenticated only if
     * <code>getInvokingAuthority</code> is non-null.
     *
     * @return the application's ID if status is <code>ACTIVE</code>
     * or <code>HOLD</code>; <code>null</code> otherwise
     *
     * @exception IllegalStateException if the current status is not
     * <code>ACTIVE</code> or <code>HOLD</code>
     *
     * @see ContentHandler#getID
     */
    public String getInvokingID() {
        if (status != Invocation.ACTIVE &&
            status != Invocation.HOLD) {
            return null;
        }
        return invokingID;
    }

    /**
     * Return a printable form of InvocationImpl.
     * Disabled if not logging
     * @return a String containing a printable form
     */
    public String toString() {
        if (AppProxy.LOG_INFO) {
            StringBuffer sb = new StringBuffer(200);
            sb.append("tid: ");         sb.append(tid);
            sb.append(" status: ");     sb.append(status);
            //        sb.append("  suiteId: ");   sb.append(suiteId);
            sb.append(", type: ");      sb.append(getType());
            sb.append(", url: ");       sb.append(getURL());
            sb.append(", respReq: ");   sb.append(getResponseRequired());
            //        sb.append(", args: ");      sb.append(getArgs());
            //        sb.append(", prevTid: ");   sb.append(previousTid);
            //        sb.append(", previous: ");
            //        sb.append((previous == null) ? "null" : "non-null");
            //        sb.append("_suiteId: ");    sb.append(invokingSuiteId);
            sb.append("\n   invokee: "); sb.append(classname);
            sb.append(", invoker: "); sb.append(invokingClassname);
            //        sb.append(", _authority: "); sb.append(invokingAuthority);
            //        sb.append(", _ID: ");       sb.append(invokingID);
            return sb.toString();
        } else {
            return super.toString();
        }
    }
}