javamanager/javainstaller/installer/javasrc/com/nokia/mj/impl/installer/midp2/install/steps/SelectUids.java
author Pat Downey <patd@symbian.org>
Wed, 01 Sep 2010 12:33:18 +0100
branchRCL_3
changeset 66 2455ef1f5bbc
parent 19 04becd199f91
permissions -rw-r--r--
Revert incorrect RCL_3 drop: Revision: v2.2.11 Kit: 201035

/*
* Copyright (c) 2008-2009 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description:
*
*/


package com.nokia.mj.impl.installer.midp2.install.steps;

import com.nokia.mj.impl.installer.applicationregistrator.ApplicationRegistrator;
import com.nokia.mj.impl.installer.exetable.ExeBall;
import com.nokia.mj.impl.installer.exetable.ExeStep;
import com.nokia.mj.impl.installer.storagehandler.ApplicationInfo;
import com.nokia.mj.impl.installer.storagehandler.SuiteInfo;
import com.nokia.mj.impl.installer.utils.FileUtils;
import com.nokia.mj.impl.installer.utils.InstallerException;
import com.nokia.mj.impl.installer.utils.Log;
import com.nokia.mj.impl.installer.utils.PlatformUid;
import com.nokia.mj.impl.utils.InstallerDetailedErrorMessage;
import com.nokia.mj.impl.utils.InstallerErrorMessage;
import com.nokia.mj.impl.utils.OtaStatusCode;
import com.nokia.mj.impl.utils.Tokenizer;
import com.nokia.mj.impl.utils.Uid;
import com.nokia.mj.impl.utils.exception.InvalidAttributeException;

import java.io.InputStream;
import java.io.IOException;
import java.util.Vector;

public class SelectUids extends ExeStep
{
    private static final String MIDLET_UID_ATTR_NAME = "Nokia-MIDlet-UID-";

    // Uids read from 'uids' file, this is used only when
    // converting midlets from old java to OMJ.
    private Uid[] iUidsFromFile = null;

    public void execute(ExeBall aBall)
    {
        InstallBall ball = (InstallBall)aBall;
        //ball.log("Selecting uids...");
        // SelectUids step is called in two phases because suite uid
        // must be allocated before security framework is used,
        // but application uids must be selected after jar file has
        // been downloaded because jar can have more MIDlet-n
        // attributes than jad.
        if (ball.iSuite.getUid() == null)
        {
            // First phase: select suite uid and application uids
            // basing on attributes that are available.
            readUidsFromFile(ball);
            selectSuiteUid(ball);
            selectApplicationUids(ball);
            // Set application root, jad and jar paths.
            updateSuitePaths(ball);
        }
        else
        {
            // Second phase: allocate application uids for those
            // applications which don't yet have uids allocated.
            selectApplicationUids(ball);
            // Check that all Nokia-MIDlet-UID-n attributes are
            // present in jar for trusted applications.
            checkNokiaMidletUids(ball);
        }
        // Log all suite info.
        //ball.log(ball.iSuite.toString());
    }

    public void cancel(ExeBall aBall)
    {
        // nop
    }

    /**
     * Selects suite uid.
     */
    private void selectSuiteUid(InstallBall ball)
    {
        // Select suite uid.
        Log.log("Selecting suite uid...");
        if (ball.iConversionInstallation &&
                iUidsFromFile != null && iUidsFromFile.length > 0)
        {
            // Conversion installation, use uid from 'uids' file.
            ball.iSuite.setUid(iUidsFromFile[0]);
            ball.log("Selected suite uid from file: " + ball.iSuite.getUid());
        }
        else if (ball.iOldSuite != null)
        {
            // Old suite exists, use its uid.
            ball.iSuite.setUid(ball.iOldSuite.getUid());
            ball.log("Selected old suite uid: " + ball.iSuite.getUid());
        }
        else
        {
            // Allocate a new suite uid.
            ball.iSuite.setUid
            (allocateUid(ball, ball.getAttributeValue("MIDlet-Name")));
            ball.log("Allocated new suite uid: " + ball.iSuite.getUid());
        }
    }

    /**
     * Selects application uids.
     */
    private void selectApplicationUids(InstallBall ball)
    {
        // Select application uids.
        int startIndex = ball.iSuite.getApplications().size() + 1;
        Log.log("Selecting application uids, startIndex " + startIndex);
        for (int i = startIndex; true; i++)
        {
            String appAttrName = "MIDlet-" + i;
            String appAttrValue = ball.getAttributeValue(appAttrName);
            if (appAttrValue != null)
            {
                Uid uid = null;
                // Split MIDlet-n attribute into tokens.
                String[] tokens = Tokenizer.split(appAttrValue, ",");
                if (tokens == null || tokens.length != 3)
                {
                    Log.logError("Invalid " + appAttrName +
                                 " attribute value: " + appAttrValue);
                    throw new InvalidAttributeException
                    (InstallerErrorMessage.INST_CORRUPT_PKG, null,
                     InstallerDetailedErrorMessage.ATTR_BAD_SYNTAX,
                     new String[] { appAttrName },
                     (ball.attributeExistsInJad(appAttrName)?
                      OtaStatusCode.INVALID_DESCRIPTOR:
                      OtaStatusCode.INVALID_JAR));
                }
                if (ball.iConversionInstallation &&
                        iUidsFromFile != null && iUidsFromFile.length > i)
                {
                    // Conversion installation, use uid from 'uids' file.
                    uid = iUidsFromFile[i];
                    ball.log("Selected app uid from file: " + uid);
                }
                if (uid == null)
                {
                    // Check if uid is specified with Nokia-MIDlet-UID attribute.
                    uid = selectAppUidFromAttribute(ball, i);
                }
                if (uid == null && ball.iOldSuite != null)
                {
                    // No uid yet, check from old suite.
                    Vector oldApps = ball.iOldSuite.getApplications();
                    if (oldApps.size() >= i)
                    {
                        // Use old application uid.
                        uid = ((ApplicationInfo)oldApps.elementAt(i-1)).getUid();
                        // Check that uid is not already in use in this
                        // suite. This could happen if Nokia-MIDlet-UID
                        // attribute was used to select uid for different
                        // app in the previous version of this suite.
                        if (appUidInUse(uid, ball.iSuite))
                        {
                            // Uid is already in use in this suite,
                            // it cannot be used twice.
                            Log.log("Old uid already in use: " + uid);
                            uid = null;
                        }
                        if (uid != null)
                        {
                            ball.log("Selected old app uid: " + uid);
                        }
                    }
                }
                if (uid == null)
                {
                    // If no uid chosen yet, then allocate a new application uid.
                    uid = allocateUid(ball, tokens[0].trim());
                    ball.log("Allocated new app uid: " + uid);
                }
                // Add a new ApplicationInfo to suite.
                ball.iSuite.addApplication
                (new ApplicationInfo
                 (uid, tokens[0].trim(), tokens[1].trim(),
                  tokens[2].trim(), ApplicationInfo.AUTOSTART_FALSE));
            }
            else
            {
                break;
            }
        }
    }

    /**
     * Selects application uid from Nokia-MIDlet-UID attribute.
     * @return uid if attribute specifies it, null otherwise.
     * @throws InstallerException if attribute exists and is not valid
     * or specified uid is already in use.
     */
    private Uid selectAppUidFromAttribute(InstallBall aBall, int aIndex)
    {
        Uid uid = null;
        String appUidAttrName = MIDLET_UID_ATTR_NAME + aIndex;
        String appUidAttrValue = aBall.getAttributeValue(appUidAttrName);
        if (appUidAttrValue != null)
        {
            uid = PlatformUid.createUid(appUidAttrValue);
            if (uid == null)
            {
                Log.logError("Invalid " + appUidAttrName +
                             " attribute value: " + appUidAttrValue);
                throw new InvalidAttributeException
                (InstallerErrorMessage.INST_CORRUPT_PKG, null,
                 InstallerDetailedErrorMessage.ATTR_BAD_SYNTAX,
                 new String[] { appUidAttrName },
                 (aBall.attributeExistsInJad(appUidAttrName)?
                  OtaStatusCode.INVALID_DESCRIPTOR:
                  OtaStatusCode.INVALID_JAR));
            }
            boolean uidInJad = aBall.attributeExistsInJad(appUidAttrName);
            // If jar file is not yet present, assume that uid
            // will be present in jar.
            boolean uidInJar = true;
            if (aBall.iJarFilename != null)
            {
                uidInJar = aBall.attributeExistsInJar(appUidAttrName);
            }
            if (uidInJar && !uidInJad && aBall.iJadAttributes != null)
            {
                // If Uid is specified in jar but not in jad
                // it must be ignored. Getting applications
                // through Java Verified process requires
                // that applications using protected Uids
                // can be installed as untrusted (without jad).
                Log.logWarning(
                    appUidAttrName +
                    " attribute present in jar but not in jad, ignoring it");
                uid = null;
            }
            if (uid != null)
            {
                // Check that uid is valid.
                PlatformUid.checkValidity
                (appUidAttrName, uid,
                 aBall.iSuite.isTrusted(),
                 uidInJad, uidInJar);
                // Check that uid is not already in use.
                if (appUidInUse(uid, aBall) ||
                        appUidInUse(uid, aBall.iSuite))
                {
                    Log.logError("Uid " + uid + " is already in use");
                    throw new InvalidAttributeException
                    (InstallerErrorMessage.INST_CORRUPT_PKG, null,
                     InstallerDetailedErrorMessage.ATTR_HANDLING_FAILED,
                     new String[] { appUidAttrName },
                     (aBall.attributeExistsInJad(appUidAttrName)?
                      OtaStatusCode.INVALID_DESCRIPTOR:
                      OtaStatusCode.INVALID_JAR));
                }
            }
            aBall.log("Selected app uid from " + appUidAttrName +
                      " attribute: " + uid);
        }
        return uid;
    }

    /**
     * Updates root, jad and jar paths to suite info object.
     */
    private void updateSuitePaths(InstallBall aBall)
    {
        aBall.iSuite.setRootDir
        (FileUtils.getAppsRoot() +
         FileUtils.getAppRootPath(aBall.iSuite.getUid()));
        if (aBall.iJadFilename != null)
        {
            if (aBall.iPreinstallation)
            {
                // In preinstallation case use file without copying it to
                // suite root dir.
                aBall.iSuite.setJadPath(aBall.iJadFilename);
            }
            else
            {
                // Set jar path to point under suite root dir.
                aBall.iSuite.setJadPath
                (aBall.iSuite.getRootDir() +
                 FileUtils.getName(aBall.iJadFilename));
            }
        }
        if (aBall.iJarFilename != null)
        {
            if (aBall.iPreinstallation)
            {
                // In preinstallation case use file without copying it to
                // suite root dir.
                aBall.iSuite.setJarPath(aBall.iJarFilename);
            }
            else
            {
                // Set jar path to point under suite root dir.
                aBall.iSuite.setJarPath
                (aBall.iSuite.getRootDir() +
                 FileUtils.getName(aBall.iJarFilename));
            }
        }
    }

    /**
     * Returns true if given application uid is used by someone else
     * than previous version of this application suite.
     *
     * @param aUid uid to be checked
     * @param aBall InstallBall object
     * @return true if uid is already in use, false if this suite can
     * use given uid
     */
    private boolean appUidInUse(Uid aUid, InstallBall aBall)
    {
        boolean result = aBall.iApplicationRegistrator.uidInUse(aUid);
        if (result)
        {
            // Uid is in use, check if it is used by previous version
            // of this suite.
            if (appUidInUse(aUid, aBall.iOldSuite))
            {
                // Uid is in use by previous version of
                // this suite, we can still use it so
                // let's return false.
                result = false;
            }
        }
        return result;
    }

    /**
     * Returns true if given uid is already in use in given suite.
     *
     * @param aUid uid to be checked
     * @param aSuite application suite info object
     * @return true if uid is already in use, false otherwise
     */
    private boolean appUidInUse(Uid aUid, SuiteInfo aSuite)
    {
        if (aSuite == null)
        {
            return false;
        }
        boolean result = false;
        Uid[] uids = aSuite.getApplicationUids();
        if (uids != null)
        {
            for (int i = 0; !result && i < uids.length; i++)
            {
                if (aUid.equals(uids[i]))
                {
                    // Uid is already in use.
                    result = true;
                }
            }
        }
        return result;
    }

    /**
     * Allocate a new uid which is not in use in JavaStorage or used by
     * some non-Java applications.
     */
    private Uid allocateUid(InstallBall aBall, String aAppName)
    {
        Uid uid = null;
        String uidSeed =
            aAppName + ":" + aBall.getAttributeValue("MIDlet-Vendor");
        do
        {
            if (uid == null)
            {
                uid = aBall.iStorageHandler.allocateUid(uidSeed);
            }
            else
            {
                //Log.log("Generated uid " + uid +
                //    " was already in use, allocating a new one");
                uid = aBall.iStorageHandler.allocateUid(uid);
            }
            // Uid returned by storage handler is not in use
            // in Java Storage, but check if the platform
            // is already using it for a non-Java application,
            // or if it has already been allocated for this suite.
        }
        while (aBall.iApplicationRegistrator.uidInUse(uid) ||
                uid.equals(aBall.iSuite.getUid()) ||
                appUidInUse(uid, aBall.iSuite));
        return uid;
    }

    /**
     * Reads uids from 'uids' file during conversion of
     * midlets from old java to OMJ.
     */
    private void readUidsFromFile(InstallBall aBall)
    {
        if (!aBall.iConversionInstallation)
        {
            return;
        }
        String uidsFilename = aBall.iConversionRoot + "uids";
        if (FileUtils.exists(uidsFilename))
        {
            Log.log("Reading uids file: " + uidsFilename);
            Vector v = new Vector();
            InputStream is = null;
            try
            {
                is = FileUtils.getInputStream(uidsFilename);
                while (is.available() > 0)
                {
                    v.addElement(readUid(is));
                }
            }
            catch (IOException ioe)
            {
                InstallerException.internalError
                ("Error while reading uids file: "  + uidsFilename, ioe);
            }
            finally
            {
                if (is != null)
                {
                    try
                    {
                        is.close();
                        is = null;
                    }
                    catch (IOException ioe)
                    {
                        Log.logWarning("Closing InputStream failed", ioe);
                    }
                }
            }
            iUidsFromFile = new Uid[v.size()];
            v.copyInto(iUidsFromFile);
            for (int i = 0; i < iUidsFromFile.length; i++)
            {
                Log.log("Read uid[" + i + "]: " + iUidsFromFile[i]);
            }
        }
        else
        {
            InstallerException.internalError("No uids file: "  + uidsFilename);
        }
    }

    /**
     * Reads a Uid from specified InputStream.
     */
    private static Uid readUid(InputStream aInputStream) throws IOException
    {
        int uid = 0;
        for (int i = 0; i < 4; i++)
        {
            uid |= (aInputStream.read() << i*8);
        }
        return PlatformUid.createUid(uid);
    }

    /**
     * Checks that all specified  Nokia-MIDlet-UID-n attributes are
     * present in jar for trusted application. Throws InstallerException
     * if attributes are not present.
     */
    private static void checkNokiaMidletUids(InstallBall aBall)
    {
        if (aBall == null || !aBall.iSuite.isTrusted())
        {
            return;
        }
        for (int i = 1; true; i++)
        {
            // Check if uid is specified with Nokia-MIDlet-UID attribute.
            String appUidAttrName = MIDLET_UID_ATTR_NAME + i;
            String appUidAttrValue = aBall.getAttributeValue(appUidAttrName);
            if (appUidAttrValue != null)
            {
                // Uid is specified with attribute, check that attribute
                // is present in jar.
                if (!aBall.attributeExistsInJar(appUidAttrName))
                {
                    Log.logError(
                        "For trusted application Uid must be specified in Jar: " +
                        appUidAttrName);
                    throw new InvalidAttributeException
                    (InstallerErrorMessage.INST_CORRUPT_PKG, null,
                     InstallerDetailedErrorMessage.ATTR_HANDLING_FAILED,
                     new String[] { appUidAttrName },
                     OtaStatusCode.INVALID_JAR);
                }
            }
            else
            {
                break;
            }
        }
    }
}