FileDocCategorySizeDatePackage
Installer.javaAPI DocJ2ME MIDP 2.0132412Thu Nov 07 12:02:24 GMT 2002com.sun.midp.midletsuite

Installer

public class Installer extends Object
An Installer manages MIDlet suites and libraries present in a Java application environment. An MIDlet suite distributed as a descriptor and JAR pair. The descriptor identifies the configuration and contains security information and the manifest of the JAR describes the contents. The implementation of an Installer is specific to the platform and provides access to procedures that make an MIDlet suite visible to users.

Each installed package is uniquely identified by a storage name constructed from the combination of the values of the MIDlet-Name and MIDlet-Vendor attributes. The syntax and content of the strings used to identify installed packages are implementation dependent. Only packages installed or upgraded using this API appear in the list of known packages.

Fields Summary
public static final int
DOWNLOADING_JAD
Status code to signal connection to the JAD server was successful.
public static final int
DOWNLOADED_1K_OF_JAD
Status code to signal that another 1K of the JAD has been download.
public static final int
DOWNLOADING_JAR
Status code to signal connection to the JAR server was successful.
public static final int
DOWNLOADED_1K_OF_JAR
Status code to signal that another 1K of the JAR has been download.
public static final int
VERIFYING_SUITE
Status code to signal that download is done and the suite is being verified.
public static final int
STORING_SUITE
Status code for local writing of the verified MIDlet suite. Stopping the install at this point has no effect, so there user should not be given a chance to stop the install.
public static final String
JAR_MANIFEST
Filename of Manifest inside the application archive.
public static final String
DATA_SIZE_PROP
MIDlet property for the size of the application data.
public static final String
JAR_SIZE_PROP
MIDlet property for the size of the application archive.
public static final String
JAR_URL_PROP
MIDlet property for the application archive URL.
public static final String
SUITE_NAME_PROP
MIDlet property for the suite name.
public static final String
VENDOR_PROP
MIDlet property for the suite vendor.
public static final String
VERSION_PROP
MIDlet property for the suite version.
public static final String
DESC_PROP
MIDlet property for the suite description.
public static final String
NOTIFY_PROP
MIDlet property for the install notify URL.
public static final String
DELETE_PROP
MIDlet property for the delete notify URL.
public static final String
CONFIGURATION_PROP
MIDlet property for the microedition configuration.
public static final String
PROFILE_PROP
MIDlet property for the profile.
public static final String
PERMISSIONS_PROP
MIDlet property for the required permissions.
public static final String
PERMISSIONS_OPT_PROP
MIDlet property for the optional permissions.
public static final String
JAD_MT
Media-Type for valid application descriptor files.
public static final String
JAR_MT_1
Media-Type for valid Jar file.
public static final String
JAR_MT_2
Media-Type for valid Jar file.
private static final int
MAX_DL_SIZE
Max number of bytes to download at one time (1K).
private static final String
BASIC_TAG
Tag that signals that the HTTP server supports basic auth.
static final String
CA_FILENAME
Filename to save the domain owner of the suite.
static final String
JAD_URL_FILENAME
Filename to save the URL of the JAD.
static final String
JAR_URL_FILENAME
Filename to save the URL of the JAR.
static final String
JAD_ENCODING_FILENAME
Filename to save the encoding of the JAD.
static final String
SETTINGS_FILENAME
Filename to save the encoding of the JAD.
static final String
JAR_FILENAME
Filename to suite JAR.
static final String
JAD_FILENAME
Filename to save the suite application descriptor.
static final String
DELETE_NOTIFY_FILENAME
Filename to save the delete notification URLs.
static final String
TMP_FILENAME
Filename to save the JAR of the suite temporarily. This is used to avoid overwriting an existing JAR prior to verification.
static final String
MANIFEST_FILENAME
Filename to save the application manifest.
static final String
SUITE_LIST_FILENAME
Filename of the list of installed suites.
static final String
SUCCESS_MSG
Success message for the suite provider.
static final String
INSUFFICIENT_MEM_MSG
Error message for the suite provider.
static final String
USER_CANCELLED_MSG
Error message for the suite provider.
static final String
JAR_SIZE_MISMATCH_MSG
Error message for the suite provider.
static final String
ATTRIBUTE_MISMATCH_MSG
Error message for the suite provider.
static final String
INVALID_JAD_MSG
Error message for the suite provider.
static final String
INVALID_JAR_MSG
Error message for the suite provider.
static final String
INCOMPATIBLE_MSG
Error message for the suite provider.
static final String
AUTHENTICATION_FAILURE_MSG
Error message for authentication failure.
static final String
AUTHORIZATION_FAILURE_MSG
Error message for authorization failure.
static final String
PUSH_REG_FAILURE_MSG
Error message for push registration failure.
static final String
DELETE_NOTIFICATION_MSG
Error message for push registration failure.
private static Installer
myInstaller
Private reference to the singleton Installer class.
private static com.sun.midp.security.SecurityToken
classSecurityToken
This class has a different security domain than the MIDlet suite.
private String
nextMidletSuiteToRun
The unique storage name of the next MIDlet suite to run.
private String
nextMidletToRun
The of the next MIDlet to run.
private Vector
midletSuiteList
Cache of installed MIDlet suites.
private Vector
deleteNotifyURLList
Cache of delete notification URL list.
private javax.microedition.io.HttpConnection
httpConnection
HTTP connection to close when we stop the installation.
private InputStream
httpInputStream
HTTP stream to close when we stop the installation.
protected InstallStateImpl
state
Holds the install state.
private String
cldcConfig
Holds the CLDC configuration string.
private Vector
supportedProfiles
Holds the MIDP supported profiles.
private String
unsignedSecurityDomain
Use this to be the security domain for unsigned suites.
Constructors Summary
protected Installer()
Constructor to prevent a second instantiation of the Installer.

        if (myInstaller != null) {
            throw new
                SecurityException("Illegal instantiation of the Installer");
        }
    
Methods Summary
private voidaddToDeleteNotifyURLList(java.lang.String url)
Queues provider's URL to send a delete notification message during the next OTA session.

param
url target url for the status message

        Vector deleteNotifyList;

        if (url == null) {
            return;
        }

        // NOTE: The spec only guarantees that the http:// protocol be
        //        supported. We do not do any checking of the protocol.

        deleteNotifyList = getDeleteNotifyURLList();
        deleteNotifyList.addElement(url);
        saveDeleteNotifyURLList(deleteNotifyList);
    
private voidaddToSuiteList(java.lang.String storageName)
Adds storage name into the list of MIDlet suites.

param
storageName ID of a suite

        Vector suiteList;

        if (suiteExists(storageName)) {
            return;
        }

        suiteList = getSuiteList();
        suiteList.addElement(storageName);
        saveSuiteList(suiteList);
    
private voidapplyCurrentUserLevelPermissions(byte[] current, byte[] domainPermissions, byte[] next)
Apply the previous user level permission of the currently installed version of a suite to the next version of the suite in a secure way.

param
current array permissions for the current version
param
domainPermissions array of the starting levels for permissions of the new domain
param
next array permissions for the next version


        for (int i = 0; i < current.length && i < next.length; i++) {
            switch (current[i]) {
            case Permissions.ALLOW:
            case Permissions.DENY:
                // not a user level permission
                continue;
            }

            switch (domainPermissions[i]) {
            case Permissions.ALLOW:
            case Permissions.DENY:
                // not a user level permission
                continue;

            case Permissions.ONE_SHOT:
                if (current[i] == Permissions.SESSION ||
                        current[i] == Permissions.DENY_SESSION) {
                    // do not apply
                    continue;
                }
                // fall through
            case Permissions.SESSION:
                if (current[i] == Permissions.BLANKET ||
                        current[i] == Permissions.BLANKET_GRANTED) {
                    // do not apply
                    continue;
                }
                // fall through
            default:
                next[i] = current[i];
                continue;
            }
        }
    
private voidcheckConfiguration()
Checks to make sure the configration need by the application is supported. Send a message back to the server if the check fails and throw an exception.

exception
InvalidJadException if the check fails

        String config;

        config = state.getAppProperty(CONFIGURATION_PROP);
        if (config == null || config.length() == 0) {
            postInstallMsgBackToProvider(INVALID_JAR_MSG);
            throw new InvalidJadException(
                InvalidJadException.MISSING_CONFIGURATION);
        }

        if (cldcConfig == null) {
            // need to call trim to remove trailing spaces
            cldcConfig = 
                System.getProperty("microedition.configuration").trim();
        }

        if (matchVersion(cldcConfig, config)) {
            // success, done
            return;
        }

        postInstallMsgBackToProvider(INCOMPATIBLE_MSG);
        throw new InvalidJadException(InvalidJadException.DEVICE_INCOMPATIBLE);
    
private voidcheckForDifferentDomains(java.lang.String url)
If the JAD belongs to an installed suite, check the URL against the installed one. Set the state.exception if the user needs to be warned.

param
url JAD or JAR URL of the suite being installed

        // perform a domain check not a straight compare
        if (state.ca == null && state.previousUrl != null) {
            HttpUrl old = new HttpUrl(state.previousUrl);
            HttpUrl current = new HttpUrl(url);

            if ((current.domain != null && old.domain == null) ||
                (current.domain == null && old.domain != null) ||
                (current.domain != null && old.domain != null &&
                 !current.domain.regionMatches(true, 0, old.domain, 0,
                                           old.domain.length()))) {
                /*
                 * The jad is at new location, could be bad,
                 * let the user decide
                 */
                state.exception = new InvalidJadException(
                                      InvalidJadException.JAD_MOVED,
                                      (state.previousUrl == null ? "none" :
                                      state.previousUrl));
                return;
            }
        }
    
protected voidcheckForJadManifestMismatches()
Checks to see that if any properties that are both in the JAD and JAR manifest are not equal and throw a exception and notify the server when a mismatch is found. Only used for trusted suites.

throws
InvalidJadException if the properties do not match


        for (int i = 0; i < state.jarProps.size(); i++) {
            String key = state.jarProps.getKeyAt(i);
            String value = state.jarProps.getValueAt(i);
            String dup = state.jadProps.getProperty(key);

            if (dup == null) {
                continue;
            }

            if (!dup.equals(value)) {
                postInstallMsgBackToProvider(ATTRIBUTE_MISMATCH_MSG);
                throw new InvalidJadException(
                    InvalidJadException.ATTRIBUTE_MISMATCH, key);
            }
        }
    
private voidcheckIfBasicAuthSupported(java.lang.String wwwAuthField)
Checks to make sure the HTTP server will support Basic authtication.

param
wwwAuthField WWW-Authenticate field from the response header
exception
InvalidJadException if server does not support Basic authentication

        wwwAuthField = wwwAuthField.trim();

        if (!wwwAuthField.regionMatches(true, 0, BASIC_TAG, 0,
                                        BASIC_TAG.length())) {
            throw new InvalidJadException(InvalidJadException.CANNOT_AUTH);
        }
    
private voidcheckPreviousVersion()
See if there is an installed version of the suite being installed and if so, make an necessary checks. Will set state fields, including the exception field for warning the user.

exception
InvalidJadException if the new version is formated incorrectly

        MIDletSuite midletSuite;
        String installedVersion;
        int cmpResult;
        
        // Check if app already exists
        midletSuite = getMIDletSuite(state.storageName);

        try {
            checkVersionFormat(state.version);

            if (midletSuite == null) {
                // there is no previous version
                return;
            }

            state.isPreviousVersion = true;
            state.previousSuite = midletSuite;

            // get this now so we do not have to save the whole suite object
            state.previousJadUrl = midletSuite.getJadUrl();
            state.previousJarUrl = midletSuite.getJarUrl();
            state.previousUrl = midletSuite.getDownloadUrl();
            state.previousCA = midletSuite.getCA();
            if (state.previousCA == null) {
                state.previousCA = Resource.getString("the manufacturer");
            }

            if (state.force) {
                // do not ask questions, force an overwrite
                return;
            }

            // If it does, check version information
            installedVersion = midletSuite.getProperty(VERSION_PROP);
            cmpResult = vercmp(state.version, installedVersion);
            if (cmpResult < 0) {
                // older version, warn user
                state.exception = new InvalidJadException(
                                  InvalidJadException.OLD_VERSION,
                                  installedVersion);
                return;
            }

            if (cmpResult == 0) {
                // already installed, warn user
                state.exception = new InvalidJadException(
                                  InvalidJadException.ALREADY_INSTALLED,
                                  installedVersion);
                return;
            }

            // new version, warn user
            state.exception = new InvalidJadException(
                                  InvalidJadException.NEW_VERSION,
                                  installedVersion);
            return;
        } catch (NumberFormatException nfe) {
            postInstallMsgBackToProvider(INVALID_JAD_MSG);
            throw new
                InvalidJadException(InvalidJadException.INVALID_VERSION);
        }
    
private voidcheckVersionFormat(java.lang.String ver)
Checks the format of a version string.

Versions must be in the form xxx.yyy.zzz, where:

xxx is the major version
yyy is the minor version
zzz is the micro version
It is acceptable to omit the micro and possibly the minor versions. If these are not included in the version string, the period immediately preceding the number must also be removed. So, the versions xxx.yyy or xxx are also valid.

Version numbers do not have to be three digits wide. However, you may pad versions with leading zeros if desired.

param
ver the version to check
exception
NumberFormatException if ver contains any characters that are not numbers or periods

        int length;
        int start = 0;
        int end;

        length = ver.length();
        for (int i = 0; ; i++) {
            // check for more than 3 parts or a trailing '.'
            if (i == 3 || start == length) {
                throw new NumberFormatException();
            }

            end = ver.indexOf('.", start);
            if (end == -1) {
                end = length;
            }

            // throws NFE if the substring is not all digits
            Integer.parseInt(ver.substring(start, end));

            if (end == length) {
                // we are done
                return;
            }

            // next time around start after the index of '.'
            start = end + 1;
        }
    
private voidcleanUp(java.lang.String storageName)
Removes the resources a software package given its storage name

param
storageName storage name for the installed package

        File file = new File(classSecurityToken);
        String storageRoot = File.getStorageRoot() + storageName;
        Vector files;
        int numberOfFiles;

        /*
         * Before removing the suite, check for registered push
         * connections that might need to be unregistered.
         */
        PushRegistryImpl.unregisterConnections(classSecurityToken,
                                               storageName);

        files = file.filenamesThatStartWith(storageRoot);
        numberOfFiles = files.size();
        for (int i = 0; i < numberOfFiles; i++) {
            try {
                file.delete((String)files.elementAt(i));
            } catch (IOException ioe) {
                // ignore
            }
        }
    
private byte[]downloadJAD()
Downloads an application descriptor file from the given URL.

return
a byte array representation of the file or null if not found
exception
IOException is thrown if any error prevents the download of the JAD

        String[] encoding = new String[1];
        ByteArrayOutputStream bos = new ByteArrayOutputStream(MAX_DL_SIZE);
        String[] acceptableTypes = {JAD_MT};
        String[] extraFieldKeys = new String[3];
        String[] extraFieldValues = new String[3];
        String locale;
        String prof = System.getProperty("microedition.profiles");
        int space = prof.indexOf(' ");
        if (space != -1) {
            prof = prof.substring(0, space);
        }


        extraFieldKeys[0] = "User-Agent";
        extraFieldValues[0] = "Profile/" + prof
                             + " Configuration/" +
                             System.getProperty("microedition.configuration");

        extraFieldKeys[1] = "Accept-Charset";
        extraFieldValues[1] = "UTF-8, " +
                              System.getProperty("microedition.encoding");

        /* locale can be null */
        locale = System.getProperty("microedition.locale");
        if (locale != null) {
            extraFieldKeys[2] = "Accept-Language";
            extraFieldValues[2] = locale;
        }

        state.beginTransferDataStatus = DOWNLOADING_JAD;
        state.transferStatus = DOWNLOADED_1K_OF_JAD;

        /*
         * Do not send the list of acceptable types because some servers
         * will send a 406 if the URL is to a JAR. It is better to
         * reject the resource at the client after check the media-type so
         * if the type is JAR a JAR only install can be performed.
         */
        downloadResource(state.jadUrl, extraFieldKeys, extraFieldValues,
                         acceptableTypes, false, false, bos, encoding,
                         InvalidJadException.INVALID_JAD_URL,
                         InvalidJadException.JAD_SERVER_NOT_FOUND,
                         InvalidJadException.JAD_NOT_FOUND,
                         InvalidJadException.INVALID_JAD_TYPE);

        state.jadEncoding = encoding[0];
        return bos.toByteArray();
    
private intdownloadJAR(java.lang.String filename)
Downloads an application archive file from the given URL into the given file. Automatically handle re-trys.

param
filename name of the file to write. This file resides in the storage area of the given application
return
size of the JAR
exception
IOException is thrown if any error prevents the download of the JAR

        HttpUrl parsedUrl;
        String url;
        String[] acceptableTypes = {JAR_MT_1, JAR_MT_2};
        int jarSize;

        RandomAccessStream jarOutputStream = null;
        OutputStream outputStream = null;

        parsedUrl = new HttpUrl(state.jarUrl);
        if (parsedUrl.authority == null && state.jadUrl != null) {
            // relative URL, add the JAD URL as the base
            try {
                parsedUrl.addBaseUrl(state.jadUrl);
            } catch (IOException e) {
                postInstallMsgBackToProvider(INVALID_JAD_MSG);
                throw new InvalidJadException(
                         InvalidJadException.INVALID_JAR_URL);
            }

            url = parsedUrl.toString();

            // The JAR URL saved to storage MUST be absolute
            state.jarUrl = url;
        } else {
            url = state.jarUrl;
        }

        jarOutputStream = new RandomAccessStream(classSecurityToken);
        jarOutputStream.connect(filename,
                                RandomAccessStream.READ_WRITE_TRUNCATE);
        outputStream = jarOutputStream.openOutputStream();

        try {
            state.beginTransferDataStatus = DOWNLOADING_JAR;
            state.transferStatus = DOWNLOADED_1K_OF_JAR;
            jarSize = downloadResource(url, null, null,
                         acceptableTypes, true, true, outputStream, null,
                         InvalidJadException.INVALID_JAR_URL,
                         InvalidJadException.JAR_SERVER_NOT_FOUND,
                         InvalidJadException.JAR_NOT_FOUND,
                         InvalidJadException.INVALID_JAR_TYPE);
            return jarSize;
        } catch (InvalidJadException ije) {
            switch (ije.getReason()) {
            case InvalidJadException.JAR_SERVER_NOT_FOUND:
            case InvalidJadException.JAR_NOT_FOUND:
                postInstallMsgBackToProvider(INVALID_JAD_MSG);
                break;

            case InvalidJadException.INVALID_JAR_TYPE:
                postInstallMsgBackToProvider(INVALID_JAR_MSG);
            }

            throw ije;
        } finally {
            try {
                jarOutputStream.disconnect();
            } catch (Exception e) {
                // ignore
            }
        }
    
private intdownloadResource(java.lang.String url, java.lang.String[] extraFieldKeys, java.lang.String[] extraFieldValues, java.lang.String[] acceptableTypes, boolean sendAcceptableTypes, boolean allowNoMediaType, java.io.OutputStream output, java.lang.String[] encoding, int invalidURLCode, int serverNotFoundCode, int resourceNotFoundCode, int invalidMediaTypeCode)
Downloads an resource from the given URL into the output stream.

param
url location of the resource to download
param
extraFieldKeys keys to the extra fields to put in the request
param
extraFieldValues values to the extra fields to put in the request
param
acceptableTypes list of acceptable media types for this resource, there must be at least one
param
sendAcceptableTypes if true the list of acceptable media types for this resource will be sent in the request
param
allowNoMediaType if true it is not consider an error if the media type is not in the response for this resource will be sent in the request
param
output output stream to write the resource to
param
encoding an array to receive the character encoding of resource, can be null
param
invalidURLCode reason code to use when the URL is invalid
param
serverNotFoundCode reason code to use when the server is not found
param
resourceNotFoundCode reason code to use when the resource is not found on the server
param
invalidMediaTypeCode reason code to use when the media type of the resource is not valid
return
size of the resource
exception
IOException is thrown if any error prevents the download of the resource

        Connection conn = null;
        StringBuffer acceptField;
        int responseCode;
        String retryAfterField;
        int retryInterval;
        String mediaType;

        try {
            for (; ; ) {
                try {
                    conn = Connector.open(url, Connector.READ);
                } catch (IllegalArgumentException e) {
                    throw new InvalidJadException(invalidURLCode, url);
                } catch (ConnectionNotFoundException e) {
                    // protcol not found
                    throw new InvalidJadException(invalidURLCode, url);
                }

                if (!(conn instanceof HttpConnection)) {
                    // only HTTP or HTTPS are supported
                    throw new InvalidJadException(invalidURLCode, url);
                }

                httpConnection = (HttpConnection)conn;

                if (extraFieldKeys != null) { 
                    for (int i = 0; i < extraFieldKeys.length &&
                            extraFieldKeys[i] != null; i++) {
                        httpConnection.setRequestProperty(extraFieldKeys[i],
                                                          extraFieldValues[i]);
                    }
                }
                    
                // 256 is given to avoid resizing without adding lengths
                acceptField = new StringBuffer(256);

                if (sendAcceptableTypes) {
                    // there must be one or more acceptable media types
                    acceptField.append(acceptableTypes[0]);
                    for (int i = 1; i < acceptableTypes.length; i++) {
                        acceptField.append(", ");
                        acceptField.append(acceptableTypes[i]);
                    }

                    httpConnection.setRequestProperty("Accept",
                                                      acceptField.toString());
                }

                httpConnection.setRequestMethod(HttpConnection.GET);

                if (state.username != null &&
                        state.password != null) {
                    httpConnection.setRequestProperty("Authorization",
                        formatAuthCredentials(state.username,
                                              state.password));
                }

                if (state.proxyUsername != null &&
                        state.proxyPassword != null) {
                    httpConnection.setRequestProperty("Proxy-Authorization",
                        formatAuthCredentials(state.proxyUsername,
                                              state.proxyPassword));
                }

                try {
                    responseCode = httpConnection.getResponseCode();
                } catch (IOException ioe) {
                    if (httpConnection.getHost() == null) {
                        throw new InvalidJadException(invalidURLCode, url);
                    }

                    throw new InvalidJadException(serverNotFoundCode, url);
                }

                if (responseCode != HttpConnection.HTTP_UNAVAILABLE) {
                    break;
                }

                retryAfterField = httpConnection.getHeaderField("Retry-After");
                if (retryAfterField == null) {
                    break;
                }

                try {
                    /*
                     * see if the retry interval is in seconds, and
                     * not an absolute date
                     */
                    retryInterval = Integer.parseInt(retryAfterField);
                    if (retryInterval > 0) {
                        if (retryInterval > 60) {
                            // only wait 1 min
                            retryInterval = 60;
                        }

                        Thread.sleep(retryInterval * 1000);
                    }
                } catch (InterruptedException ie) {
                    // ignore thread interrupt
                    break;
                } catch (NumberFormatException ne) {
                    // ignore bad format
                    break;
                }

                httpConnection.close();

                if (state.stopInstallation) {
                    postInstallMsgBackToProvider(USER_CANCELLED_MSG);
                    throw new IOException("stopped");
                }
            } // end for

            if (responseCode == HttpConnection.HTTP_NOT_FOUND) {
                throw new InvalidJadException(resourceNotFoundCode);
            }

            if (responseCode == HttpConnection.HTTP_NOT_ACCEPTABLE) {
                throw new InvalidJadException(invalidMediaTypeCode, "");
            }

            if (responseCode == HttpConnection.HTTP_UNAUTHORIZED) {
                // automatically throws the correct exception
                checkIfBasicAuthSupported(
                     httpConnection.getHeaderField("WWW-Authenticate"));

                state.exception = 
                    new InvalidJadException(InvalidJadException.UNAUTHORIZED);
                return 0;
            }

            if (responseCode == HttpConnection.HTTP_PROXY_AUTH) {
                // automatically throws the correct exception
                checkIfBasicAuthSupported(
                     httpConnection.getHeaderField("WWW-Authenticate"));

                state.exception =
                    new InvalidJadException(InvalidJadException.PROXY_AUTH);
                return 0;
            }

            if (responseCode != HttpConnection.HTTP_OK) {
                throw new IOException("Failed to download " + url +
                    " HTTP response code: " + responseCode);
            }

            mediaType = getMediaType(httpConnection.getType());
            if (mediaType != null) {
                boolean goodType = false;

                for (int i = 0; i < acceptableTypes.length; i++) {
                    if (mediaType.equals(acceptableTypes[i])) {
                        goodType = true;
                        break;
                    }
                }

                if (!goodType) {
                    throw new InvalidJadException(invalidMediaTypeCode,
                                                  mediaType);
                }
            } else if (!allowNoMediaType) {
                throw new InvalidJadException(invalidMediaTypeCode, "");
            }

            if (encoding != null) {
                encoding[0] = getCharset(httpConnection.getType());
            }

            httpInputStream = httpConnection.openInputStream();
            return transferData(httpInputStream, output);
        } finally {
            // Close the streams or connections this method opened.
            try {
                httpInputStream.close();
            } catch (Exception e) {
                // ignore
            }
                    
            try {
                conn.close();
            } catch (Exception e) {
                // ignore
            }
        }
    
public booleanexecute(java.lang.String storageName, java.lang.String midlet)
Queues the execution of the named Application suite to run. The current application suite should terminate itself normally to make resources available to the new application suite. Only one package and set of MIDlets can be queued in this manner. If multiple calls to execute are made, the package and MIDlets specified during the last invokation will be executed when the current application is terminated.

param
storageName of an installed package
param
midlet name of MIDlet to invoke
return
true if the current application MUST exit before the new application suite can be executed
exception
SecurityException if the caller does not have permission to execute the installed package


        nextMidletSuiteToRun = storageName;
        nextMidletToRun = midlet; 
        return true;
    
private static java.lang.StringformatAuthCredentials(java.lang.String username, java.lang.String password)
Formats the username and password for HTTP basic authentication according RFC 2617.

param
username for HTTP authentication
param
password for HTTP authentication
return
properly formated basic authentication credential

        byte[] data = new byte[username.length() + password.length() + 1];
        int j = 0;

        for (int i = 0; i < username.length(); i++, j++) {
            data[j] = (byte)username.charAt(i);
        }

        data[j] = (byte)':";
        j++;

        for (int i = 0; i < password.length(); i++, j++) {
            data[j] = (byte)password.charAt(i);
        }

        return "Basic " + Base64.encode(data, 0, data.length);
    
java.lang.StringgetCA(java.lang.String storageRoot)
Gets the CA of an installed package.

param
storageRoot storage root name for the installed package it should have any file separators need
return
string with the CN of the CA or null is returned if no value is available

        RandomAccessStream myStorage;
        DataInputStream dataStream;
        String ca;

        myStorage = new RandomAccessStream(classSecurityToken);

        try {
            myStorage.connect(storageRoot + CA_FILENAME,
                              Connector.READ);
            try {
                dataStream = myStorage.openDataInputStream();
                ca = dataStream.readUTF();
            } finally {
                myStorage.disconnect();
            }

            return ca;
        } catch (IOException ioe) {
            return null;
        }
    
private static java.lang.StringgetCharset(java.lang.String contentType)
Parses out the charset from the content-type field. The charset parameter is after the ';' in the content-type field.

param
contentType value of the content-type field
return
charset

        int start;
        int end;

        if (contentType == null) {
            return null;
        }

        start = contentType.indexOf("charset");
        if (start < 0) {
            return null;
        }

        start = contentType.indexOf('=", start);
        if (start < 0) {
            return null;
        }

        // start past the '='
        start++;

        end = contentType.indexOf(';", start);
        if (end < 0) {
            end = contentType.length();
        }
            
        return contentType.substring(start, end).trim();
    
private java.util.VectorgetDeleteNotifyURLList()
Retrieves the queued delete notification list. Each element in the Vector is the URL to send the delete notification to.

return
the delete notification list stored in a Vector

        RandomAccessStream storage;
        DataInputStream in;
        String line;

        if (deleteNotifyURLList != null) {
            return deleteNotifyURLList;
        }

        deleteNotifyURLList = new Vector();
        try {
            storage = new RandomAccessStream(classSecurityToken);

            storage.connect(File.getStorageRoot() + DELETE_NOTIFY_FILENAME, 
                            Connector.READ);
            try {
                in = storage.openDataInputStream();
                for (; ; ) {
                    line = in.readUTF();
                    if (line == null) {
                        break;
                    }

                    deleteNotifyURLList.addElement(line);
                }
            } finally {
                storage.disconnect();
            }
        } catch (IOException e) {
            /* will happen the first time, because it will not exist */
        }

        return deleteNotifyURLList;
    
protected byte[][]getInitialPermissions(java.lang.String domain)
Builds the initial API permission for suite currently being installed.

param
domain security domain name for the CA of the suite
return
2 arrays, maximum and current level of permissions
exception
InvalidJadException if a permission attribute is not formatted properly or a required permission is denied

        byte[][] domainPermissions = Permissions.forDomain(classSecurityToken,
                                                           domain);
        byte[] permissions = Permissions.getEmptySet();

        // only the current level of each permission has to be adjusted
        getRequestedPermissions(PERMISSIONS_PROP,
                                domainPermissions[Permissions.CUR_LEVELS],
                                permissions, true);

        getRequestedPermissions(PERMISSIONS_OPT_PROP,
                                domainPermissions[Permissions.CUR_LEVELS],
                                permissions, false);

        domainPermissions[Permissions.CUR_LEVELS] = permissions;
        return domainPermissions;
    
public static com.sun.midp.midletsuite.InstallergetInstaller()
Returns a reference to the singleton Installer object.

return
the installer reference
exception
SecurityException if the caller does not have permission to install software

        MIDletSuite midletSuite = Scheduler.getScheduler().getMIDletSuite();

        // if a MIDlet suite is not scheduled, assume the JAM is calling.
        if (midletSuite != null) {
            midletSuite.checkIfPermissionAllowed(Permissions.AMS);
        }

        return getInstallerCommon();
    
public static com.sun.midp.midletsuite.InstallergetInstaller(com.sun.midp.security.SecurityToken securityToken)
Returns a reference to the singleton Installer object.

param
securityToken security token of the calling class
return
the installer reference
exception
SecurityException if the caller does not have permission to install software

        securityToken.checkIfPermissionAllowed(Permissions.AMS);

        return getInstallerCommon();
    
private static com.sun.midp.midletsuite.InstallergetInstallerCommon()
Returns a reference to the singleton Installer object.

return
the installer reference

        String nameOfInstaller;

        if (myInstaller != null) {
            return myInstaller;
        }

        /*
         * In internal.config an alternate installer, such as a
         * SecureInstaller can be specified.
         */
        nameOfInstaller = Configuration.getProperty(
                              "com.sun.midp.midletsuite.installer");
        if (nameOfInstaller == null) {
            myInstaller = new SecureInstaller();
            return myInstaller;
        }
        
        try {
            myInstaller = 
                (Installer)Class.forName(nameOfInstaller).newInstance();
            return myInstaller;
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
    
public com.sun.midp.midlet.MIDletSuitegetMIDletSuite(java.lang.String storageName)
Gets the MIDlet Suite from storage.

param
storageName given to the suite by the installer when it was downloaded
return
MIDlet Suite for use by the Scheduler

        return getMIDletSuite(storageName, null);
    
public com.sun.midp.midlet.MIDletSuitegetMIDletSuite(java.lang.String storageName, java.lang.String midletToRun)
Gets the MIDlet Suite from storage, and selects one midlet to be run.

param
storageName the unique name of the suite given by the installer when it was downloaded
param
midletToRun the name of the initial MIDlet in this suite to run, can be null
return
MIDlet Suite for use by the Scheduler

        File file = new File(classSecurityToken);
        String storageRoot = File.getStorageRoot() + storageName;
        String ca;

        if (!suiteExists(storageName)) {
            return null;
        }

        ca = getCA(storageRoot);
        return new MIDletSuiteImpl(classSecurityToken,
                                   storageRoot,
                                   storageName,
                                   ca,
                                   midletToRun);
    
public com.sun.midp.midlet.MIDletSuitegetMIDletSuite(java.lang.String storageName, int midletToRun)
Gets the MIDlet Suite from storage, and selects one midlet to be run.

param
storageName the unique name of the suite given by the installer when it was downloaded
param
midletToRun the number of the initial MIDlet in this suite to run
return
MIDlet Suite for use by the Scheduler

        File file = new File(classSecurityToken);
        String storageRoot = File.getStorageRoot() + storageName;
        String ca;

        if (!suiteExists(storageName)) {
            return null;
        }

        ca = getCA(storageRoot);
        return new MIDletSuiteImpl(classSecurityToken,
                                   storageRoot,
                                   storageName,
                                   ca,
                                   midletToRun);
    
private static java.lang.StringgetMediaType(java.lang.String contentType)
Parses out the media-type from the content-type field and convert to lower case. The media-type everything for the ';' that marks the parameters.

param
contentType value of the content-type field
return
media-type in lower case

        int semiColon;

        if (contentType == null) {
            return null;
        }

        semiColon = contentType.indexOf(';");
        if (semiColon < 0) {
            return contentType.toLowerCase();
        }

        return contentType.substring(0, semiColon).toLowerCase();
    
public java.lang.StringgetNextMIDletSuiteToRun()
Gets the unique storage name of the next MIDlet suite to run.

return
storage name of a MIDlet suite

        return nextMidletSuiteToRun;
    
public java.lang.StringgetNextMIDletToRun()
Gets the name of the next MIDlet to run.

return
storage name of a MIDlet

        return nextMidletToRun; 
    
private voidgetRequestedPermissions(java.lang.String propName, byte[] domainPermissions, byte[] permissions, boolean required)
Gets the permissions for a domain that are requested the manifest.

param
propName name of the property in the manifest
param
domainPermissions array of the starting levels for permissions of a domain
param
permissions array to put the permissions from the domain in when found in the manifest property
param
required if set to true the manifest permissions are required
exception
InvalidJadException if a permission attribute is not formatted properly or a required permission is denied


        String reqPermissionLine;
        Vector manifestPermissions;
        String permission;
        boolean found;
        int i;

        reqPermissionLine = state.getAppProperty(propName);
        if (reqPermissionLine == null) {
            return;
        }

        manifestPermissions = Util.getCommaSeparatedValues(reqPermissionLine);
        if (manifestPermissions.size() == 0) {
            postInstallMsgBackToProvider(INVALID_JAD_MSG);
            throw new
                InvalidJadException(InvalidJadException.INVALID_VALUE);
        }

        for (int j = 0; j < manifestPermissions.size(); j++) {
            permission = (String)manifestPermissions.elementAt(j);

            if (permission.length() == 0) {
                postInstallMsgBackToProvider(INVALID_JAD_MSG);
                throw new
                    InvalidJadException(InvalidJadException.INVALID_VALUE);
            }

            found = false;
            for (i = 0; i < Permissions.NUMBER_OF_PERMISSIONS; i++) {
                if (Permissions.getName(i).equals(permission)) {
                    if (domainPermissions[i] != Permissions.NEVER) {
                        found = true;
                    }

                    break;
                }
            }

            if (!found) {
                if (required) {
                    postInstallMsgBackToProvider(AUTHORIZATION_FAILURE_MSG);
                    throw new InvalidJadException(
                        InvalidJadException.AUTHORIZATION_FAILURE, permission);
                }

                continue;
            }

            permissions[i] = domainPermissions[i];
        }
    
protected java.lang.StringgetSecurityDomainName(java.lang.String storageName, java.lang.String ca)
Gets the security domain name for this MIDlet Suite from storage.

param
storageName given to the suite by the installer when it was downloaded
param
ca CA of an installed suite
return
name of the security domain for the MIDlet Suite

        // This is a method that is overridden by a secure installer.
        return Permissions.UNTRUSTED_DOMAIN_NAME;
    
private java.util.VectorgetSuiteList()
Retrieves the list of MIDlet suites and store them into a Vector object. Each element in the Vector is the storage name of an installed application.

return
the list of installed suites stored in a Vector

        RandomAccessStream storage;
        DataInputStream in;
        String line;

        if (midletSuiteList != null) {
            return midletSuiteList;
        }

        midletSuiteList = new Vector();
        try {
            storage = new RandomAccessStream(classSecurityToken);

            storage.connect(File.getStorageRoot() +
                           SUITE_LIST_FILENAME, Connector.READ);
            try {
                in = storage.openDataInputStream();
                for (; ; ) {
                    line = in.readUTF();
                    if (line == null) {
                        break;
                    }

                    midletSuiteList.addElement(line);
                }
            } finally {
                storage.disconnect();
            }
        } catch (IOException e) {
            /* will happen the first time, because it will not exist */
        }

        return midletSuiteList;
    
public static voidinitSecurityToken(com.sun.midp.security.SecurityToken token)
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.


                                  
         
        if (classSecurityToken != null) {
            return;
        }

        classSecurityToken = token;
        MIDletSuiteImpl.initSecurityToken(classSecurityToken);
    
public java.lang.StringinstallJad(java.lang.String location, boolean force, boolean removeRMS, InstallListener installListener)
Installs a software package from the given URL. The URL is assumed refer to an application descriptor.

If the component to be installed is the same as an existing component (by comparing the MIDlet-Name, MIDlet-Vendor attributes) then this install is an upgrade if the version number is greater than the current version. If so, the new version replaces in its entirety the current version.

It is implementation dependent when the upgraded component is made available for use.

The implementation of install must be robust in the presence of failures such as running out of memory. If this method throws an exception then the package must not be installed and any previous version of the component must be left intact and operational.

To receive status updates and installer warnings, provide an install listener. If no listener is provided all warnings will be thrown as exceptions.

param
location the URL from which the application descriptor can be updated
param
force if true the MIDlet suite components to be installed will overwrite any existing components without any version comparison
param
removeRMS if true and existing RMS data will be removed when overwriting an existing suite
param
installListener object to receive status updates and install warnings, can be null
return
the unique name of the installed package.
exception
ConnectionNotFoundException if JAD URL is invalid
exception
IOException is thrown if any error prevents the installation of the MIDlet suite, including being unable to access the application descriptor or JAR
exception
InvalidJadException if the downloaded application descriptor is invalid
exception
SecurityException if the caller does not have permission to install software
exception
IllegalArgumentException is thrown, if the location of the descriptor file is not specified


        state = new InstallStateImpl();

        state.jadUrl = location;
        state.force = force;
        state.removeRMS = removeRMS;
        state.nextStep = 1;
        state.listener = installListener;

        return performInstall();
    
public java.lang.StringinstallJar(java.lang.String location, boolean force, boolean removeRMS, InstallListener installListener)
Installs a software package from the given URL. The URL is assumed refer to a JAR.

If the component to be installed is the same as an existing component (by comparing the MIDlet-Name, MIDlet-Vendor attributes) then this install is an upgrade if the version number is greater than the current version. If so, the new version replaces in its entirety the current version.

It is implementation dependent when the upgraded component is made available for use.

The implementation of install must be robust in the presence of failures such as running out of memory. If this method throws an exception then the package must not be installed and any previous version of the component must be left intact and operational.

To receive status updates and installer warnings, provide an install listener. If no listener is provided all warnings will be thrown as exceptions.

param
location the URL from which the JAR can be updated
param
force if true the MIDlet suite components to be installed will overwrite any existing components without any version comparison
param
removeRMS if true and existing RMS data will be removed when overwriting an existing suite
param
installListener object to receive status updates and install warnings, can be null
return
the unique name of the installed package.
exception
IOException is thrown if any error prevents the installation of the MIDlet suite, including being unable to access the JAR
exception
InvalidJadException if the downloaded JAR is invalid
exception
SecurityException if the caller does not have permission to install software
exception
IllegalArgumentException is thrown, if the location of the JAR specified


        if (location == null || location.length() == 0) {
            throw
                new IllegalArgumentException("Must specify URL of .jar file");
        }

        state = new InstallStateImpl();

        state.jarUrl = location;
        state.force = force;
        state.removeRMS = removeRMS;
        state.nextStep = 4;
        state.listener = installListener;

        return performInstall();
    
private voidinstallStep1()
Downloads the JAD, save it in the install state. Parse the JAD, make sure it has the required properties, and save them in the install state.

exception
IOException is thrown, if an I/O error occurs during descriptor or jar file download
exception
InvalidJadException is thrown, if the descriptor file is not properly formatted or does not contain the required attributes
exception
IllegalArgumentException is thrown, if the descriptor file is not specified

        if (state.jadUrl == null || state.jadUrl.length() == 0) {
            throw
                new IllegalArgumentException("Must specify URL of .jad file");
        }

        state.jad = downloadJAD();
        if (state.exception != null) {
            return;
        }

        state.jadProps = new JadProperties();
        try {
            state.jadProps.load(new ByteArrayInputStream(state.jad),
                state.jadEncoding);
        } catch (InvalidJadException ije) {
            postInstallMsgBackToProvider(INVALID_JAD_MSG);
            throw ije;
        }

        state.suiteName = state.jadProps.getProperty(SUITE_NAME_PROP);
        if (state.suiteName == null || state.suiteName.length() == 0) {
            postInstallMsgBackToProvider(INVALID_JAD_MSG);
            throw new
                InvalidJadException(InvalidJadException.MISSING_SUITE_NAME);
        }

        state.vendor = state.jadProps.getProperty(VENDOR_PROP);
        if (state.vendor == null || state.vendor.length() == 0) {
            postInstallMsgBackToProvider(INVALID_JAD_MSG);
            throw new
                InvalidJadException(InvalidJadException.MISSING_VENDOR);
        }

        state.version = state.jadProps.getProperty(VERSION_PROP);
        if (state.version == null || state.version.length() == 0) {
            postInstallMsgBackToProvider(INVALID_JAD_MSG);
            throw new
                InvalidJadException(InvalidJadException.MISSING_VERSION);
        }

        state.storageName = makeStorageName(state.vendor, state.suiteName);

        checkPreviousVersion();
        state.nextStep++;
    
private voidinstallStep2()
If the JAD belongs to an installed suite, check the URL against the installed one.

        state.nextStep++;

        if (state.isPreviousVersion) {
            checkForDifferentDomains(state.jadUrl);
        }
    
private voidinstallStep3()
Makes sure the suite can fit in storage and confirm installation with the user.

exception
IOException is thrown, if an I/O error occurs during descriptor or jar file download
exception
InvalidJadException is thrown, if the descriptor file is not properly formatted or does not contain the required

        String sizeString;
        int dataSize;
        int suiteSize;

        sizeString = state.jadProps.getProperty(JAR_SIZE_PROP);
        if (sizeString == null) {
            postInstallMsgBackToProvider(INVALID_JAD_MSG);
            throw new
                InvalidJadException(InvalidJadException.MISSING_JAR_SIZE);
        }
        
        try {
            state.expectedJarSize = Integer.parseInt(sizeString);
        } catch (NumberFormatException e) {
            postInstallMsgBackToProvider(INVALID_JAD_MSG);
            throw new
                InvalidJadException(InvalidJadException.INVALID_VALUE);
        }

        sizeString = state.jadProps.getProperty(DATA_SIZE_PROP);
        if (sizeString == null) {
            dataSize = 0;
        } else {
            try {
                dataSize = Integer.parseInt(sizeString);
            } catch (NumberFormatException e) {
                postInstallMsgBackToProvider(INVALID_JAD_MSG);
                throw new
                    InvalidJadException(InvalidJadException.INVALID_VALUE);
            }
        }

        /*
         * A suite is a jad + jar + manifest + url + data size.
         * lets say the manifest is the same size as the jad
         * since we do know at this point. the size is in bytes,
         * UTF-8 chars can be upto 3 bytes
         */
        suiteSize = state.expectedJarSize + (state.jad.length * 2) +
                    (state.jadUrl.length() * 3) + dataSize;
        
        state.file = new File(classSecurityToken);

        if (suiteSize > state.file.getBytesAvailableForFiles()) {
            postInstallMsgBackToProvider(INSUFFICIENT_MEM_MSG);

            // the size reported to the user should be in K and rounded up
            throw new
                InvalidJadException(InvalidJadException.INSUFFICIENT_STORAGE,
                    Integer.toString((suiteSize + 1023)/ 1024));
        }

        state.jarUrl = state.jadProps.getProperty(JAR_URL_PROP);
        if (state.jarUrl == null || state.jarUrl.length() == 0) {
            postInstallMsgBackToProvider(INVALID_JAD_MSG);
            throw new
                InvalidJadException(InvalidJadException.MISSING_JAR_URL);
        }

        if (state.listener != null &&
                !state.listener.confirmJarDownload(state)) {
            state.stopInstallation = true;
            postInstallMsgBackToProvider(USER_CANCELLED_MSG);
            throw new IOException("stopped");
        }

        state.nextStep++;
    
private voidinstallStep4()
Downloads the JAR, make sure it is the correct size, make sure the required attributes match the JAD's. Then store the application.

exception
IOException is thrown, if an I/O error occurs during descriptor or jar file download
exception
InvalidJadException is thrown, if the descriptor file is not properly formatted or does not contain the required

        int bytesDownloaded;
        MIDletInfo midletInfo;
        String midlet;
        String classfilename;


        // Save jar file to temp name; we need to do this to read the
        // manifest entry, but, don't want to overwrite an exisiting
        // application in case there are problems with the manifest
        state.storageRoot = File.getStorageRoot();
        state.tempFilename = state.storageRoot + TMP_FILENAME;

        bytesDownloaded = downloadJAR(state.tempFilename);

        if (state.exception != null) {
            return;
        }

        if (state.listener != null) {
            state.listener.updateStatus(VERIFYING_SUITE, state);
        }

        try {
            state.storage = new RandomAccessStream(classSecurityToken);

            verifyJar(state.storage, state.tempFilename);

            // Create JAR Properties (From .jar file's MANIFEST
            try {
                state.manifest = JarReader.readJarEntry(classSecurityToken,
                                     state.tempFilename, JAR_MANIFEST);
                if (state.manifest == null) {
                    postInstallMsgBackToProvider(INVALID_JAR_MSG);
                    throw new
                        InvalidJadException(InvalidJadException.CORRUPT_JAR,
                                            JAR_MANIFEST);
                }
            } catch (IOException ioe) {
                postInstallMsgBackToProvider(INVALID_JAR_MSG);
                throw new
                    InvalidJadException(InvalidJadException.CORRUPT_JAR,
                                        JAR_MANIFEST);
            }

            state.jarProps = new ManifestProperties();

            try {
                state.jarProps.load(new ByteArrayInputStream(state.manifest));
            } catch (InvalidJadException ije) {
                postInstallMsgBackToProvider(INVALID_JAR_MSG);
                throw ije;
            }

            for (int i = 1; ; i++) {
                midlet = state.getAppProperty("MIDlet-" + i);
                if (midlet == null) {
                    break;
                }
		    
                midletInfo = new MIDletInfo(midlet);
                classfilename = midletInfo.classname.replace('.", '/") +
                                ".class";

                try {
		    /* Attempt to read the MIDlet from the JAR file. */
		    if (JarReader.readJarEntry(classSecurityToken,
                            state.tempFilename, classfilename) == null) {
                        postInstallMsgBackToProvider(INVALID_JAR_MSG);
                        throw new InvalidJadException(
                            InvalidJadException.CORRUPT_JAR, classfilename);
                    }
                } catch (IOException ioe) {
                    postInstallMsgBackToProvider(INVALID_JAR_MSG);
                    throw new
                        InvalidJadException(InvalidJadException.CORRUPT_JAR,
                                            classfilename);
                }
            }

            // move on to the next step after a warning
            state.nextStep++;

            // Check Manifest entries against .jad file
            if (state.jadUrl != null) {
                if (bytesDownloaded != state.expectedJarSize) {
                    postInstallMsgBackToProvider(JAR_SIZE_MISMATCH_MSG);
                    throw new  InvalidJadException(
                        InvalidJadException.JAR_SIZE_MISMATCH);
                }

                if (!state.suiteName.equals(
                        state.jarProps.getProperty(SUITE_NAME_PROP))) {
                    postInstallMsgBackToProvider(ATTRIBUTE_MISMATCH_MSG);
                    throw new InvalidJadException(
                        InvalidJadException.SUITE_NAME_MISMATCH);
                }

                if (!state.version.equals(
                        state.jarProps.getProperty(VERSION_PROP))) {
                    postInstallMsgBackToProvider(ATTRIBUTE_MISMATCH_MSG);
                    throw new InvalidJadException(
                         InvalidJadException.VERSION_MISMATCH);
                }

                if (!state.vendor.equals(
                        state.jarProps.getProperty(VENDOR_PROP))) {
                    postInstallMsgBackToProvider(ATTRIBUTE_MISMATCH_MSG);
                    throw new InvalidJadException(
                         InvalidJadException.VENDOR_MISMATCH);
                }
            } else {
                state.file = new File(classSecurityToken);

                state.suiteName = state.jarProps.getProperty(SUITE_NAME_PROP);
                if (state.suiteName == null ||
                        state.suiteName.length() == 0) {
                    postInstallMsgBackToProvider(INVALID_JAR_MSG);
                    throw new InvalidJadException(
                         InvalidJadException.MISSING_SUITE_NAME);
                }

                state.vendor = state.jarProps.getProperty(VENDOR_PROP);
                if (state.vendor == null || state.vendor.length() == 0) {
                    postInstallMsgBackToProvider(INVALID_JAR_MSG);
                    throw new InvalidJadException(
                         InvalidJadException.MISSING_VENDOR);
                }

                state.version = state.jarProps.getProperty(VERSION_PROP);
                if (state.version == null || state.version.length() == 0) {
                    postInstallMsgBackToProvider(INVALID_JAR_MSG);
                    throw new InvalidJadException(
                         InvalidJadException.MISSING_VERSION);
                }
                
                try {
                    checkVersionFormat(state.version);
                } catch (NumberFormatException nfe) {
                    postInstallMsgBackToProvider(INVALID_JAR_MSG);
                    throw new InvalidJadException(
                         InvalidJadException.INVALID_VERSION);
                }

                // NYI if already installed, check the domain of the JAR URL

                state.storageName = makeStorageName(state.vendor,
                                                    state.suiteName);

                checkPreviousVersion();
            }
        } catch (Exception e) {
            state.file.delete(state.tempFilename);

            if (e instanceof IOException) {
                throw (IOException)e;
            }

            throw (RuntimeException)e;
        }
    
private voidinstallStep5()
If the JAR belongs to an installed suite if there was no JAD, check the URL against the installed one.

        state.nextStep++;

        if (state.jadUrl == null && state.isPreviousVersion) {
            checkForDifferentDomains(state.jarUrl);
        }
    
private voidinstallStep6()
Checks the permissions and store the suite.

exception
IOException is thrown, if an I/O error occurs during storing the suite
exception
InvalidJadException is thrown, if the there is permission problem

        String suiteStorageRoot;
        DataOutputStream storageStream;
        String securityDomain;

        try {            
            if (state.ca != null) {
                // suite was signed
                securityDomain = getSecurityDomainName(state.storageRoot,
                                                       state.ca);
            } else {
                securityDomain = unsignedSecurityDomain;
            }

            /*
             * untrusted and test domains do not check for requested
             * permissions
             */
            if (Permissions.UNTRUSTED_DOMAIN_NAME.equals(securityDomain)) {
                if (state.isPreviousVersion &&
                        state.previousSuite.isTrusted()) {
                    // Do not overwrite trusted suites with untrusted ones
                    postInstallMsgBackToProvider(AUTHORIZATION_FAILURE_MSG);
                    throw new InvalidJadException(
                        InvalidJadException.TRUSTED_OVERWRITE_FAILURE,
                        state.previousCA);
                }                    

                state.permissions = Permissions.forDomain(classSecurityToken,
                                                          securityDomain);

                /*
                 * To keep public key management simple, there is only one
                 * trusted keystore. So it is possible that the CA for
                 * the suite is untrusted. This may be done on purpose for
                 * testing. This is OK, but do not confuse the user by saying
                 * the untrusted suite is authorized, so set the CA name to
                 * null.
                 */
                state.ca = null;
            } else {
                /*
                 * For trusted suites, make sure an properties duplicated in
                 * both the manifest and JAD are the same.
                 */
                if (state.jadUrl != null) {
                    checkForJadManifestMismatches();
                }

                state.trusted = true;
                state.permissions = getInitialPermissions(securityDomain);
            }

            if (state.isPreviousVersion) {
                applyCurrentUserLevelPermissions(
                  state.previousSuite.getPermissions()[Permissions.CUR_LEVELS],
                    state.permissions[Permissions.MAX_LEVELS],
                    state.permissions[Permissions.CUR_LEVELS]);

                if (state.removeRMS) {
                    // override suite comparisons, just remove RMS
                    RecordStoreFile.removeRecordStoresForSuite(
                        classSecurityToken,
                        state.storageRoot + state.storageName);
                } else {
                    processPreviousRMS();
                }
            }

            state.securityToken = new SecurityToken(classSecurityToken,
                                                    state.permissions);

            checkConfiguration();
            matchProfile();

            suiteStorageRoot = state.storageRoot + state.storageName;

            // make sure at least 1 second has passed
            while (System.currentTimeMillis() - state.startTime < 1000);

            synchronized (state) {
                // this is the point of no return, one last check
                if (state.stopInstallation) {
                    postInstallMsgBackToProvider(USER_CANCELLED_MSG);
                    throw new IOException("stopped");
                }

                if (state.listener != null) {
                    state.listener.updateStatus(STORING_SUITE, state);
                }

                state.writingSuite = true;
            }

            registerPushConnections();

        } catch (Exception e) {
            state.file.delete(state.tempFilename);
            if (e instanceof IOException) {
                throw (IOException)e;
            }

            throw (RuntimeException)e;
        }

        try {
            state.file.rename(state.tempFilename,
                              suiteStorageRoot + JAR_FILENAME);
            
            state.storage.connect(suiteStorageRoot + MANIFEST_FILENAME,
                              RandomAccessStream.READ_WRITE_TRUNCATE);
            state.storage.writeBytes(state.manifest, 0, state.manifest.length);
            state.storage.disconnect();

            if (state.file.exists(suiteStorageRoot + JAD_URL_FILENAME)) {
                // erase the old JAD URL since we did not get new one
                state.file.delete(suiteStorageRoot + JAD_URL_FILENAME);
                state.file.delete(suiteStorageRoot + JAD_FILENAME);
            }

            if (state.jadUrl != null) {
                // Save JAD, JAR & manifest files
                state.storage.connect(suiteStorageRoot + JAD_FILENAME, 
                                  RandomAccessStream.READ_WRITE_TRUNCATE);
                state.storage.writeBytes(state.jad, 0, state.jad.length);
                state.storage.disconnect();

                // convert the JAD URL to UTF8 and write it to storage
                state.storage.connect(suiteStorageRoot + JAD_URL_FILENAME,
                                  RandomAccessStream.READ_WRITE_TRUNCATE);
                storageStream = state.storage.openDataOutputStream();
                storageStream.writeUTF(state.jadUrl);
                storageStream.close();
                state.storage.disconnect();
            }

            // convert the JAR URL to UTF8 and write it to storage
            state.storage.connect(suiteStorageRoot + JAR_URL_FILENAME,
                                  RandomAccessStream.READ_WRITE_TRUNCATE);
            storageStream = state.storage.openDataOutputStream();
            storageStream.writeUTF(state.jarUrl);
            storageStream.close();
            state.storage.disconnect();

            if (state.jadEncoding != null) {
                // convert the JAD encoding to UTF8 and write it to storage
                state.storage.connect(suiteStorageRoot + JAD_ENCODING_FILENAME,
                                  RandomAccessStream.READ_WRITE_TRUNCATE);
                storageStream = state.storage.openDataOutputStream();
                storageStream.writeUTF(state.jadEncoding);
                storageStream.close();
                state.storage.disconnect();
            } else if (state.file.exists(suiteStorageRoot +
                                         JAD_ENCODING_FILENAME)) {
                // erase the old encoding since we did not get new one
                state.file.delete(suiteStorageRoot + JAD_ENCODING_FILENAME);
            }

            // todo get the jad encoding and write to a file
            if (state.ca != null) {
                // convert the domain owner to UTF8 and write it to storage
                state.storage.connect(suiteStorageRoot + CA_FILENAME,
                                  RandomAccessStream.READ_WRITE_TRUNCATE);
                storageStream = state.storage.openDataOutputStream();
                storageStream.writeUTF(state.ca);
                storageStream.close();
                state.storage.disconnect();
            }

            saveSuiteSettings(classSecurityToken, suiteStorageRoot,
                state.pushInterruptSetting, state.permissions, state.trusted);

            addToSuiteList(state.storageName);

            state.nextStep++;

            try {
                postInstallMsgBackToProvider(SUCCESS_MSG);
            } catch (RuntimeException re) {
                /*
                 * The suite is successfully installed, but the post of the
                 * status message failed. Do not let this failure prevent 
                 * the suite from being used.
                 */
            }

            return;
        } catch (Exception e) {
            try {
                state.storage.disconnect();
            } catch (IOException ex) {
                // ignore
            }

            cleanUp(state.storageName);

            if (e instanceof IOException) {
                throw (IOException)e;
            }

            throw (RuntimeException)e;
        }
    
public java.lang.String[]list()
List all installed software packages by storage name.

return
an array of Strings of the storage names for the installed packages
exception
SecurityException if the caller does not have permission to see what software is installed

        Vector suiteList = getSuiteList();

        String[] array = new String[suiteList.size()];
        for (int i = 0; i < suiteList.size(); i++) {
            array[i] = (String)suiteList.elementAt(i);
        }

        return array;
    
public static java.lang.StringmakeStorageName(java.lang.String vendorName, java.lang.String appName)
Returns a storage identifier of an application.

param
vendorName name of the vendor that created the application, as given in a JAD file
param
appName name of the application, as given in a JAD file
return
the platform-specific storage name of the application given by vendorName and appName

        return File.unicodeToAsciiFilename(vendorName) + '_" +
            File.unicodeToAsciiFilename(appName) + '_";
    
private voidmatchProfile()
Tries to match one of the supported profiles with a profile listed in string of profiles separated by a space. Send a message back to the server if a match is not found and throw an exception.

exception
InvalidJadException if there is no match

        String profiles = state.getAppProperty(PROFILE_PROP);

        if (profiles == null || profiles.length() == 0) {
            postInstallMsgBackToProvider(INVALID_JAR_MSG);
            throw new
                InvalidJadException(InvalidJadException.MISSING_PROFILE);
        }

        // build the list of supported profiles if needed
        if (supportedProfiles == null) {
            int start;
            int nextSpace = -1;
            // need to call trim to remove trailing spaces
            String meProfiles = 
                System.getProperty("microedition.profiles").trim();
            supportedProfiles = new Vector();

            for (; ; ) {
                start = nextSpace + 1;
                nextSpace = meProfiles.indexOf(' ", start);

                // consecutive spaces, keep searching
                if (nextSpace == start) {
                    continue;
                }

                if ((nextSpace < 0)) {
                    supportedProfiles.addElement(
                        meProfiles.substring(start, meProfiles.length()));
                    break;
                }

                supportedProfiles.addElement(
                    meProfiles.substring(start, nextSpace));

            }
        }            
            
        /*
         * for each profiles listed in MicroEdition-Profile, we need to
         * find a matching profile in microedition.profiles.
         */
        int current = 0;
        int nextSeparatorIndex = 0;
        String requestedProfile;
        boolean supported = false;

        // convert tab to space so that the parsing later is simplified
        StringBuffer tmp = new StringBuffer(profiles);
        boolean modified = false;
        while ((nextSeparatorIndex = profiles.indexOf('\t", current)) != -1) {
            tmp.setCharAt(nextSeparatorIndex, ' ");
            current++;
            modified = true;
        }

        if (modified) {
            profiles = tmp.toString();
        }
            
        // reset the indices
        current = nextSeparatorIndex = 0;
        do {
            // get the next requested profiles
            nextSeparatorIndex = profiles.indexOf(' ", current);

            if (nextSeparatorIndex == current) {
                // consecutive spaces, keep searching
                current++;
                continue;
            }

            if (nextSeparatorIndex == -1) {
                // last (or the only one) value in the list
                requestedProfile = 
                   profiles.substring(current, profiles.length());
            } else {
                requestedProfile = 
                    profiles.substring(current, nextSeparatorIndex);
                current = nextSeparatorIndex + 1;
            }

            /* 
             * try to match each requested profiles against the supported
             * ones.
             */
            supported = false;
            for (int i = 0; i < supportedProfiles.size(); i++) {
                String supportedProfile = 
                    (String)supportedProfiles.elementAt(i);
                if (matchVersion(supportedProfile, requestedProfile)) {
                     supported = true;
                     break;
                }
            }   

            // short circuit the test if there is one mismatch
            if (!supported) {
                break;
            }
        } while (nextSeparatorIndex != -1);

        // matched all requested profiles against supported ones
        if (supported) {
            return;
        }

        postInstallMsgBackToProvider(INCOMPATIBLE_MSG);
        throw new InvalidJadException(InvalidJadException.DEVICE_INCOMPATIBLE);
    
private booleanmatchVersion(java.lang.String name1, java.lang.String name2)
Match the name of the configuration or profile, and return true if the first name has a greater or equal version than the second. The names of the format "XXX-Y.Y" (e.g. CLDC-1.0, MIDP-2.0) as used in the system properties (microedition.configuration & microedition.profiles). This is used for checking both configuration and profiles.

param
name1 name of configuration or profile
param
name2 name of configuration or profile
return
true is name1 matches name2 and is greater or equal in version number. false otherwise

        String base1 = name1.substring(0, name1.indexOf('-"));
        String base2 = name2.substring(0, name1.indexOf('-"));

        if (base1.equals(base2)) {
            String ver1 = name1.substring(name1.indexOf('-") + 1, 
                                          name1.length());
            String ver2 = name2.substring(name2.indexOf('-") + 1, 
                                          name2.length());
            return (vercmp(ver1, ver2) >= 0);
        } else {
            return false;
        }
    
private synchronized java.lang.StringperformInstall()
Performs an install.

return
the unique name of the installed package
exception
IOException is thrown, if an I/O error occurs during descriptor or jar file download
exception
InvalidJadException is thrown, if the descriptor file is not properly formatted or does not contain the required information
exception
IllegalArgumentException is thrown, if the descriptor file is not specified


	/* Disable push interruptions during install. */
	PushRegistryImpl.enablePushLaunch(classSecurityToken, false);

	try {
	    state.startTime = System.currentTimeMillis();
	    
	    while (state.nextStep < 7) {
		/*
		 * clear the previous warning, so we can tell if another has
		 * happened
		 */
		state.exception = null;
		
		if (state.stopInstallation) {
		    postInstallMsgBackToProvider(USER_CANCELLED_MSG);
		    throw new IOException("stopped");
		}
		
		switch (state.nextStep) {
		case 1:
		    installStep1();
		    break;
		    
		case 2:
		    installStep2();
		    break;
		    
		case 3:
		    installStep3();
		    break;
		    
		case 4:
		    installStep4();
		    break;
		    
		case 5:
		    installStep5();
		    break;
		    
		case 6:
		    installStep6();
		    break;
		}
		
		if (state.exception != null) {
		    if (state.listener == null) {
			throw state.exception;
		    }
		    
		    if (!state.listener.warnUser(state)) {
			state.stopInstallation = true;
			postInstallMsgBackToProvider(USER_CANCELLED_MSG);
			throw state.exception;
		    }
		}
	    }
	} finally {
	    PushRegistryImpl.enablePushLaunch(classSecurityToken, true);
	}

        return state.storageName;
    
private voidpostInstallMsgBackToProvider(java.lang.String message)
Posts a status message back to the provider's URL in JAD.

param
message status message to post

        String url;

        // Send out delete notifications that have been queued, first
        postQueuedDeleteMsgsBackToProvider();

        // Now, send out install notifications
        url = state.getAppProperty(NOTIFY_PROP);
        postMsgBackToProvider(message, url);
    
private voidpostMsgBackToProvider(java.lang.String message, java.lang.String url)
Posts a status message back to the provider's URL in JAD.

param
message status message to post
param
url target http url for the status message

        HttpConnection transaction;
        OutputStream out;

        if (url == null) {
            return;
        }

        try {
            transaction = (HttpConnection)Connector.open(url, Connector.WRITE);
            try {
                transaction.setRequestMethod(HttpConnection.POST);

                if ((state != null) && 
                    (state.proxyUsername != null) &&
                    (state.proxyPassword != null)) {
                    transaction.setRequestProperty("Proxy-Authorization",
                        formatAuthCredentials(state.proxyUsername,
                                              state.proxyPassword));
                }

                out = transaction.openOutputStream();

                try {
                    out.write(message.getBytes());
                } finally {
                    out.close();
                }

                // we will wait for the server to run the script
                transaction.getResponseCode();
            } finally {
                transaction.close();
            }
        } catch (IOException ioe) {
            // ignore IOExceptions
        } catch (IllegalArgumentException iae) {
            // ignore IllegalArgumentExceptions
        } catch (ClassCastException cce) {
            // ignore ClassCastExceptions
        }
    
private voidpostQueuedDeleteMsgsBackToProvider()
Posts all queued delete notification messages

        Vector      deleteNotifyList;
        Enumeration e;

        deleteNotifyList = getDeleteNotifyURLList();

        for (e = deleteNotifyList.elements(); e.hasMoreElements(); ) {
            String url = (String)e.nextElement();
            postMsgBackToProvider(DELETE_NOTIFICATION_MSG, url);
            removeFromDeleteNotifyURLList(url);
        }
    
private voidprocessPreviousRMS()
If this is an update, make sure the RMS data is handle correctly according to the OTA spec.

From the OTA spec:

The RMS record stores of a MIDlet suite being updated MUST be managed as follows:

  • If the cryptographic signer of the new MIDlet suite and the original MIDlet suite are identical, then the RMS record stores MUST be retained and made available to the new MIDlet suite.
  • If the scheme, host, and path of the URL that the new Application Descriptor is downloaded from is identical to the scheme, host, and path of the URL the original Application Descriptor was downloaded from, then the RMS MUST be retained and made available to the new MIDlet suite.
  • If the scheme, host, and path of the URL that the new MIDlet suite is downloaded from is identical to the scheme, host, and path of the URL the original MIDlet suite was downloaded from, then the RMS MUST be retained and made available to the new MIDlet suite.
  • If the above statements are false, then the device MUST ask the user whether the data from the original MIDlet suite should be retained and made available to the new MIDlet suite.

exception
IOException if the install is stopped

        HttpUrl newUrl;
        HttpUrl originalUrl;
        String suiteStorageRoot = state.storageRoot + state.storageName;

        if (!RecordStoreFile.suiteHasRmsData(suiteStorageRoot)) {
            return;
        }

        if (state.previousCA != null && state.ca != null &&
                state.ca.equals(state.previousCA)) {
            // signers the same
            return;
        }

        try {
            newUrl = new HttpUrl(state.jadUrl);
            originalUrl = new HttpUrl(state.previousJadUrl);

            if (newUrl.scheme.equals(originalUrl.scheme) &&
                    newUrl.host.equals(originalUrl.host) &&
                    newUrl.path.equals(originalUrl.path)) {
                return;
            }
        } catch (NullPointerException npe) {
            // no match, fall through
        }

        try {
            newUrl = new HttpUrl(state.jarUrl);
            originalUrl = new HttpUrl(state.previousJarUrl);

            if (newUrl.scheme.equals(originalUrl.scheme) &&
                    newUrl.host.equals(originalUrl.host) &&
                    newUrl.path.equals(originalUrl.path)) {
                return;
            }
        } catch (NullPointerException npe) {
            // no match, fall through
        }

        // ask the user, if no listener assume no for user's answer

        if (state.listener != null) {
            if (state.listener.keepRMS(state)) {
                // user wants to keep the data
                return;
            }
        }

        // this is a good place to check for a stop installing call
        if (state.stopInstallation) {
            postInstallMsgBackToProvider(USER_CANCELLED_MSG);
            throw new IOException("stopped");
        }

        RecordStoreFile.removeRecordStoresForSuite(classSecurityToken,
                                                   suiteStorageRoot);
    
private voidredoPreviousPushConnections()
Registers the push connections for previous version after and aborted update.

        for (int i = 1; ; i++) {
            String pushProp;

            pushProp = state.previousSuite.getProperty("MIDlet-Push-" + i);
            if (pushProp == null) {
                break;
            }

            /*       
             * Parse the comma separated values  -           
             *  " connection, midlet, role, filter"
             */
            int comma1 = pushProp.indexOf(',", 0);
            int comma2 = pushProp.indexOf(',", comma1 + 1);
            
            String conn = pushProp.substring(0, comma1).trim();
            String midlet = pushProp.substring(comma1+1, comma2).trim();
            String filter = pushProp.substring(comma2+1).trim();

            /* Register the new push connection string. */
            try {
                PushRegistryImpl.registerConnectionInternal(
                                                    classSecurityToken,
                                                    state,
                                                    conn, midlet, 
                                                    filter, true);
            } catch (IOException e) {
                // should not happen
            } catch (ClassNotFoundException e) {
                // should not happen
            }
        }
    
private voidregisterPushConnections()
Registers the push connections for the application. Send a message back to the server if a connection cannot be registered and throw an exception.

exception
InvalidJadException if a connection cannot be registered

        byte[] curLevels = state.permissions[Permissions.CUR_LEVELS];

        if (state.isPreviousVersion) {
            PushRegistryImpl.unregisterConnections(classSecurityToken,
                                                   state.storageName);
        }

        for (int i = 1; ; i++) {
            String pushProp;

            pushProp = state.getAppProperty("MIDlet-Push-" + i);
            if (pushProp == null) {
                break;
            }

            /*       
             * Parse the comma separated values  -           
             *  " connection, midlet, role, filter"
             */
            int comma1 = pushProp.indexOf(',", 0);
            int comma2 = pushProp.indexOf(',", comma1 + 1);
            
            String conn = pushProp.substring(0, comma1).trim();
            String midlet = pushProp.substring(comma1+1, comma2).trim();
            String filter = pushProp.substring(comma2+1).trim();

            /* Register the new push connection string. */
            try {
                PushRegistryImpl.registerConnectionInternal(
                                                    classSecurityToken,
                                                    state,
                                                    conn, midlet, 
                                                    filter, false);
            } catch (Exception e) {
                /* If already registered, abort the installation. */
                PushRegistryImpl.unregisterConnections(classSecurityToken,
                                                       state.storageName);

                if (state.isPreviousVersion) {
                    // put back the old ones, removed above
                    redoPreviousPushConnections();
                }

                if (e instanceof SecurityException) {
                    postInstallMsgBackToProvider(AUTHORIZATION_FAILURE_MSG);

                    // since our state object put the permission in message
                    throw new InvalidJadException(
                        InvalidJadException.AUTHORIZATION_FAILURE,
                        e.getMessage());
                }

                postInstallMsgBackToProvider(PUSH_REG_FAILURE_MSG);

                if (e instanceof IllegalArgumentException) {
                    throw new InvalidJadException(
                        InvalidJadException.PUSH_FORMAT_FAILURE, pushProp);
                }

                if (e instanceof ConnectionNotFoundException) {
                    throw new InvalidJadException(
                        InvalidJadException.PUSH_PROTO_FAILURE, pushProp);
                }

                if (e instanceof IOException) {
                    throw new InvalidJadException(
                        InvalidJadException.PUSH_DUP_FAILURE, pushProp);
                }

                if (e instanceof ClassNotFoundException) {
                    throw new InvalidJadException(
                        InvalidJadException.PUSH_CLASS_FAILURE, pushProp);
                }

                // error in the RI code
                throw (RuntimeException)e;
            }
        }

        if (state.isPreviousVersion) {
            // use the old setting
            state.pushInterruptSetting =
                (byte)state.previousSuite.getPushInterruptSetting();

            // The old suite may have not had push connections
            if (state.pushInterruptSetting != Permissions.NEVER) {
                return;
            }
        }

        if (curLevels[Permissions.PUSH] == Permissions.NEVER) {
            state.pushInterruptSetting = Permissions.NEVER;
        } else if (curLevels[Permissions.PUSH] == Permissions.ALLOW) {
            state.pushInterruptSetting = Permissions.BLANKET;
        } else if (curLevels[Permissions.PUSH] == Permissions.ONE_SHOT) {
            // treat the ONE_SHOT as sesssion for interrupting
            state.pushInterruptSetting = Permissions.SESSION;
        } else {
            state.pushInterruptSetting = curLevels[Permissions.PUSH];
        }
    
public voidremove(java.lang.String storageName)
Removes a software package given its storage name

If the component is in use it must continue to be available to the other components that are using it. The resources it consumes must not be released until it is not in use.

param
storageName storage name for the installed package
exception
SecurityException if the caller does not have permission to remove the installed package
exception
IllegalArgumentException if the suite cannot be found

        MIDletSuite suiteToDelete = getMIDletSuite(storageName, null);
        String deleteURL = null;

        synchronized (myInstaller) {
	    /*
	     * Block any push launch operations while the remove is
	     * in progress.
	     */
	    PushRegistryImpl.enablePushLaunch(classSecurityToken, false);
	    try {
		/*
		 * Before removing the suite, get info to send delete
		 * notification back to server.
		 */
		suiteToDelete = getMIDletSuite(storageName, null);
		if (suiteToDelete != null) {
		    deleteURL = suiteToDelete.getProperty(DELETE_PROP);
		    addToDeleteNotifyURLList(deleteURL);
		}
		
		if (!removeFromSuiteList(storageName)) {
		    throw new IllegalArgumentException("Could not find " +
						       storageName);
		}
		cleanUp(storageName);
	    } finally {
		PushRegistryImpl.enablePushLaunch(classSecurityToken, true);
	    }
        }
    
private voidremoveFromDeleteNotifyURLList(java.lang.String url)
Removes the URL from the delete notification list.

param
url a URL

        Vector deleteNotifyList;

        deleteNotifyList = getDeleteNotifyURLList();
        deleteNotifyList.removeElement(url);
        saveDeleteNotifyURLList(deleteNotifyList);
    
private booleanremoveFromSuiteList(java.lang.String storageName)
Removes the suite from the list of installed suites.

param
storageName a suite
return
true if the suite was in the list, else false

        Vector suiteList;

        if (!suiteExists(storageName)) {
            return false;
        }

        suiteList = getSuiteList();
        suiteList.removeElement(storageName);
        saveSuiteList(suiteList);
        return true;
    
private voidsaveDeleteNotifyURLList(java.util.Vector list)
Writes the given delete notification list to disk.

param
list Vector in the same format as one returned by getDeleteNotifyURLList()

        File file;
        RandomAccessStream storage;
        String filename;
        DataOutputStream out;
        String line;

        file = new File(classSecurityToken);
        storage = new RandomAccessStream(classSecurityToken);
        filename = File.getStorageRoot() + DELETE_NOTIFY_FILENAME;
        try {
            file.delete(filename);
        } catch (Exception e) {
            // Ignore the exception that happens when the file does not exist
        }

        try {
            storage.connect(filename, RandomAccessStream.READ_WRITE_TRUNCATE);
            try {
                out = storage.openDataOutputStream();
                for (int i = 0; i < list.size(); i++) {
                    line = (String)list.elementAt(i);
                    out.writeUTF(line);
                }
            } finally {
                storage.disconnect();
            }
        } catch (IOException e) {
            /* Ignored. Should not happen. */
        }
    
private voidsaveSuiteList(java.util.Vector list)
Writes the given MIDlet suite list to disk.

param
list Vector in the same format as one returned by getSuiteList()

        File file;
        RandomAccessStream storage;
        String filename;
        DataOutputStream out;
        String line;

        file = new File(classSecurityToken);
        storage = new RandomAccessStream(classSecurityToken);
        filename = File.getStorageRoot() + SUITE_LIST_FILENAME;
        try {
            file.delete(filename);
        } catch (Exception e) {
            // ignore the exception that happen when the file does not exist
        }

        try {
            storage.connect(filename, RandomAccessStream.READ_WRITE_TRUNCATE);
            try {
                out = storage.openDataOutputStream();
                for (int i = 0; i < list.size(); i++) {
                    line = (String)list.elementAt(i);
                    out.writeUTF(line);
                }
            } finally {
                storage.disconnect();
            }
        } catch (IOException e) {
            /* ignored. Shouldn't happen */
        }
    
public static voidsaveSuiteSettings(com.sun.midp.security.SecurityToken token, java.lang.String suiteStorageRoot, byte pushInterruptSetting, byte[][] permissions, boolean trusted)
Saves any of the settings (security or others) that the user may have changed.

param
token security token with internal MIDP permission
param
suiteStorageRoot storage root of the suite
param
pushInterruptSetting push interrupt setting
param
permissions security permissions for the suite
param
trusted true if the suite is trusted
exception
IOException if an error happens while writing

        saveSuiteSettings(new RandomAccessStream(token),
            suiteStorageRoot, pushInterruptSetting, permissions, trusted);
    
public static voidsaveSuiteSettings(java.lang.String suiteStorageRoot, byte pushInterruptSetting, byte[][] permissions, boolean trusted)
Saves any of the settings (security or others) that the user may have changed.

param
suiteStorageRoot storage root of the suite
param
pushInterruptSetting push interrupt setting
param
permissions security permissions for the suite
param
trusted true if the suite is trusted
exception
IOException if an error happens while writing

        saveSuiteSettings(new RandomAccessStream(), suiteStorageRoot,
            pushInterruptSetting, permissions, trusted);
    
private static voidsaveSuiteSettings(com.sun.midp.io.j2me.storage.RandomAccessStream storage, java.lang.String suiteStorageRoot, byte pushInterruptSetting, byte[][] permissions, boolean trusted)
Saves any of the settings (security or others) that the user may have changed.

param
storage pre-allocated storage object, but not connected
param
suiteStorageRoot storage root of the suite
param
pushInterruptSetting push interrupt setting
param
permissions security permissions for the suite
param
trusted true if the suite is trusted
exception
IOException if an error happens while writing

        DataOutputStream storageStream;
        byte[] maximums = permissions[Permissions.MAX_LEVELS];
        byte[] currentLevels = permissions[Permissions.CUR_LEVELS];

        storage.connect(suiteStorageRoot + SETTINGS_FILENAME,
                        RandomAccessStream.READ_WRITE_TRUNCATE);
        try {
            storageStream = storage.openDataOutputStream();

            /*
             * write the version of the file
             * Note that version number only has to
             * increase if data has been removed, not if new data has been
             * added to the end of the file.
             */
            storageStream.writeByte(1);

            storageStream.writeBoolean(trusted);

            storageStream.writeByte(pushInterruptSetting);

            storageStream.writeByte(currentLevels.length);
            for (int i = 0; i < currentLevels.length; i++) {
                storageStream.writeByte(currentLevels[i]);
            }

            storageStream.writeByte(maximums.length);
            for (int i = 0; i < maximums.length; i++) {
                storageStream.writeByte(maximums[i]);
            }

            // Add new items at the end of the file
        } finally {
            try {
                storage.disconnect();
            } catch (IOException e) {
                // ignore
            }
        }
    
public voidsetUnsignedSecurityDomain(java.lang.String domain)
Sets security domain for unsigned suites. The default is untrusted. Can only be called by JAM for testing.

param
domain name of a security domain

        MIDletSuite midletSuite = Scheduler.getScheduler().getMIDletSuite();

        // if a MIDlet suite is not scheduled, assume the JAM is calling.
        if (midletSuite != null) {
            midletSuite.checkIfPermissionAllowed(Permissions.MIDP);
        }

        unsignedSecurityDomain = domain;
    
public booleanstopInstalling()
Stops the installation. If installer is not installing then this method has no effect. This will cause the install method to throw an IOException if the install is not writting the suite to storage which is the point of no return.

return
true if the install will stop, false if it is too late

        if (state == null) {
            return false;
        }

        synchronized (state) {
            if (state.writingSuite) {
                return false;
            }
        
            state.stopInstallation = true;

            try {
                httpInputStream.close();
            } catch (Exception e) {
                // ignore
            }

            try {
                httpConnection.close();
            } catch (Exception e) {
                // ignore
            }

            return true;
        }
    
public booleansuiteExists(java.lang.String storageName)
Tells if a suite exists.

param
storageName storage name of a suite
return
true if a suite of the given storage name already exists on the system

        Vector suiteList;

        suiteList = getSuiteList();
        for (int i = 0; i < suiteList.size(); i++) {
            if (suiteList.elementAt(i).equals(storageName)) {
                return true;
            }
        }

        return false;
    
private inttransferData(java.io.InputStream in, java.io.OutputStream out)
Function that actually does the work of transfering file data.

Updates the listener ever 1 K bytes.

If the amount of data to be read is larger than maxDLSize we will break the input into chunks no larger than maxDLSize. This prevents the VM from running out of memory when processing large files.

param
in the input stream to read from
param
out the output stream to write to
return
number of bytes written to the output stream
exception
IOException if any exceptions occur during transfer of data

        byte[] buffer = new byte[MAX_DL_SIZE];
        int bytesRead;
        int totalBytesWritten = 0;

        if (state.listener != null) {
            state.listener.updateStatus(state.beginTransferDataStatus, state);
        }

        try {
            for (int nextUpdate = totalBytesWritten + 1024; ; ) {
                bytesRead = in.read(buffer);

                if (state.listener != null && (bytesRead == -1 ||
                        totalBytesWritten + bytesRead >= nextUpdate)) {

                    synchronized (state) {
                        if (state.stopInstallation) {
                            throw new IOException("stopped");
                        }

                        state.listener.updateStatus(state.transferStatus,
                                                    state);
                    }

                    nextUpdate = totalBytesWritten + 1024;
                }

                if (bytesRead == -1) {
                    return totalBytesWritten;
                }

                out.write(buffer, 0, bytesRead);
                totalBytesWritten += bytesRead;
            }
        } catch (IOException ioe) {
            if (state.stopInstallation) {
                postInstallMsgBackToProvider(USER_CANCELLED_MSG);
                throw new IOException("stopped");
            } else {
                throw ioe;
            }
        }
    
private intvercmp(java.lang.String ver1, java.lang.String ver2)
Compares two version strings. The return values are very similar to that of strcmp() in 'C'. If the first version is less than the second version, a negative number will be returned. If the first version is greater than the second version, a positive number will be returned. If the two versions are equal, zero is returned.

Versions must be in the form xxx.yyy.zzz, where:

xxx is the major version
yyy is the minor version
zzz is the micro version
It is acceptable to omit the micro and possibly the minor versions. If these are not included in the version string, the period immediately preceding the number must also be removed. So, the versions xxx.yyy or xxx are also valid.

Version numbers do not have to be three digits wide. However, you may pad versions with leading zeros if desired.

If a version number is omitted, its value is assumed to be zero. All tests will be based on this assumption.

For example:

1.04 > 1.
1.04 < 1.4.1
1.04 = 1.4.0

param
ver1 the first version to compare
param
ver2 the second version to compare
return
1 if ver1 is greater than ver2 0 if ver1 is equal to ver2 -1 if ver1 is less than ver2
exception
NumberFormatException if either ver1 or ver2 contain characters that are not numbers or periods

        String strVal1;
        String strVal2;
        int    intVal1;
        int    intVal2;
        int    idx1 = 0;
        int    idx2 = 0;
        int    newidx;

        if ((ver1 == null) && (ver2 == null)) {
            return 0;
        }

        if (ver1 == null) {
            return -1;
        }

        if (ver2 == null) {
            return 1;
        }

        for (int i = 0; i < 3; i++) {
            strVal1 = "0"; // Default value
            strVal2 = "0"; // Default value
            if (idx1 >= 0) {
                newidx = ver1.indexOf('.", idx1);
                if (newidx < 0) {
                    strVal1 = ver1.substring(idx1);
                } else {
                    strVal1 = ver1.substring(idx1, newidx);
                    newidx++; // Idx of '.'; need to go to next char
                }

                idx1 = newidx;
            }

            if (idx2 >= 0) {
                newidx = ver2.indexOf('.", idx2);
                if (newidx < 0) {
                    strVal2 = ver2.substring(idx2);
                } else {
                    strVal2 = ver2.substring(idx2, newidx);
                    newidx++;
                }

                idx2 = newidx;
            }

            intVal1 = Integer.parseInt(strVal1); // May throw NFE
            intVal2 = Integer.parseInt(strVal2); // May throw NFE

            if (intVal1 > intVal2) {
                return 1;
            }

            if (intVal1 < intVal2) {
                return -1;
            }
        }

        return 0;
    
protected voidverifyJar(com.sun.midp.io.j2me.storage.RandomAccessStream jarStorage, java.lang.String jarFilename)
Verifies a Jar. Post any error back to the server.

param
jarStorage System store for applications
param
jarFilename name of the jar to read
exception
IOException if any error prevents the reading of the JAR
exception
InvalidJadException if the JAR is not valid

    
public booleanwasStopped()
Tells if the installation was stopped by another thread.

return
true if the installation was stopped by another thread

        if (state == null) {
            return false;
        }

        return state.stopInstallation;