/*
*
*
* 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.io.j2me.push;
import java.io.IOException;
import java.util.Enumeration;
import javax.microedition.io.ConnectionNotFoundException;
import com.sun.midp.io.Util;
import com.sun.midp.main.*;
import com.sun.midp.midletsuite.MIDletSuiteStorage;
import com.sun.midp.midlet.MIDletStateHandler;
import com.sun.midp.midlet.MIDletSuite;
import com.sun.midp.security.SecurityToken;
import com.sun.midp.log.Logging;
/**
* CLDC implementation of ConnectionRegistry.
*/
final class ConnectionRegistry
implements Runnable, MIDletProxyListListener {
/**
* Push option to only launch this suite when not other applications
* are running.
*/
static final int PUSH_OPT_WHEN_ONLY_APP = 1;
/** This class has a different security domain than the MIDlet suite. */
private static SecurityToken classSecurityToken;
/**
* Flag to control when push launching is permitted.
* This flag is set to false by the AMS when installing or removing
* MIDlets, when an interruption could compromise the integrity of
* the operation.
*/
static boolean pushEnabled = true;
/**
* This flag is set to true by the AMS when running in MVM singal MIDlet
* mode. In this mode the current MIDlet that is not the application
* manager should be destroyed before the next MIDlet is started.
*/
static boolean mvmSingleMidletMode;
/** MIDlet proxy list reference. */
private MIDletProxyList midletProxyList;
/** Cached reference to the midlet suite storage instance. */
private static MIDletSuiteStorage storage;
/**
* Start listening for push notifications. Will throw a security
* exception if called by any thing other than the MIDletSuiteLoader.
*/
static void startListening() {
(new Thread(new ConnectionRegistry())).start();
}
/**
* Keeps an object of this class from being created out of
* the startListening method.
* Will throw a security exception if called by any thing other
* than the MIDletSuiteLoader.
*/
private ConnectionRegistry() {
/*
* Will throw a security exception if called by any thing other
* than the MIDletSuiteLoader.
*/
midletProxyList =
MIDletProxyList.getMIDletProxyList(classSecurityToken);
midletProxyList.addListener(this);
if (storage == null) {
storage = MIDletSuiteStorage.getMIDletSuiteStorage(
classSecurityToken);
}
}
/**
* Run the polling loop to check for inbound connections.
*/
public void run() {
int fd = -1;
int ret = 0;
while (true) {
try {
fd = poll0(System.currentTimeMillis());
if (fd != -1) {
if (pushEnabled) {
byte[] registryEntry = new byte[512];
if ((ret = getMIDlet0(fd, registryEntry, 512)) == 0) {
String name = Util.toJavaString(registryEntry);
launchEntry(name);
}
} else {
checkInByHandle0(fd);
}
}
} catch (Exception e) {
if (Logging.TRACE_ENABLED) {
Logging.trace(e, null);
}
}
}
}
/**
* Parse the registration entry and launch the associated
* <code>MIDlet</code>.
* @param name registration string for connection and
* <code>MIDlet</code> to be launched
*/
private void launchEntry(String name) {
String conn;
String midlet;
String filter;
String strSuiteId;
int id;
MIDletSuite next = null;
/*
* Parse the comma separated values -
* " connection, midlet, filter, id"
* " midlet, wakeup, midlet suite ID"
*/
int comma1 = name.indexOf(',', 0);
int comma2 = name.indexOf(',', comma1 + 1);
int comma3 = name.indexOf(',', comma2 + 1);
if (comma3 == -1) {
/* Alarm was triggered */
conn = null;
midlet = name.substring(0, comma1).trim();
strSuiteId = name.substring(comma2+1).trim();
} else {
conn = name.substring(0, comma1).trim();
midlet = name.substring(comma1+1, comma2).trim();
filter = name.substring(comma2+1, comma3).trim();
strSuiteId = name.substring(comma3+1).trim();
}
try {
/*
* IMPL_NOTE: here it's assumed that when a suiteId is converted
* to string the padding zeroes are placed _before_ the value,
* for ex., suiteId 3 is converted into "00000003".
* MIDletSuiteStorage.stringToSuiteId() API should be added later.
*/
id = Integer.parseInt(strSuiteId);
} catch (NumberFormatException nfe) {
id = MIDletSuite.UNUSED_SUITE_ID;
}
try {
/*
* Check to see if the MIDlet is already started.
*/
if (midletProxyList.isMidletInList(id, midlet)) {
if (conn != null) {
checkInConnectionInternal(conn);
}
return;
}
next = storage.getMIDletSuite(id, false);
if (next == null) {
if (conn != null) {
checkInConnectionInternal(conn);
}
return;
}
if ((next.getPushOptions() & PUSH_OPT_WHEN_ONLY_APP) != 0 &&
!onlyAppManagerRunning()) {
if (conn != null) {
checkInConnectionInternal(conn);
}
return;
}
if (!next.permissionToInterrupt(conn)) {
// user does not want the interruption
if (conn != null) {
checkInConnectionInternal(conn);
}
return;
}
if (MIDletSuiteUtils.execute(classSecurityToken, id, midlet,
null)) {
/* We are in SVM mode, destroy all running MIDlets. */
MIDletStateHandler.getMidletStateHandler().destroySuite();
} else if (mvmSingleMidletMode) {
destroyAppMidlets();
}
} catch (Throwable e) {
// Could not launch requested push entry
if (conn != null) {
checkInConnectionInternal(conn);
}
if (Logging.TRACE_ENABLED) {
Logging.trace(e, null);
}
} finally {
if (next != null) {
next.close();
}
}
}
/**
* Check to see if only the application manager MIDlet is running.
*
* @return true if only the application manager is running
*/
private boolean onlyAppManagerRunning() {
Enumeration midlets = midletProxyList.getMIDlets();
while (midlets.hasMoreElements()) {
MIDletProxy midlet = (MIDletProxy)midlets.nextElement();
if (midlet.getSuiteId() != MIDletSuite.INTERNAL_SUITE_ID ||
midlet.getClassName().indexOf("Manager") == -1) {
return false;
}
}
return true;
}
/**
* Destroy every MIDlet except the application manager midlet.
* This should only be used in MVM Signal MIDlet Mode.
*/
private void destroyAppMidlets() {
Enumeration midlets = midletProxyList.getMIDlets();
while (midlets.hasMoreElements()) {
MIDletProxy midlet = (MIDletProxy)midlets.nextElement();
if (midlet.getSuiteId() == MIDletSuite.INTERNAL_SUITE_ID &&
midlet.getClassName().indexOf("Manager") != -1) {
continue;
}
midlet.destroyMidlet();
}
}
/**
* Called when a MIDlet is added to the list, not used by this class.
*
* @param midlet The proxy of the MIDlet being added
*/
public void midletAdded(MIDletProxy midlet) {}
/**
* Called when the state of a MIDlet in the list is updated.
*
* @param midlet The proxy of the MIDlet that was updated
* @param fieldId code for which field of the proxy was updated
*/
public void midletUpdated(MIDletProxy midlet, int fieldId) {}
/**
* Called when a MIDlet is removed from the list, the connections
* in "launch pending" state for this MIDlet will be checked in.
*
* @param midlet The proxy of the removed MIDlet
*/
public void midletRemoved(MIDletProxy midlet) {
byte[] asciiClassName = Util.toCString(midlet.getClassName());
checkInByMidlet0(midlet.getSuiteId(), asciiClassName);
}
/**
* Called when error occurred while starting a MIDlet object. The
* connections in "launch pending" state for this MIDlet will be checked
* in.
*
* @param externalAppId ID assigned by the external application manager
* @param suiteId Suite ID of the MIDlet
* @param className Class name of the MIDlet
* @param errorCode start error code
* @param errorDetails start error code
*/
public void midletStartError(int externalAppId, int suiteId,
String className, int errorCode,
String errorDetails) {
byte[] asciiClassName = Util.toCString(className);
checkInByMidlet0(suiteId, asciiClassName);
}
/**
* Initializes the security token for this class, so it can
* perform actions that a normal MIDlet Suite cannot.
*
* @param token security token for this class.
*/
public static void initSecurityToken(SecurityToken token) {
if (classSecurityToken == null) {
classSecurityToken = token;
}
}
/**
* Register a dynamic connection.
*
* @param midletSuite <code>MIDlet</code> suite to register connection for
* @param connection generic connection <em>protocol</em>, <em>host</em>
* and <em>port number</em>
* (optional parameters may be included
* separated with semi-colons (;))
* @param midlet class name of the <code>MIDlet</code> to be launched,
* when new external data is available
* @param filter a connection URL string indicating which senders
* are allowed to cause the MIDlet to be launched
*
* @exception ClassNotFoundException if the <code>MIDlet</code> class
* name can not be found in the current
* <code>MIDlet</code> suite
* @exception IOException if the connection is already
* registered or if there are insufficient resources
* to handle the registration request
* @see #unregisterConnection
*/
public static void registerConnection(MIDletSuite midletSuite,
Connection connection, String midlet, String filter)
throws ClassNotFoundException, IOException {
registerConnectionInternal(midletSuite,
connection.getConnection(), midlet, filter, true);
}
/**
* Check the registration arguments.
* @param connection preparsed connection to check
* @param midlet class name of the <code>MIDlet</code> to be launched,
* when new external data is available
* @param filter a connection URL string indicating which senders
* are allowed to cause the MIDlet to be launched
* @exception IllegalArgumentException if connection or filter is not
* valid
* @exception ConnectionNotFoundException if PushRegistry doesn't support
* this kind of connections
*/
static void checkRegistration(Connection connection, String midlet,
String filter)
throws ConnectionNotFoundException {
final String c = connection.getConnection();
ProtocolPush.getInstance(c).checkRegistration(c, midlet, filter);
}
/**
* Register a dynamic connection with the
* application management software. Once registered,
* the dynamic connection acts just like a
* connection preallocated from the descriptor file.
* The internal implementation includes the storage name
* that uniquely identifies the <code>MIDlet</code>.
* This method bypasses the class loader specific checks
* needed by the <code>Installer</code>.
*
* @param midletSuite MIDlet suite for the suite registering,
* the suite only has to implement isRegistered,
* checkForPermission, and getID.
* @param connection generic connection <em>protocol</em>, <em>host</em>
* and <em>port number</em>
* (optional parameters may be included
* separated with semi-colons (;))
* @param midlet class name of the <code>MIDlet</code> to be launched,
* when new external data is available
* @param filter a connection URL string indicating which senders
* are allowed to cause the MIDlet to be launched
* @param registerConnection if true, register a connection with a
* protocol,
* used by the installer when redo old connections during an
* aborted update
*
* @exception ClassNotFoundException if the <code>MIDlet</code> class
* name can not be found in the current
* <code>MIDlet</code> suite
* @exception IOException if the connection is already
* registered or if there are insufficient resources
* to handle the registration request
*
* @see #unregisterConnection
*/
static void registerConnectionInternal(
final MIDletSuite midletSuite,
final String connection,
final String midlet,
final String filter,
final boolean registerConnection)
throws ClassNotFoundException, IOException {
if (registerConnection) {
/*
* No need to register connection when bypassChecks: restoring
* RFC: why add0 below?
*/
ProtocolPush.getInstance(connection)
.registerConnection(midletSuite, connection, midlet, filter);
}
byte[] asciiRegistration = Util.toCString(connection
+ "," + midlet
+ "," + filter
+ "," + suiteIdToString(midletSuite));
if (add0(asciiRegistration) == -1) {
// in case of Bluetooth URL, unregistration within Bluetooth
// PushRegistry was already performed by add0()
throw new IOException("Connection already registered");
}
}
/**
* Remove a dynamic connection registration.
*
* @param midletSuite <code>MIDlet</code> suite to unregister connection
* for
* @param connection generic connection <em>protocol</em>,
* <em>host</em> and <em>port number</em>
* @exception SecurityException if the connection was
* not registered by the current <code>MIDlet</code>
* suite
* @return <code>true</code> if the unregistration was successful,
* <code>false</code> the connection was not registered.
* @see #registerConnection
*/
public static boolean unregisterConnection(MIDletSuite midletSuite,
String connection) {
byte[] asciiRegistration = Util.toCString(connection);
byte[] asciiStorage = Util.toCString(suiteIdToString(midletSuite));
int ret = del0(asciiRegistration, asciiStorage);
if (ret == -2) {
throw new SecurityException("wrong suite");
}
return ret != -1;
}
/**
* Check in a push connection into AMS so the owning MIDlet can get
* launched next time data is pushed. This method is used when a MIDlet
* will not be able to get the connection and close (check in) the
* connection for some reason. (normally because the user denied a
* permission)
* <p>
* For datagram connections this function will discard the cached message.
* <p>
* For server socket connections this function will close the
* accepted connection.
*
* @param connection generic connection <em>protocol</em>, <em>host</em>
* and <em>port number</em>
* (optional parameters may be included
* separated with semi-colons (;))
* @exception IllegalArgumentException if the connection string is not
* valid
* @exception SecurityException if the <code>MIDlet</code> does not
* have permission to clear a connection
* @return <code>true</code> if the check in was successful,
* <code>false</code> the connection was not registered.
* @see #unregisterConnection
*/
static boolean checkInConnectionInternal(final String connection) {
/* Verify that the connection requested is valid. */
if (connection == null || connection.length() == 0) {
throw new IllegalArgumentException("Connection missing");
}
byte[] asciiRegistration = Util.toCString(connection);
return checkInByName0(asciiRegistration) != -1;
}
/**
* Return a list of registered connections for the current
* <code>MIDlet</code> suite.
*
* @param midletSuite <code>MIDlet</code> suite to list connections for
* @param available if <code>true</code>, only return the list of
* connections with input available
* @return array of connection strings, where each connection is
* represented by the generic connection <em>protocol</em>,
* <em>host</em> and <em>port number</em> identification
*/
public static String [] listConnections(MIDletSuite midletSuite,
boolean available) {
return connectionsToArray(listConnections(midletSuite.getID(),
available));
}
/**
* Return a list of registered connections for given
* <code>MIDlet</code> suite. AMS permission is required.
*
* @param id identifies the specific <code>MIDlet</code>
* suite to be launched
* @param available if <code>true</code>, only return the list of
* connections with input available
*
* @return array of connection strings, where each connection is
* represented by the generic connection <em>protocol</em>,
* <em>host</em> and <em>port number</em> identification
*/
static String listConnections(int id, boolean available) {
byte[] nativeID;
String connections = null;
byte[] connlist;
nativeID = Util.toCString(suiteIdToString(id));
connlist = new byte[512];
if (list0(nativeID, available, connlist, 512) == 0) {
connections = Util.toJavaString(connlist);
}
return connections;
}
/**
* Converts connections as string into string array.
*
* @param connections Connections to convert
* @return array of connections
*/
private static String [] connectionsToArray(String connections) {
if (connections == null) {
return new String[0];
}
/* Count the commas in the returned string */
int count = 0;
int offset = 0;
do {
offset = connections.indexOf(',', offset + 1);
count ++;
} while (offset > 0);
/* Now parse out the connections for easier access by caller. */
String[] ret = new String[count];
int start = 0;
for (int i = 0; i < count; i++) {
offset = connections.indexOf(',', start);
if (offset > 0) {
/* Up to the next comma */
ret[i] = connections.substring(start, offset);
} else {
/* From the last comma to the end of the string. */
ret[i] = connections.substring(start);
}
start = offset + 1;
}
return ret;
}
/**
* Retrieve the registered <code>MIDlet</code> for a requested connection.
*
* @param connection generic connection <em>protocol</em>, <em>host</em>
* and <em>port number</em>
* (optional parameters may be included
* separated with semi-colons (;))
* @return class name of the <code>MIDlet</code> to be launched,
* when new external data is available, or
* <code>null</code> if the connection was not
* registered
* @see #registerConnection
*/
public static String getMIDlet(String connection) {
String midlet = null;
byte[] asciiConn = Util.toCString(connection);
byte[] registryEntry = new byte[512];
if (getEntry0(asciiConn, registryEntry, 512) == 0) {
String name = Util.toJavaString(registryEntry);
try {
int comma1 = name.indexOf(',', 0);
int comma2 = name.indexOf(',', comma1 + 1);
midlet = name.substring(comma1+1, comma2).trim();
} catch (Exception e) {
if (Logging.TRACE_ENABLED) {
Logging.trace(e, null);
}
}
}
return midlet;
}
/**
* Retrieve the registered filter for a requested connection.
*
* @param connection generic connection <em>protocol</em>, <em>host</em>
* and <em>port number</em>
* (optional parameters may be included
* separated with semi-colons (;))
* @return a filter string indicating which senders
* are allowed to cause the MIDlet to be launched or
* <code>null</code> if the connection was not
* registered
* @see #registerConnection
*/
public static String getFilter(String connection) {
String filter = null;
byte[] asciiConn = Util.toCString(connection);
byte[] registryEntry = new byte[512];
if (getEntry0(asciiConn, registryEntry, 512) == 0) {
String name = Util.toJavaString(registryEntry);
try {
int comma1 = name.indexOf(',', 0);
int comma2 = name.indexOf(',', comma1 + 1);
int comma3 = name.indexOf(',', comma2 + 1);
filter = name.substring(comma2+1, comma3).trim();
} catch (Exception e) {
if (Logging.TRACE_ENABLED) {
Logging.trace(e, null);
}
}
}
return filter;
}
/**
* Register a time to launch the specified application. The
* <code>PushRegistry</code> supports one outstanding wake up
* time per <code>MIDlet</code> in the current suite. An application
* is expected to use a <code>TimerTask</code> for notification
* of time based events while the application is running.
* <P>If a wakeup time is already registered, the previous value will
* be returned, otherwise a zero is returned the first time the
* alarm is registered. </P>
*
* @param midletSuite <code>MIDlet</code> suite to register alarm for
* @param midlet class name of the <code>MIDlet</code> within the
* current running <code>MIDlet</code> suite
* to be launched,
* when the alarm time has been reached
* @param time time at which the <code>MIDlet</code> is to be executed
* in the format returned by <code>Date.getTime()</code>
* @return the time at which the most recent execution of this
* <code>MIDlet</code> was scheduled to occur,
* in the format returned by <code>Date.getTime()</code>
* @exception ConnectionNotFoundException if the runtime system does not
* support alarm based application launch
*/
public static long registerAlarm(MIDletSuite midletSuite,
String midlet, long time)
throws ConnectionNotFoundException {
byte[] asciiName = Util.toCString(midlet + ","
+ time + ","
+ suiteIdToString(midletSuite));
return addAlarm0(asciiName, time);
}
/**
* Converts <code>MIDlet</code> suite ID into a string.
*
* @param suiteId <code>MIDlet</code> suite to convert ID of
* @return string representation
*/
private static String suiteIdToString(final int suiteId) {
// assert storage != null; // Listener should be started before
return storage.suiteIdToString(suiteId);
}
/**
* Converts <code>MIDlet</code> suite ID into a string.
*
* @param midletSuite <code>MIDlet</code> suite to convert ID of
* @return string representation
*/
private static String suiteIdToString(final MIDletSuite midletSuite) {
// assert midletSuite != null;
return suiteIdToString(midletSuite.getID());
}
/**
* Native connection registry add connection function.
* @param connection string to register
* @return 0 if successful, -1 if failed
*/
private static native int add0(byte[] connection);
/**
* Native function to test registered inbound connections
* for new connection notification.
* @param time current time to use for alarm checks
* @return handle for the connection with inbound connection
* pending.
*/
private native int poll0(long time);
/**
* Native connection registry lookup for MIDlet name from file
* descriptor.
* @param handle file descriptor of registered active connection
* @param regentry registered entry
* @param entrysz maximum string that will be accepted
* @return 0 if successful, -1 if failed
*/
private static native int getMIDlet0(int handle, byte[] regentry,
int entrysz);
/**
* Native connection registry lookup registry entry from a
* specific connection.
* @param connection registered connection string
* @param regentry registered entry
* @param entrysz maximum string that will be accepted
* @return 0 if successful, -1 if failed
*/
private static native int getEntry0(byte[]connection, byte[] regentry,
int entrysz);
/**
* Native connection registry add alarm function.
* @param midlet string to register
* @param time
* @return 0 if unregistered, otherwise the time of the previous
* registered alarm
*/
private static native long addAlarm0(byte[] midlet, long time);
/**
* Native connection registry del connection function.
* @param connection string to register
* @param storage current suite storage name
* @return 0 if successful, -1 if failed
*/
private static native int del0(byte[] connection, byte[] storage);
/**
* Native connection registry check in connection function.
* @param connection string to register
* @return 0 if successful, -1 if failed
*/
private static native int checkInByName0(byte[] connection);
/**
* Native connection registry check in connection function.
* @param handle native handle of the connection
*/
private static native void checkInByHandle0(int handle);
/**
* Native connection registry method to check in connections that are in
* launch pending state for a specific MIDlet.
*
* @param suiteId Suite ID of the MIDlet
* array
* @param className Class name of the MIDlet as zero terminated ASCII
* byte array
*/
private static native void checkInByMidlet0(int suiteId,
byte[] className);
/**
* Native connection registry list connection function.
* @param midlet string to register
* @param available if <code>true</code>, only return the list of
* connections with input available
* @param connectionlist comma separated string of connections
* @param listsz maximum string that will be accepted in connectionlist
* @return 0 if successful, -1 if failed
*/
private static native int list0(byte[] midlet, boolean available,
byte[] connectionlist, int listsz);
/**
* Native connection registry delete a suite's connections function.
* @param id suite's ID
*/
static native void delAllForSuite0(int id);
}
|