/*
*
*
* 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 com.sun.midp.io.HttpUrl;
import com.sun.midp.security.SecurityInitializer;
import com.sun.midp.security.SecurityToken;
import com.sun.midp.security.ImplicitlyTrustedClass;
import com.sun.midp.midlet.MIDletSuite;
import java.io.IOException;
import java.util.Hashtable;
import java.util.Vector;
import javax.microedition.content.*;
import javax.microedition.io.Connector;
/**
* Implementation of Content Handler registry. It maintains
* the set of currently registered handlers and updates to
* the file that holds the permanent set.
* The RegistryImpl class maintains an array of the current
* registrations that is initialized on first use.
*/
public final class RegistryImpl {
/**
* Inner class to request security token from SecurityInitializer.
* SecurityInitializer should be able to check this inner class name.
*/
static private class SecurityTrusted
implements ImplicitlyTrustedClass {};
/** This class has a different security domain than the MIDlet suite */
private static SecurityToken classSecurityToken =
SecurityInitializer.requestToken(new SecurityTrusted());
static {
AppProxy.setSecurityToken(classSecurityToken);
RegistryStore.setSecurityToken(classSecurityToken);
}
/** The set of active Invocations. */
private final Hashtable activeInvocations = new Hashtable();
/** The set of active RegistryImpls. */
private static Hashtable registries = new Hashtable();
/** The mutex used to avoid corruption between threads. */
private static final Object mutex = new Object();
/** Implementation of the listener. */
private ResponseListenerImpl listenerImpl;
/** The ContentHandlerImpl that matches the classname of this Registry. */
private ContentHandlerImpl handlerImpl;
/** The Registry that is delegating to this RegistryImpl. */
private Registry registry;
/** The AppProxy for this registry. */
final AppProxy application;
/** Count of responses received. */
int responseCalls;
/**
* Gets the RegistryImpl for the application class.
* The SecurityToken is needed to call from the public API package.
* The application is identified by the classname that implements
* the lifecycle of the Java runtime environment.
* The classname must be the name of a registered application class
* or a registered content handler.
* <p>
* For a MIDP implementation,
* application classes must be registered with the
* <code>MIDlet-<n></code> attribute; content handlers are
* registered with the <code>MicroEdition-Handler-<n></code>
* attribute or the {@link #register register} method.
* <p>
* When the RegistryImpl is created (the first time) all of the
* existing Invocations are marked. They will be subject to
* {@link #cleanup} when the MIDlet exits.
*
* @param classname the application class
* @param token the security token needed to control access to the impl
*
* @return a RegistryImpl instance providing access to content handler
* registrations and invocations; MUST NOT be <code>null</code>
* @exception ContentHandlerException is thrown with a reason of
* <code>NO_REGISTERED_HANDLER</code> if there is no
* content handler registered for the classname in the current
* application
* @exception NullPointerException if <code>classname</code> is
* <code>null</code>
*/
public static RegistryImpl getRegistryImpl(String classname,
Object token)
throws ContentHandlerException
{
AppProxy.checkAPIPermission(token);
return getRegistryImpl(classname);
}
/**
* Gets the RegistryImpl for the application class.
* The application is identified by the classname that implements
* the lifecycle of the Java runtime environment.
* The classname must be the name of a registered application class
* or a registered content handler.
* <p>
* For a MIDP implementation,
* application classes must be registered with the
* <code>MIDlet-<n></code> attribute; content handlers are
* registered with the <code>MicroEdition-Handler-<n></code>
* attribute or the {@link #register register} method.
* <p>
* When the RegistryImpl is created (the first time) all of the
* existing Invocations are marked. They will be subject to
* {@link #cleanup} when the MIDlet exits.
*
* @param classname the application class
*
* @return a RegistryImpl instance providing access to content handler
* registrations and invocations; MUST NOT be <code>null</code>
* @exception ContentHandlerException is thrown with a reason of
* <code>NO_REGISTERED_HANDLER</code> if there is no
* content handler registered for the classname in the current
* application
* @exception NullPointerException if <code>classname</code> is
* <code>null</code>
*/
static RegistryImpl getRegistryImpl(String classname)
throws ContentHandlerException
{
// Synchronize between competing operations
RegistryImpl curr = null;
synchronized (mutex) {
// Check if class already has a RegistryImpl
curr = (RegistryImpl)registries.get(classname);
if (curr != null) {
// Check that it is still a CH or MIDlet
if (curr.handlerImpl == null &&
(!curr.application.isRegistered())) {
// Classname is not a registered MIDlet or ContentHandler
throw new
ContentHandlerException("not a registered MIDlet",
ContentHandlerException.NO_REGISTERED_HANDLER);
}
return curr;
}
// Create a new instance and insert it into the list
curr = new RegistryImpl(classname);
registries.put(classname, curr);
}
/*
* Unsynchronized, a new RegistryImpl has been created.
* Mark any existing Invocations so that at cleanup the pre-existing
* Invocations can be handled properly.
*/
InvocationStore.setCleanup(curr.application.getStorageId(),
classname, true);
return curr;
}
/**
* RegistryImpl constructor and insert the instance in the
* list of registered applications.
*
* @param classname the application class for this instance
*
* @exception ContentHandlerException if
* the <code>classname</code> is not registered either
* as a MIDlet or a content handler
* @exception NullPointerException if <code>classname</code>
* is <code>null</code>
* @exception SecurityException is thrown if the caller
* does not have the correct permision
*/
private RegistryImpl(String classname)
throws ContentHandlerException
{
try {
// Get the application for the class
application = AppProxy.getCurrent().forClass(classname);
} catch (ClassNotFoundException cnfe) {
throw new ContentHandlerException("not an application",
ContentHandlerException.NO_REGISTERED_HANDLER);
} catch (IllegalArgumentException iae) {
throw new ContentHandlerException("not an application",
ContentHandlerException.NO_REGISTERED_HANDLER);
}
/* Remember the ContentHandlerImpl, if there is one. */
handlerImpl = getServer(application);
if (handlerImpl == null && (!application.isRegistered())) {
// Classname is not a registered MIDlet or ContentHandler; fail
throw new ContentHandlerException("not a registered MIDlet",
ContentHandlerException.NO_REGISTERED_HANDLER);
}
}
/**
* Sets the Registry that is delegating to this instance.
* Settable only once.
* Synchronization is performed in
* {@link javax.microedition.content.Registry#register}.
* @param newRegistry the Registry delegating to this
* @see #getRegistry
*/
public void setRegistry(Registry newRegistry) {
if (registry == null) {
registry = newRegistry;
}
}
/**
* Gets the Registry that is delegating to this RegistryImpl.
* @return a Registry instance
* @see #setRegistry
*/
public Registry getRegistry() {
return registry;
}
/**
* Cleanup as necessary for this classname, both for ContentHandlerServer
* and the registry.
* Cleanup is required by the fault handling descriptions in
* {@link javax.microedition.content.ContentHandlerServer}.
* <ul>
* <li>
* If an Invocation with a status of <code>ACTIVE</code> is dequeued by
* the content handler, but the handler does not call
* {@link javax.microedition.content.ContentHandlerServer#finish finish}
* or make a request to chain a new Invocation to the ACTIVE
* invocation before the content handler exits, then the AMS MUST
* complete the request with an ERROR status.
* </li>
* <li>
* If the content handler is not running, or exits before processing
* all queued requests or responses, then it MUST be started.
* The content handler is expected to dequeue at least one
* invocation that was queued before it was started.
* If it does not dequeue any pending Invocations, then Invocations
* that were in the queue for the content handler
* before it was started MUST be handled as follows:
* <ul>
* <li>Invocation requests with a status of <code>ACTIVE</code>
* are completed with the <code>ERROR</code> status.</li>
* <li>Invocation responses are discarded.</li>
* <li>Invocations queued after the content handler was started are
* retained and will require it to be restarted.</li>
* </ul>
* </li>
* </ul>
* @param suiteId the MIDletSuite to cleanup after
* @param classname the application class to cleanup
*/
static void cleanup(int suiteId, String classname) {
InvocationImpl invoc = null;
while ((invoc =
InvocationStore.getCleanup(suiteId, classname)) != null) {
invoc.setStatus(Invocation.ERROR);
}
}
/**
* Create and initialize a new ContentHandler server with
* type(s), suffix(es), and action(s), action name(s),
* access restrictions and content handler ID.
* Compute the application name, ID, and version
*
* @param classname the application class name that implements
* this content handler. The value MUST NOT be <code>null</code>
* and MUST implement the lifecycle of the Java runtime
* @param types an array of types to register;
* if <code>null</code> it is treated the same as an empty array
* @param suffixes an array of suffixes to register;
* if <code>null</code> it is treated the same as an empty array
* @param actions an array of actions to register;
* if <code>null</code> it is treated the same as an empty array
* @param actionnames an array of ActionNameMaps to register;
* if <code>null</code> it is treated the same as an empty array
* @param id the content handler ID; if <code>null</code>
* a non-null value MUST be provided by the implementation
* @param accessRestricted the IDs of applications and content
* handlers that are
* allowed visibility and access to this content handler;
* if <code>null</code> then all applications and content
* handlers are allowed access; if <code>non-null</code>, then
* ONLY applications and content handlers with matching IDs are
* allowed access.
* @param appl the AppProxy registering the handler
*
* @return the registered ContentHandler; MUST NOT be <code>null</code>
* @exception NullPointerException if any of the following items is
* <code>null</code>:
* <ul>
* <li>classname</li>
* <li>any types, suffixes, actions, actionnames, or
* accessRestricted array element</li>,
* <li>msuite</li>
* </ul>
*
* @exception IllegalArgumentException can be thrown:
* <ul>
* <li>if any of the <code>types</code>, <code>suffix</code>,
* <code>actions</code>, or <code>accessRestricted</code>
* strings have a length of zero, or </li>
* <li>if the <code>classname</code> does not implement the valid
* lifecycle for the Java Runtime,</li>
* <li>if the sequence of actions in each ActionNameMap
* is not the same as the sequence of <code>actions</code>,
* or </li>
* <li>if the locales of the ActionNameMaps are not unique, or.</li>
* <li>if the length of the <code>accessRestricted</code>
* array is zero.</li>.
* </ul>
*/
static ContentHandlerImpl newHandler(String classname,
String[] types,
String[] suffixes,
String[] actions,
ActionNameMap[] actionnames,
String id,
String[] accessRestricted,
AppProxy appl)
throws IllegalArgumentException
{
// Default the ID if not supplied
if (id == null) {
// Generate a unique ID based on the MIDlet suite
id = appl.getDefaultID();
}
// Create a new ContentHandler instance
ContentHandlerImpl handler =
new ContentHandlerImpl(types, suffixes, actions,
actionnames, id, accessRestricted,
appl.getAuthority());
handler.classname = classname;
handler.storageId = appl.getStorageId();
handler.appname = appl.getApplicationName();
handler.version = appl.getVersion();
return handler;
}
/**
* Registers the application class using content
* type(s), suffix(es), and action(s), action name(s),
* access restrictions and content handler ID.
* <p>
* An application can use this method to replace or update
* its own registrations
* that have the same classname with new information.
* The update occurs atomically; the update to the registry
* either occurs or it does not.
* <p>
* The content handler may request to the following
* items:
* <ul>
* <li>zero or more content types</li>
* <li>zero or more suffixes</li>
* <li>zero or more actions</li>
* <li>zero or more mappings from actions to action names</li>
* <li>zero or more access restrictions</li>
* <li>a optional application ID</li>
* </ul>
*
* <p>
* If no exceptions are thrown, then the type(s), suffix(s), action(s),
* action names, and access restrictions, and ID
* will be registered for the application class.
* <p>
* If an exception is thrown, then the previous registration, if
* any, will not be removed or modified.
*
* @param classname the application class name that implements
* this content handler. The value MUST NOT be <code>null</code>
* and MUST implement the lifecycle of the Java runtime
* @param types an array of types to register;
* if <code>null</code> it is treated the same as an empty array
* @param suffixes an array of suffixes to register;
* if <code>null</code> it is treated the same as an empty array
* @param actions an array of actions to register;
* if <code>null</code> it is treated the same as an empty array
* @param actionnames an array of ActionNameMaps to register;
* if <code>null</code> it is treated the same as an empty array
* @param id the content handler ID; if <code>null</code>
* a non-null value MUST be provided by the implementation
* @param accessRestricted the IDs of applications and content
* handlers that are
* allowed visibility and access to this content handler;
* if <code>null</code> then all applications and content
* handlers are allowed access; if <code>non-null</code>, then
* ONLY applications and content handlers with matching IDs are
* allowed access.
*
* @return the registered ContentHandler; MUST NOT be <code>null</code>
* @exception NullPointerException if any of the following items is
* <code>null</code>:
* <ul>
* <li>classname</li>
* <li>any types, suffixes, actions, actionnames, or
* accessRestricted array element</li>
* </ul>
*
* @exception IllegalArgumentException can be thrown:
* <ul>
* <li>if any of the <code>types</code>, <code>suffix</code>,
* <code>actions</code>, or <code>accessRestricted</code>
* strings have a length of zero, or </li>
* <li>if the <code>classname</code> does not implement the valid
* lifecycle for the Java Runtime,</li>
* <li>if the sequence of actions in each ActionNameMap
* is not the same as the sequence of <code>actions</code>,
* or </li>
* <li>if the locales of the ActionNameMaps are not unique, or.</li>
* <li>if the length of the <code>accessRestricted</code>
* array is zero.</li>.
* </ul>
* @exception ClassNotFoundException if the <code>classname</code>
* is not present
* @exception ContentHandlerException with a error code of
* {@link ContentHandlerException#AMBIGUOUS} if <code>id</code>
* is a prefix of any registered handler or if any registered
* handler ID is a prefix of this ID
* @exception SecurityException if registration
* is not permitted
*/
public ContentHandlerImpl register(String classname,
String[] types,
String[] suffixes,
String[] actions,
ActionNameMap[] actionnames,
String id,
String[] accessRestricted)
throws SecurityException, IllegalArgumentException,
ClassNotFoundException, ContentHandlerException
{
application.checkRegisterPermission("register");
// May throw ClassNotFoundException or IllegalArgumentException
AppProxy appl = application.forClass(classname);
synchronized (mutex) {
// Create a new ContentHandler instance
ContentHandlerImpl handler =
newHandler(classname, types, suffixes, actions,
actionnames, id, accessRestricted, appl);
handler.registrationMethod = ContentHandlerImpl.REGISTERED_DYNAMIC;
ContentHandlerImpl conflict = checkConflicts(handler);
if (conflict != null) {
unregister(classname);
}
RegistryStore.register(handler);
setServer(handler);
if (AppProxy.LOG_INFO) {
appl.logInfo("Register: " + classname +
", id: " + handler.getID());
}
return handler;
}
}
/**
* Sets the ContentHandlerImpl; update any active RegistryImpl.
* Replaces the entry in RegisteredTypes list as well.
*
* @param server the ContentHandlerImpl for this RegistryImpl
* @see javax.microedition.content.ContentHandlerServerImpl
*/
public void setServer(ContentHandlerImpl server) {
synchronized (mutex) {
// Update the RegistryImpl, if any, this is a server for
RegistryImpl impl = (RegistryImpl)registries.get(server.classname);
if (impl != null) {
impl.handlerImpl = server;
}
}
}
/**
* Check for conflicts between a proposed new handler and the existing
* handlers. If the handler is being replaced it will be returned.
* Locate and return any existing handler for the same classname.
*
* @param handler the new content handler
*
* @return a ContentHandlerImpl within the suite that
* need to be removed to register the new ContentHandler
*/
static ContentHandlerImpl checkConflicts(ContentHandlerImpl handler)
throws ContentHandlerException
{
ContentHandlerImpl[] handlers = RegistryStore.findConflicted(handler.ID);
ContentHandlerImpl existing = null;
if (handlers != null) {
switch (handlers.length) {
case 0:
break;
case 1:
if (handler.classname.equals(handlers[0].classname)) {
existing = handlers[0];
break;
}
default:
throw new ContentHandlerException(
"ID would be ambiguous: " + handler.ID,
ContentHandlerException.AMBIGUOUS);
}
}
if (existing == null) {
existing = RegistryStore.getHandler(handler.storageId, handler.classname);
}
return existing;
}
/**
* Gets all of the content types for which there are registered
* handlers.
* After a successful registration, the content handler's type(s),
* if any, will appear in this list.
* <P>
* Only content handlers that this application is
* allowed to access will be included.</p>
*
* @return an array of types; MUST NOT be <code>null</code>
*/
public String[] getTypes() {
return RegistryStore.getValues(getID(), RegistryStore.FIELD_TYPES);
}
/**
* Gets all of the IDs of the registered content handlers.
* <P>
* Only content handlers that this application is
* allowed to access will be included.</p>
* @return an array of content handler IDs;
* MUST NOT be <code>null</code>
*/
public String[] getIDs() {
return RegistryStore.getValues(getID(), RegistryStore.FIELD_ID);
}
/**
* Gets all of the actions of the registered content handlers.
* After a successful registration the content handler's action(s),
* if any, will appear in this list.
* <P>
* Only content handlers that this application is
* allowed to access will be included.</p>
* @return an array of content handler actions;
* MUST NOT be <code>null</code>
*/
public String[] getActions() {
return RegistryStore.getValues(getID(), RegistryStore.FIELD_ACTIONS);
}
/**
* Gets all of the suffixes of the registered content handlers.
* After a successful registration the content handler's suffix(es),
* if any, will appear in this list.
* <P>
* Only content handlers that this application is
* allowed to access will be included.</p>
* @return an array of content handler suffixes;
* MUST NOT be <code>null</code>
*/
public String[] getSuffixes() {
return RegistryStore.getValues(getID(), RegistryStore.FIELD_SUFFIXES);
}
/**
* Removes the content handler registration for the application
* class and any bindings to the content handler name, content
* type(s), suffix(es), action(s), and access restrictions.
*
* @param classname the name of the content handler class
* @return if the content handler was
* successfully removed <code>true</code> is returned,
* <code>false</code> otherwise
* @exception NullPointerException if <code>classname</code> is
* <code>null</code>
*/
public boolean unregister(String classname) {
if (classname == null) {
throw new NullPointerException(
"classname argument can not be null");
}
synchronized (mutex) {
ContentHandlerImpl curr = null;
RegistryImpl reg = (RegistryImpl)registries.get(classname);
if (reg != null) {
curr = reg.getServer();
} else {
try {
curr = RegistryStore.getHandler(application.getStorageId(), classname);
} catch (IllegalArgumentException iae) {
// Empty class name falls down without further processing.
}
}
if (curr != null) {
RegistryStore.unregister(curr.getID());
int suiteId = application.getStorageId();
if (reg != null && classname.equals(curr.classname) &&
suiteId == curr.storageId) {
reg.handlerImpl = null;
}
return true;
}
}
return false;
}
/**
* Checks the Invocation and uses the ID, type, URL, and action,
* if present, to find a matching ContentHandler and queues this
* request to it.
* <p>
* 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 #findHandler findHandler}. If any handlers are
* found, one is selected for this Invocation.
* The choice of content handler is implemention dependent.
* <p>
* If there is a non-null <code>previous</code> 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 invocation the Invocation containing the target ID, type,
* actions, arguments, and responseRequired parameters;
* MUST NOT be <code>null</code>
* @param previous a previous Invocation for this Invocation;
* may be <code>null</code>
*
* @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 ID, type, URL, and action are all
* <code>null</code>, or </li>
* <li> the argument array contains any <code>null</code>
* references</li>
* </ul>
* @exception IOException is thrown if access to the content fails
* @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 that
* matches the requested ID, type, url or actions.
* </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 NullPointerException is thrown if the
* <code>invocation</code> is <code>null</code>
* @exception SecurityException if an invoke operation is not permitted
*/
public boolean invoke(InvocationImpl invocation, InvocationImpl previous)
throws IllegalArgumentException, IOException,
ContentHandlerException
{
synchronized (mutex) {
// Locate the content handler for this Invocation.
ContentHandlerImpl handler =
(ContentHandlerImpl)findHandler(invocation)[0];
// Fill in information about the invoking application
invocation.invokingID = getID();
invocation.invokingSuiteId = application.getStorageId();
invocation.invokingClassname = application.getClassname();
invocation.invokingAuthority = application.getAuthority();
invocation.invokingAppName = application.getApplicationName();
boolean shouldExit = invocation.invoke(previous, handler);
// Remember the invoked invocation for getResponse
insertActive(invocation);
return shouldExit;
}
}
/**
* Reinvokes the Invocation and uses the ID, type, URL, and action
* to find a matching ContentHandler and re-queues this request to
* it. Reinvocation is used to delegate the handling of an active
* Invocation to another content handler.
* The processing of the Invocation instance is complete and the
* status is set to <code>OK</code>. Responses to the
* reinvocation will be queued to the original invoking
* application, if a response is required.
*
* <p>
* Candidate content handlers are found as described in
* {@link #findHandler findHandler}. If any handlers are
* found, one is selected for this Invocation.
* The choice of content handler is implementation dependent.
* <p>
* The status of this Invocation is set to <code>OK</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 application 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 invocation an Invocation containing the target ID, type,
* action, arguments, and responseRequired parameters;
* MUST NOT be <code>null</code>
*
* @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 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 access to the content fails
* @exception ContentHandlerException is thrown with a reason of:
* <code>NO_REGISTERED_HANDLER</code> if
* there is no registered content handler that
* matches the requested ID, type, URL, and action
*
* @exception NullPointerException is thrown if the
* <code>invocation</code> is <code>null</code>
* @exception SecurityException if an invoke operation is not
* permitted or if access to the content is not permitted
*/
public boolean reinvoke(InvocationImpl invocation)
throws IllegalArgumentException, IOException,
ContentHandlerException, SecurityException
{
synchronized (mutex) {
// Locate the content handler for this Invocation.
ContentHandlerImpl handler =
(ContentHandlerImpl)findHandler(invocation)[0];
// Save the TID in case the invoke fails
int tid = invocation.tid;
// The information about the invoking application is already set
boolean shouldExit = invocation.invoke(null, handler);
/*
* Only if the invoke succeeds can the original Invocation be
* discarded.
* Restore the tid so the correct native invoc is disposed.
*/
invocation.tid = tid;
invocation.setStatus(InvocationImpl.DISPOSE);
invocation.setStatus(Invocation.OK);
return shouldExit;
}
}
/**
* Gets the next Invocation response pending for this application.
* The method blocks until an Invocation response is available, but
* not for longer than the timeout period.
* The method can be unblocked with a call to
* {@link #cancelGetResponse}.
* The application can process the Invocation based on
* its status. The status is one of
* <code>OK</code>, <code>CANCELLED</code>, or <code>ERROR</code>.
* <p>
* If the Invocation was invoked with
* {@link #invoke(InvocationImpl invocation, InvocationImpl
* previous)},
* the <code>getPrevious</code> method MUST return the
* previous Invocation.
* If the status of the previous Invocation is <code>HOLD</code>
* then its status is restored to <code>ACTIVE</code>.
*
* <p>
* If the original Invocation instance is reachable, then it
* MUST be updated with the values from the response
* and be returned to the application. If it is not
* reachable, then a new instance is returned from getResponse
* with the response values.
*
* @param wait <code>true</code> if the method
* MUST wait for an Invocation if one is not currently available;
* otherwise <code>false</code>
* @param resp an InvocationImpl to fill in with the response
*
* @exception IllegalArgumentException if the context is not valid
*
* @return the next pending response Invocation or <code>null</code>
* if the timeout expires and no Invocation is available or
* if cancelled with {@link #cancelGetResponse}
* @see #invoke
* @see #cancelGetResponse
*/
public Invocation getResponse(boolean wait, InvocationImpl resp)
{
// Application has tried to get a response; reset cleanup flags on all
if (responseCalls == 0) {
InvocationStore.setCleanup(application.getStorageId(),
application.getClassname(), false);
}
responseCalls++;
// Find a response for this application and context
InvocationImpl invoc =
InvocationStore.getResponse(resp, application.getStorageId(),
application.getClassname(), wait);
if (invoc != null) {
// Keep track of how many responses have been recevied;
/*
* If there was a previous Request/Tid
* find or create the previous Invocation
* and update its state.
*/
InvocationImpl existing = removeActive(invoc);
if (existing != null) {
/*
* Copy mutable fields to the existing Invocation
* Continue with the pre-existing Invocation
*/
existing.ID = invoc.ID;
existing.arguments = invoc.arguments;
existing.data = invoc.data;
existing.url = invoc.url;
existing.type = invoc.type;
existing.action = invoc.action;
existing.status = invoc.status;
invoc = existing;
} else {
// If there is a previousTid then restore the previous
if (invoc.previousTid != 0) {
/*
* There will be a previous Invocation unless the app has
* already finished it. It will have a HOLD status.
*/
invoc.previous =
InvocationStore.getByTid(invoc.previousTid, 0);
}
}
if (invoc.previous != null &&
invoc.previous.status == Invocation.HOLD) {
// Restore ACTIVE status to a previously HELD Invocation
invoc.previous.setStatus(Invocation.ACTIVE);
}
// Make an attempt to gain the foreground
if (invoc.invokingSuiteId != MIDletSuite.UNUSED_SUITE_ID &&
invoc.invokingClassname != null) {
// Strong FG transition requested
application.requestForeground(invoc.invokingSuiteId,
invoc.invokingClassname);
}
return invoc.invocation;
}
return null;
}
/**
* Cancels a pending <code>getResponse</code>.
* This method will force a Thread blocked in a call to the
* <code>getResponse</code> method for the same application
* context to return early.
* If no Thread is blocked; this call has no effect.
*/
public void cancelGetResponse() {
InvocationStore.cancel();
}
/**
* Sets the listener to be notified when a new response is
* available for the application context. The request must
* be retrieved using {@link #getResponse getResponse}.
*
* @param listener the listener to register;
* <code>null</code> to remove the listener.
*/
public void setListener(ResponseListener listener) {
// Create or update the listener implementation
synchronized (this) {
if (listener != null || listenerImpl != null) {
// Create or update the active listener thread
if (listenerImpl == null) {
listenerImpl =
new ResponseListenerImpl(this, listener);
} else {
listenerImpl.setListener(listener);
}
// If the listener thread no longer needed; clear it
if (listener == null) {
listenerImpl = null;
}
}
}
}
/**
* Gets the registered content handlers that could be used for
* this Invocation. Only handlers accessible to the application
* are considered. The values for ID, type, URL, and
* action are used in the following order:
* <ul>
* <li>If the ID is non-null, then the set of candidate
* handlers is determined from the {@link #forID forID}
* method with the parameter <tt>exact</tt> set to false.
* If there is an exact match it MUST be returned as
* the first handler.
* The type and URL are ignored. If there are no handlers that match
* the requested ID then a <tt>ContentHandlerException</tt>
* is thrown.</li>
*
* <li>If the ID and type are <code>null</code> and
* the URL is <code>non-null</code> and
* If the protocol supports typing of content, then
* the type is determined
* as described in {@link Invocation#findType}.
* If the type cannot be determined from the content,
* the type is set to <code>null</code>.</li>
*
* <li>If the ID is null and type is non-null,
* then the set of candidate handlers is determined from the
* {@link #forType forType} method.
* If there are no handlers that match the requested type
* then a <tt>ContentHandlerException</tt> is thrown. </li>
*
* <li>If both the ID and type are <code>null</code> and
* the URL is <code>non-null</code> and
* if the protocol does not support typing of content
* or the type was not available from the content,
* then the set of candidate handlers
* includes any handler with a suffix that matches the
* end of the path component of the URL.
* If there are no handlers that match a registered
* suffix then a <tt>ContentHandlerException</tt> is thrown.</li>
*
* <li>If the ID, type, and URL are all null, the set of candidate
* handlers includes all of the accessible handlers.</li>
*
* <li>If the action is non-null, the set of candidate handlers
* is reduced to contain only handlers that support the
* action.</li>
*
* <li>If the set of candidate handlers is empty
* then a <tt>ContentHandlerException</tt> is thrown.</li>
* </ul>
* <p>
* The calling thread blocks while the ID and type are being determined.
* If a network access is needed there may be an associated delay.
*
* @param invoc the ID, type, action, and URL that
* are needed to identify one or more content handlers;
* must not be <code>null</code>
* @return an array of the <code>ContentHandler</code>(s)
* that could be used for this Invocation; MUST NOT be <code>null</code>;
*
* @exception IOException is thrown if access to the content fails
* @exception ContentHandlerException is thrown with a reason of
* <code>NO_REGISTERED_HANDLER</code> if
* there is no registered content handler that
* matches the requested ID, type, URL, and action
*
* @exception IllegalArgumentException is thrown if ID, type, URL,
* and action are all <code>null</code> or
* if the content is accessed via the URL and the URL is invalid
* @exception NullPointerException is thrown if the
* <code>invocation</code> is <code>null</code>
* @exception SecurityException is thrown if access to the content
* is not permitted
*/
public ContentHandler[] findHandler(InvocationImpl invoc)
throws IOException, ContentHandlerException
{
ContentHandler[] handlers = null;
if (invoc.getID() != null) {
ContentHandler handler = forID(invoc.getID(), false);
if (handler != null) {
handlers = new ContentHandler[1];
handlers[0] = handler;
}
} else {
String action = invoc.getAction();
// ID is null
synchronized (mutex) {
// Inhibit types change while doing lookups
if (invoc.getType() == null &&
invoc.getURL() != null) {
try {
invoc.findType();
} catch (ContentHandlerException che) {
// Type is null
}
}
if (invoc.getType() != null) {
// The type is known; lookup the handlers
handlers = forType(invoc.getType());
} else if (invoc.getURL() != null) {
/**
* Call platform specific function for
* getting handler by URL
*/
ContentHandler suitable =
RegistryStore.getByURL(getID(), invoc.getURL(), action);
if (suitable != null) {
handlers = new ContentHandler[1];
handlers[0] = suitable;
}
} else if (action != null) {
handlers = forAction(action);
action = null;
} else {
throw new IllegalArgumentException(
"not ID, type, URL, or action");
}
// Set of candidate handlers; check for matching action
if (handlers != null && action != null) {
int rem = 0; // number of handlers to remove
for (int i = 0; i < handlers.length; i++) {
if (!handlers[i].hasAction(action)) {
handlers[i] = null;
rem++;
}
}
if (rem > 0) {
int newsz = handlers.length - rem;
if (newsz > 0) {
ContentHandler[] newhand =
new ContentHandler[newsz];
int j;
int k;
for (j = k = 0; j < newsz; j++) {
while (handlers[k] == null) {
k++;
}
newhand[j] = handlers[k++];
}
handlers = newhand;
} else {
handlers = null;
}
}
}
}
}
if (handlers == null || handlers.length == 0) {
throw new ContentHandlerException("no registered handler",
ContentHandlerException.NO_REGISTERED_HANDLER);
}
return handlers;
}
/**
* Gets the registered content handlers for the content type.
* <P>
* Only content handlers that are visible and accessible to this
* application are returned.
*
* @param type the type of the requested content handlers
* @return an array of the <code>ContentHandler</code>s registered
* for the type; MUST NOT be <code>null</code>.
* An empty array is returned if there are no
* <code>ContentHandler</code>s accessible to
* this application.
* @exception NullPointerException if <code>type</code> is
* <code>null</code>
*/
public ContentHandler[] forType(String type) {
return RegistryStore.findHandler(getID(), RegistryStore.FIELD_TYPES,
type);
}
/**
* Gets the registered content handlers that support the action.
* <P>
* Only content handlers that are visible and accessible to this
* application are returned.
*
* @param action content handlers for which the action is supported
* @return an array of the <code>ContentHandler</code>s registered
* for the action; MUST NOT be <code>null</code>;
* an empty array is returned if no <code>ContentHandler</code>s
* are accessible to this application
* @exception NullPointerException if <code>action</code> is
* <code>null</code>
*/
public ContentHandler[] forAction(String action) {
return RegistryStore.findHandler(getID(), RegistryStore.FIELD_ACTIONS,
action);
}
/**
* Gets all of the content handlers for the suffix.
* Only content handlers that are visible and accessible to this
* application are returned.
*
* @param suffix the suffix to be used to get the associated
* content handlers
*
* @return an array of the <code>ContentHandler</code>s registered
* for the suffix; MUST NOT be <code>null</code>.
* An empty array is returned if there are none accessible to
* this application
*
* @exception NullPointerException if <code>suffix</code> is
* <code>null</code>
*/
public ContentHandler[] forSuffix(String suffix) {
return RegistryStore.findHandler(getID(), RegistryStore.FIELD_SUFFIXES,
suffix);
}
/**
* Gets the registered content handler for the ID.
* The query can be for an exact match or for the handler
* matching the prefix of the requested ID.
* <P>
* Only a content handler which is visible to and accessible to this
* application will be returned.
*
* @param ID the content handler application ID of the content
* handler requested
* @param exact <code>true</code> to require an exact match;
* <code>false</code> to allow a registered content handler ID
* to match a prefix of the requested ID
*
* @return the content handler that matches the ID,
* otherwise <code>null</code>
*
* @exception NullPointerException if <code>ID</code> is
* <code>null</code>
*/
public ContentHandler forID(String ID, boolean exact) {
return RegistryStore.getHandler(getID(), ID,
exact? RegistryStore.SEARCH_EXACT: RegistryStore.SEARCH_PREFIX);
}
/**
* Gets the registered content handler for the
* application class of this RegistryImpl.
*
* @return the content handler for the registered
* <code>classname</code> if it was registered by this application.
* Otherwise, it returns <code>null</code>.
* @exception NullPointerException if <code>classname</code> is
* <code>null</code>
*/
public ContentHandlerImpl getServer() {
return handlerImpl;
}
/**
* Gets the content handler for the specified application class.
* The classname must be a class in the current application.
*
* @param appl the application to look up a server fro
* @return the content handler information for the registered
* classname if the classname was registered by this application,
* otherwise return <code>null</code>
* @exception NullPointerException if <code>classname</code> is
* <code>null</code>
*/
ContentHandlerImpl getServer(AppProxy appl) {
synchronized (mutex) {
String classname = appl.getClassname();
int storageId = appl.getStorageId();
ContentHandlerImpl handler = RegistryStore.getHandler(storageId, classname);
if (handler != null) {
handler.appname = appl.getApplicationName();
handler.version = appl.getVersion();
handler.authority = appl.getAuthority();
}
return handler;
}
}
/**
* Gets the content handler ID for the current application.
* The ID uniquely identifies the application which contains the
* content handler.
* The application ID is assigned when the application is installed.
* If the application is a content handler then the ID must be
* the content handler ID.
* @return the ID; MUST NOT be <code>null</code>
*/
public String getID() {
return (handlerImpl != null) ?
handlerImpl.getID() : application.getApplicationID();
}
/**
* Insert an Invocation to the set of active Invocations.
*
* @param invoc an Invocation to add
*/
private void insertActive(InvocationImpl invoc) {
Integer tid = new Integer(invoc.tid);
activeInvocations.put(tid, invoc);
}
/**
* Remove an Invocation from the set of active Invocations.
* @param invoc an Invocation to remvoe
* @return the active Invocation or null if not found
*/
private InvocationImpl removeActive(InvocationImpl invoc) {
Integer tid = new Integer(invoc.tid);
return (InvocationImpl)activeInvocations.remove(tid);
}
}
|