/* * * * Copyright 1990-2009 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.j2me.content; import java.io.IOException; import javax.microedition.content.ContentHandler; import javax.microedition.content.ContentHandlerException; import javax.microedition.content.Invocation; import javax.microedition.content.Registry; import javax.microedition.io.Connection; import javax.microedition.io.ConnectionNotFoundException; /** * Implementation of Invocation class. * <p> * This class MUST NOT have any public methods that are not also * public in Invocation (the superclass). The sensitive methods * of the class MUST be package private. */ public final class InvocationImpl { public static Tunnel tunnel = null; public static final int UNDEFINED_TID = 0; /** * 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 = UNDEFINED_TID; ApplicationID destinationApp; // /** 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; ApplicationID invokingApp; // /** 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() { if( AppProxy.LOGGER != null ) AppProxy.LOGGER.println( getClass().getName() + " is created" ); status = Invocation.INIT; responseRequired = true; arguments = ContentHandlerImpl.ZERO_STRINGS; data = ZERO_BYTES; destinationApp = AppProxy.createAppID(); invokingApp = AppProxy.createAppID(); } /** * 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) { if( AppProxy.LOGGER != null ) AppProxy.LOGGER.println( getClass().getName() + ".setData " + 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) { if( AppProxy.LOGGER != null ) AppProxy.LOGGER.println( getClass().getName() + ".setURL " + 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) { if( AppProxy.LOGGER != null ) AppProxy.LOGGER.println( getClass().getName() + ".setType " + 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) { if( AppProxy.LOGGER != null ) AppProxy.LOGGER.println( getClass().getName() + ".setAction " + 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( AppProxy.LOGGER != null ) AppProxy.LOGGER.println( getClass().getName() + ".setResponseRequired " + 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) { if( AppProxy.LOGGER != null ) AppProxy.LOGGER.println( getClass().getName() + ".setID " + 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 { if( AppProxy.LOGGER != null ) AppProxy.LOGGER.println( getClass().getName() + ".invoke prev = " + previous + ", handler = '" + handler + "'" ); /* * Check all of the arguments for validity. */ for (int i = 0; i < arguments.length; i++) { if (arguments[i] == null) { throw new IllegalArgumentException("argument[" + i + "] 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); destinationApp = handler.applicationID.duplicate(); // Queue this Invocation InvocationStore.put(this); setStatus(Invocation.WAITING); // Set the status of the previous invocation if (previous != null) { previous.setStatus(Invocation.HOLD); } return InvocationStoreProxy.launchInvocationTarget( this ) == InvocationStoreProxy.LIT_MIDLET_START_FAILED; } /** * 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( AppProxy.LOGGER != null ){ AppProxy.LOGGER.println( "finish( " + status + "), Invocation " + this); } switch( status ){ case Invocation.OK: case Invocation.CANCELLED: case Invocation.INITIATED: break; default: throw new IllegalArgumentException(); } setStatus(status); if (getResponseRequired()) { if (destinationApp.isNative()) { // 'native to java' invocation is finished return AppProxy.platformFinish(tid); } return InvocationStoreProxy.launchInvocationTarget( this ) == InvocationStoreProxy.LIT_MIDLET_START_FAILED; } return false; } /** * Creates and opens a Connection to the content accessible 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 { url.length(); // null check ContentReader reader = new ContentReader(url, username, password); return reader.open(timeouts); } /** * 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; switch( this.status ){ case Invocation.OK: case Invocation.CANCELLED: case Invocation.ERROR: case Invocation.INITIATED: /* * If a response is required, switch the target * application; if not then discard the Invocation. */ if (!responseRequired){ if( tid != UNDEFINED_TID ){ InvocationStore.dispose(tid); tid = UNDEFINED_TID; } return; } /* Swap the source and target applications */ ApplicationID tmpApp = invokingApp; invokingApp = destinationApp; destinationApp = tmpApp; InvocationStore.update(this); /* Unmark the response it is "new" to the target */ InvocationStore.resetFlags(tid); break; default: InvocationStore.update(this); break; } } /** * 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, SecurityException { if (type != null) { return type; } if (url != null) { ContentReader reader = new ContentReader(url, username, password); String type = reader.findType(); if (type != null) { this.type = type; return type; } } else if (data.length > 0) { // TODO: try to determine type by data signature } throw new ContentHandlerException( "Can not determine the content type", ContentHandlerException.TYPE_UNKNOWN); } /** * 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 identifying 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.LOGGER != null) { StringBuffer sb = new StringBuffer(200); sb.append("tid: "); sb.append(tid); sb.append( " status = "); String s = "{" + status + "}"; switch( status ){ case Invocation.ACTIVE: s = "ACTIVE"; break; case Invocation.WAITING: s = "WAITING"; break; case Invocation.ERROR: s = "ERROR"; break; case Invocation.OK: s = "OK"; break; case Invocation.CANCELLED: s = "CANCELLED"; break; case Invocation.HOLD: s = "HOLD"; break; case Invocation.INIT: s = "INIT"; break; case Invocation.INITIATED: s = "INITIATED"; break; } sb.append(s + ", handlerID = '" + ID + "'" ); sb.append("\n type: "); sb.append(getType()); sb.append(", url: "); sb.append(getURL()); sb.append(", respReq: "); sb.append(getResponseRequired()); sb.append("\n invokee: "); sb.append(destinationApp); sb.append(", invoker: "); sb.append(invokingApp); return sb.toString(); } return super.toString(); } public Invocation wrap() { if( invocation == null ) invocation = tunnel.newInvocation(this); return invocation; } }