/* * * * 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 java.util.Hashtable; import javax.microedition.content.ActionNameMap; import javax.microedition.content.ContentHandler; import javax.microedition.content.ContentHandlerException; import javax.microedition.content.Invocation; import javax.microedition.content.Registry; import javax.microedition.content.ResponseListener; import com.sun.j2me.content.ContentHandlerImpl.Handle; import com.sun.j2me.security.Token; import com.sun.j2me.security.TrustedClass; import com.sun.jsr211.security.SecurityInitializer; /** * 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 implements Counter { /** * Inner class to request security token from SecurityInitializer. * SecurityInitializer should be able to check this inner class name. */ static private class SecurityTrusted implements TrustedClass {}; /** This class has a different security domain than the App suite */ private static Token securityToken; static { securityToken = SecurityInitializer.requestToken(new SecurityTrusted()); RegistryStore.setSecurityToken(securityToken); } /** The set of active Invocations. */ private /*static*/ final Hashtable activeInvocations = new Hashtable(); /** The set of active RegistryImpls. */ private static final 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; int cancelCounter = 0; /** 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, Token token) throws ContentHandlerException { AppProxy.checkAPIPermission(token); try { return getRegistryImpl(AppProxy.getCurrent().forClass(classname)); } catch (IllegalArgumentException iae) { throw new ContentHandlerException("not an application", ContentHandlerException.NO_REGISTERED_HANDLER); } catch (ClassNotFoundException cnfe) { throw new ContentHandlerException("not an application", ContentHandlerException.NO_REGISTERED_HANDLER); } } /** * 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(AppProxy appl) throws ContentHandlerException { // Synchronize between competing operations RegistryImpl curr = null; synchronized (mutex) { if( AppProxy.LOGGER != null ) AppProxy.LOGGER.println( "RegistryImpl.getRegistryImpl( '" + appl + "' )"); // Check if class already has a RegistryImpl curr = (RegistryImpl)registries.get(appl); 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(appl); registries.put(appl, curr); if( AppProxy.LOGGER != null ){ AppProxy.LOGGER.println( "registers:" ); java.util.Enumeration e = registries.keys(); while( e.hasMoreElements() ){ Object key = e.nextElement(); AppProxy.LOGGER.println( "\t" + key + " " + registries.get(key) ); } } } /* * 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, 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 permission */ private RegistryImpl(AppProxy app) throws ContentHandlerException { application = app; /* 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(ApplicationID appID) { InvocationImpl invoc = null; while ((invoc = InvocationStore.getCleanup(appID)) != null) { invoc.setStatus(Invocation.ERROR); } } /** * 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 { if(AppProxy.LOGGER != null){ AppProxy.LOGGER.println( getClass().getName() + ".register '" + classname + "'" ); } application.checkRegisterPermission("register"); // May throw ClassNotFoundException or IllegalArgumentException AppProxy appl = application.forClass(classname); synchronized (mutex) { // Default the ID if not supplied if (id == null) { // Generate a unique ID based on the MIDlet suite id = appl.getDefaultID(); } int registrationMethod = // non-native, dynamically registered ~ContentHandlerImpl.REGISTERED_STATIC_FLAG & ContentHandlerImpl.REGISTERED_STATIC_FLAG; ContentHandlerRegData handlerData = new ContentHandlerRegData(registrationMethod, types, suffixes, actions, actionnames, id, accessRestricted); ContentHandlerImpl conflict = checkConflicts(handlerData.getID(), appl); if (conflict != null) { unregister(classname); } ContentHandlerImpl.Handle handle = RegistryStore.register(appl, handlerData); setServer(handle.get()); if (AppProxy.LOGGER != null) { AppProxy.LOGGER.println("Register: " + classname + ", id: " + id); } return handle.get(); } } /** * Sets the ContentHandlerImpl; update any active RegistryImpl. * Replaces the entry in RegisteredTypes list as well. * * @param server the ContentHandlerImpl for this RegistryImpl * @see com.sun.j2me.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.applicationID); if (impl != null) { impl.handlerImpl = server; } } } /** * The special finder for acquiring handler by its suite and class name. * @param suiteId explored suite Id * @param classname requested class name. * * @return found handler or <code>null</code> if none found. */ static ContentHandlerImpl getHandler(AppProxy appl) { return RegistryStore.getHandler(appl); } /** * 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(String handlerID, AppProxy appl) throws ContentHandlerException { ContentHandlerImpl[] handlers = RegistryStore.findConflicted(handlerID); ContentHandlerImpl existing = null; if (handlers != null) { switch (handlers.length) { case 0: break; case 1: if (appl.equals(handlers[0].applicationID)) { existing = handlers[0]; break; } default: throw new ContentHandlerException( "ID would be ambiguous: " + handlerID, ContentHandlerException.AMBIGUOUS); } } if (existing == null) { existing = getHandler(appl); } 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(), Handle.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(), Handle.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(), Handle.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(), Handle.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) { classname.length(); // NullPointer check if(AppProxy.LOGGER != null) AppProxy.LOGGER.println( "unregister '" + classname + "'" ); try { AppProxy appl = application.forClass(classname); synchronized (mutex) { RegistryImpl reg = (RegistryImpl)registries.get(appl); ContentHandlerImpl curr = (reg != null)? reg.getServer() : getHandler(appl); if (curr != null) { RegistryStore.unregister(curr.getID()); if (reg != null && appl.equals(curr.applicationID)) { reg.handlerImpl = null; } return true; } } } catch (IllegalArgumentException iae) { } catch (ClassNotFoundException e) { } if(AppProxy.LOGGER != null) AppProxy.LOGGER.println( "unregister() failed." ); 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 implementation 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.invokingApp = application.duplicate(); // TODO: may be authority and app name should be moved to AppID? 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 method 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 canceled with {@link #cancelGetResponse} * @see #invoke * @see #cancelGetResponse */ public Invocation getResponse(boolean wait) { // Application has tried to get a response; reset cleanup flags on all if (responseCalls == 0) { InvocationStore.setCleanup(application, false); } responseCalls++; // Find a response for this application and context InvocationImpl invoc = InvocationStore.getResponse(application, wait, this); if (invoc != null) { // Keep track of how many responses have been received; final ApplicationID fromApp = invoc.invokingApp; ApplicationID toApp = invoc.destinationApp; /* * 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.getStatus(); 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, false); } } if (invoc.previous != null && invoc.previous.getStatus() == Invocation.HOLD) { // Restore ACTIVE status to a previously HELD Invocation invoc.previous.setStatus(Invocation.ACTIVE); toApp = invoc.previous.destinationApp; } // Make an attempt to gain the foreground AppProxy.requestForeground(fromApp, toApp); return invoc.wrap(); } 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() { cancelCounter++; 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 { HandlersCollection collection = new HandlersCollection(); ContentHandlerImpl.Handle.Receiver output = collection; if( invoc.getAction() != null ){ output = new HandlerActionFilter( invoc.getAction(), output ); } // 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 RegistryStore.enumHandlers( getID(), ContentHandlerImpl.Handle.FIELD_TYPES, invoc.getType(), output ); } else if (invoc.getURL() != null) { int lpIdx = invoc.getURL().lastIndexOf('.'); if( lpIdx != -1 ){ String suffix = invoc.getURL().substring(lpIdx); RegistryStore.enumHandlers( getID(), ContentHandlerImpl.Handle.FIELD_SUFFIXES, suffix, output ); } } else if (invoc.getAction() != null) { RegistryStore.enumHandlers( getID(), ContentHandlerImpl.Handle.FIELD_ACTIONS, invoc.getAction(), collection /* skip action filter here */ ); } else { throw new IllegalArgumentException( "not ID, type, URL, or action"); } } handlers = collection.getArray(); } 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(), Handle.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(), Handle.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(), Handle.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 for * @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) { ContentHandlerImpl handler = getHandler(appl); 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); if( AppProxy.LOGGER != null ) AppProxy.LOGGER.println(getClass().getName() + ".insertActive(" + tid + ")"); activeInvocations.put(tid, invoc); } /** * Remove an Invocation from the set of active Invocations. * @param invoc an Invocation to remove * @return the active Invocation or null if not found */ private InvocationImpl removeActive(InvocationImpl invoc) { Integer tid = new Integer(invoc.tid); InvocationImpl result = (InvocationImpl)activeInvocations.remove(tid); if( AppProxy.LOGGER != null ) AppProxy.LOGGER.println(getClass().getName() + ".removeActive(" + tid + ") = " + result ); return result; } public int getCounter() { return cancelCounter; } }