0001: /*
0002: * Copyright 1990-2006 Sun Microsystems, Inc. All Rights Reserved.
0003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
0004: *
0005: * This program is free software; you can redistribute it and/or
0006: * modify it under the terms of the GNU General Public License version
0007: * 2 only, as published by the Free Software Foundation.
0008: *
0009: * This program is distributed in the hope that it will be useful, but
0010: * WITHOUT ANY WARRANTY; without even the implied warranty of
0011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0012: * General Public License version 2 for more details (a copy is
0013: * included at /legal/license.txt).
0014: *
0015: * You should have received a copy of the GNU General Public License
0016: * version 2 along with this work; if not, write to the Free Software
0017: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
0018: * 02110-1301 USA
0019: *
0020: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
0021: * Clara, CA 95054 or visit www.sun.com if you need additional
0022: * information or have any questions.
0023: */
0024:
0025: package com.sun.jumpimpl.module.installer;
0026:
0027: import com.sun.jump.common.JUMPAppModel;
0028: import com.sun.jump.common.JUMPApplication; //import com.sun.jump.executive.JUMPUserInputManager;
0029: import com.sun.jump.module.contentstore.JUMPContentStore;
0030: import com.sun.jump.module.contentstore.JUMPData;
0031: import com.sun.jump.module.contentstore.JUMPNode;
0032: import com.sun.jump.module.contentstore.JUMPStore;
0033: import com.sun.jump.module.contentstore.JUMPStoreFactory;
0034: import com.sun.jump.module.contentstore.JUMPStoreHandle;
0035: import com.sun.jump.module.download.JUMPDownloadDescriptor;
0036: import com.sun.jump.common.JUMPContent;
0037: import com.sun.jump.module.installer.JUMPInstallerModule;
0038: import java.io.BufferedReader;
0039: import java.io.File;
0040: import java.io.FileOutputStream;
0041: import java.io.IOException;
0042: import java.io.InputStream;
0043: import java.io.InputStreamReader;
0044: import java.net.MalformedURLException;
0045: import java.net.URL;
0046: import java.util.Enumeration;
0047: import java.util.Hashtable;
0048: import java.util.Iterator;
0049: import java.util.Map;
0050: import java.util.Properties;
0051: import java.util.Vector;
0052: import java.util.jar.JarFile;
0053: import java.util.zip.ZipEntry;
0054:
0055: /**
0056: * XLETInstallerImpl contains the implementation of the JUMPInstallerModule
0057: * for XLET applications within JUMP.
0058: * Note: XLET and Main installations within JUMP in CDC behave almost the same.
0059: * For this reason, MAINInstallerImpl simply subclasses XLETInstallerImpl
0060: * and overrides a small set of methods to define specific Main application
0061: * behavior. In the future, if the behavior of XLET and Main application
0062: * installation start to differ much more, it may be wise to extract a base
0063: * class out of this class and create a subclass each for XLETs and Main
0064: * applications.
0065: */
0066: public class XLETInstallerImpl extends JUMPContentStore implements
0067: JUMPInstallerModule {
0068: /**
0069: * The filename extention for application descriptor files.
0070: */
0071: private final static String APP_DESCRIPTOR_EXTENSION = ".app";
0072: /**
0073: * The name of the directory to hold XLET and Main applications
0074: */
0075: private final static String REPOSITORY_APPS_DIRNAME = "./apps";
0076: /**
0077: * The name of the directory to hold XLET and Main icons
0078: */
0079: private final static String REPOSITORY_ICONS_DIRNAME = "./icons";
0080: /**
0081: * The name of the directory to hold XLET and Main application descriptor files
0082: */
0083: private final static String REPOSITORY_DESCRIPTORS_DIRNAME = "./descriptors";
0084: /**
0085: * Handle to the content store
0086: */
0087: private JUMPStoreHandle storeHandle = null;
0088: /**
0089: * The root directory of the content store
0090: */
0091: protected String contentStoreDir = null;
0092: /**
0093: * Holds the app id -> JUMPApplication object relationship
0094: */
0095: private Hashtable installedAppIdHashtable = null;
0096: private int installedAppIdHashtableKey = 1;
0097:
0098: /**
0099: * The keys below are used within application descriptor files only.
0100: * These keys are not the sames keys as the ones used in the download module.
0101: */
0102: private final static String DESCRIPTOR_BUNDLENAME_KEY = "bundle";
0103: private final static String DESCRIPTOR_APPMODEL_KEY = "type";
0104: private final static String DESCRIPTOR_JARPATH_KEY = "path";
0105: private final static String DESCRIPTOR_TITLE_KEY = "title";
0106: private final static String DESCRIPTOR_ICON_KEY = "icon";
0107: private final static String DESCRIPTOR_SECURITYLEVEL_KEY = "icon";
0108: private final static String DESCRIPTOR_ID_KEY = "id";
0109:
0110: protected final static String DESCRIPTOR_INITIALCLASS_KEY = "xletName";
0111:
0112: /**
0113: * Print out messages
0114: */
0115: private boolean verbose = false;
0116:
0117: /**
0118: *
0119: */
0120: private static final boolean ALLOW_MULTIPLE_APP_INSTALLS = false;
0121:
0122: private String getAvailableAppIdHashKey() {
0123: int tempHashKey = installedAppIdHashtableKey;
0124: while (installedAppIdHashtable.containsKey(Integer
0125: .toString(tempHashKey))) {
0126: if (tempHashKey == Integer.MAX_VALUE) {
0127: tempHashKey = 1;
0128: } else {
0129: tempHashKey++;
0130: }
0131:
0132: // For the very, very unlikely case that MAX_VALUE applications
0133: // are running and we cycled through all integer values.
0134: if (tempHashKey == installedAppIdHashtableKey) {
0135: return null;
0136: }
0137: }
0138: installedAppIdHashtableKey = tempHashKey;
0139: return Integer.toString(tempHashKey);
0140: }
0141:
0142: private void addInstalledAppIdEntry(Object key, Object app) {
0143: // Sanity check
0144: if (key == null || app == null) {
0145: return;
0146: }
0147:
0148: int keyVal = Integer.parseInt((String) key);
0149: if (keyVal < 1) {
0150: return;
0151: } else {
0152: installedAppIdHashtableKey = keyVal;
0153: }
0154: installedAppIdHashtable.put(key, app);
0155: }
0156:
0157: private void removeInstalledAppIdEntry(Object key) {
0158: if (key == null) {
0159: return;
0160: }
0161: Object app = installedAppIdHashtable.remove(key);
0162: }
0163:
0164: /**
0165: * Returns an instance of the content store to be used with the installer.
0166: * @return Instance of JUMPStore
0167: */
0168: protected JUMPStore getStore() {
0169: return JUMPStoreFactory.getInstance().getModule(
0170: JUMPStoreFactory.TYPE_FILE);
0171: }
0172:
0173: /**
0174: * Implementation of JUMPInstaler.unload()
0175: */
0176: public void unload() {
0177: contentStoreDir = null;
0178: verbose = false;
0179: if (storeHandle != null) {
0180: closeStore(storeHandle);
0181: }
0182: installedAppIdHashtable.clear();
0183: installedAppIdHashtable = null;
0184: }
0185:
0186: /**
0187: * load the installer module
0188: * @param map the configuration data required for loading this service.
0189: */
0190: public void load(Map map) {
0191: // check if verbose mode is used
0192: String verboseStr = System.getProperty("installer.verbose");
0193: if (verboseStr == null && map != null) {
0194: verboseStr = (String) map.get("installer.verbose");
0195: }
0196: if (verboseStr != null
0197: && verboseStr.toLowerCase().equals("true")) {
0198: verbose = true;
0199: }
0200:
0201: // the repository directory should be passed in as a system property
0202: contentStoreDir = System.getProperty("contentstore.root");
0203: if (contentStoreDir == null && map != null) {
0204: contentStoreDir = (String) map.get("contentstore.root");
0205: }
0206: if (contentStoreDir != null) {
0207: // remove any ending /'s'
0208: if (!contentStoreDir.endsWith("/")) {
0209: contentStoreDir = contentStoreDir.concat("/");
0210: }
0211: } else {
0212: // default to the current directory
0213: contentStoreDir = "./";
0214: }
0215:
0216: // Get the store handle from the JUMPContentStoreSubClass.
0217: storeHandle = openStore(true);
0218:
0219: // Make sure apps/, icons/, descriptors/ exist under the root
0220: // of content store. This is inherited CDC-specific behavior.
0221: try {
0222: // Create the directories within the repository if they don't exist'
0223: if (storeHandle.getNode(REPOSITORY_APPS_DIRNAME) == null) {
0224: storeHandle.createNode(REPOSITORY_APPS_DIRNAME);
0225: }
0226: if (storeHandle.getNode(REPOSITORY_ICONS_DIRNAME) == null) {
0227: storeHandle.createNode(REPOSITORY_ICONS_DIRNAME);
0228: }
0229: if (storeHandle.getNode(REPOSITORY_DESCRIPTORS_DIRNAME) == null) {
0230: storeHandle.createNode(REPOSITORY_DESCRIPTORS_DIRNAME);
0231: }
0232: } catch (Exception e) {
0233: e.printStackTrace();
0234: }
0235:
0236: closeStore(storeHandle);
0237:
0238: // Populate the installedAppIdHashtable to keep track of the installed
0239: // ids of all currently installed applications of this type
0240: installedAppIdHashtableKey = 1;
0241: if (installedAppIdHashtable != null) {
0242: installedAppIdHashtable.clear();
0243: }
0244: installedAppIdHashtable = new Hashtable();
0245: JUMPContent[] content = getInstalled();
0246: if (content != null) {
0247: for (int j = 0; j < content.length; j++) {
0248: JUMPApplication app = ((JUMPApplication) content[j]);
0249: int installedId = app.getId();
0250: addInstalledAppIdEntry(Integer.toString(installedId),
0251: app);
0252: }
0253: }
0254: }
0255:
0256: /**
0257: * install content specified by the given descriptor and location.
0258: * @return the installed content
0259: * @param location URL of content to be installed
0260: * @param desc object describing the content to be installed
0261: */
0262: public JUMPContent[] install(URL location,
0263: JUMPDownloadDescriptor desc) {
0264: // sanity check
0265: if (location == null
0266: || desc == null
0267: || !location.getProtocol().equals("file")
0268: || desc.getType() != JUMPDownloadDescriptor.TYPE_APPLICATION) {
0269: return null;
0270: }
0271:
0272: // This is the all important "name" value. This is the value retrieved
0273: // from the <name> in the Descriptor file. This value will be used
0274: // as the bundle name and is also used to name the jarfile and parent
0275: // directory in the repository. We need to restrict the characters used
0276: // within this name value, which means that all of the characters in the name
0277: // must be valid filename characters. This name value is not intended
0278: // for any display value purposes. For that, use <ddx:display>.
0279: // Here's an example. Let's say the name value is CasinoGames. Then
0280: // the resulting jarfile name will be: apps/CasinoGames/CasinoGames.jar.
0281: // If there is already a pathname that is the same, the jarfile name changes
0282: // a bit. We currently do not overwrite the existing duplicate file. Read
0283: // below for more details.
0284: String bundleName = desc.getName();
0285: if (bundleName == null) {
0286: return null;
0287: }
0288:
0289: // We need to replace spaces because apparently java doesn't like
0290: // jarfiles with spaces in the name. Any further string substitutions
0291: // should be done here.
0292: bundleName = bundleName.replace(' ', '_');
0293:
0294: trace(getString("Installing") + bundleName);
0295:
0296: String jarPathWithinStore = REPOSITORY_APPS_DIRNAME + '/'
0297: + bundleName + '/' + bundleName + ".jar";
0298: String jarPath = null;
0299: if (ALLOW_MULTIPLE_APP_INSTALLS) {
0300: // createUniquePathName ensures that we don't overwrite an existing file by
0301: // retrieving a unique name for saving. If there exists a file of the same
0302: // name, using the example above the new name will look something like this:
0303: // apps/CasinoGames(2)/CasinoGames(2).jar.
0304: jarPath = createUniquePathName(contentStoreDir
0305: + jarPathWithinStore);
0306: } else {
0307: jarPath = contentStoreDir + jarPathWithinStore;
0308: if (new File(jarPath).exists()) {
0309: System.out
0310: .println("*** Error installing bundle: A bundle with this name is already installed.");
0311: return null;
0312: }
0313: }
0314:
0315: trace(getString("AttemptingToSave") + jarPath);
0316:
0317: // Because of the possibility of the filename being modified because of
0318: // an already exiting file, the bundle name will also need to change.
0319: // The new bundle name is within the filename itself, so extract the bundle
0320: // name. For the example above, the new bundle name will be CasinoGames(2),
0321: // not CasinoGames. Again, this is only in the rare case of duplicates.
0322: // This should be changed if the policy is to overwrite existing filenames.
0323: int dotindex = jarPath.lastIndexOf('.');
0324: int fileSeparatorIndex = jarPath.lastIndexOf('/');
0325: if (dotindex == -1 || fileSeparatorIndex == -1) {
0326: return null;
0327: }
0328:
0329: // The bundleName because the fileName minus the .jar
0330: bundleName = jarPath
0331: .substring(fileSeparatorIndex + 1, dotindex);
0332: String parentDir = jarPath.substring(0, fileSeparatorIndex);
0333:
0334: try {
0335: // This is the location of the file passed into the install method
0336: File origFile = new File(location.getFile());
0337: if (!origFile.exists()) {
0338: trace(getString("CannotAccessFile") + ": "
0339: + location.getFile());
0340: return null;
0341: }
0342:
0343: // First, create the parent directory for the saved application
0344: File destDir = new File(parentDir);
0345: destDir.mkdir();
0346: if (!destDir.exists()) {
0347: trace(destDir.toString() + " "
0348: + getString("DoesNotExist"));
0349: return null;
0350: }
0351:
0352: // Move the file from the passed-in location
0353: File destFile = new File(jarPath);
0354: if (destFile == null) {
0355: return null;
0356: }
0357: boolean result = origFile.renameTo(destFile
0358: .getCanonicalFile());
0359: if (!result) {
0360: trace(getString("CannotMoveFile"));
0361:
0362: // The file move didn't work. A reason for this could be that
0363: // the original file and destination file are in two different
0364: // filesystems. Try copying a buffer from the URL input stream.
0365: trace(getString("UsingURLInputStream"));
0366: byte[] buffer = copyBuffer(location.openStream(), desc
0367: .getSize());
0368: if (buffer == null) {
0369: trace(getString("CannotSaveFile"));
0370: return null;
0371: }
0372:
0373: // Write out to a file within the repository
0374: FileOutputStream fos = new FileOutputStream(destFile);
0375: if (fos == null) {
0376: trace(getString("CannotSaveFile"));
0377: return null;
0378: }
0379: fos.write(buffer);
0380: fos.close();
0381: origFile.delete();
0382: }
0383: } catch (Exception ex) {
0384: ex.printStackTrace();
0385: try {
0386: storeHandle.deleteNode(parentDir);
0387: } catch (IOException e) {
0388: e.printStackTrace();
0389: }
0390: return null;
0391: }
0392:
0393: // Now it is time to install the applications into the system
0394: Properties apps[] = desc.getApplications();
0395: if (apps == null) {
0396: return null;
0397: }
0398:
0399: Hashtable installedContentHashtable = new Hashtable();
0400: for (int i = 0; i < apps.length; i++) {
0401: Properties app = apps[i];
0402:
0403: // sanity check
0404: if (app == null) {
0405: continue;
0406: }
0407:
0408: String appTitle = app.getProperty("JUMPApplication_title");
0409: if (appTitle == null) {
0410: return null;
0411: }
0412:
0413: // Properties object to hold application properties to be written to .app file
0414: // The key values in this properties object should match the key values
0415: // defined for application descriptor files.
0416: Properties appProperties = new Properties();
0417:
0418: String appIDHashKey = getAvailableAppIdHashKey();
0419: if (appIDHashKey == null) {
0420: trace("ERROR: Could not obtain an id hash key value.");
0421: return null;
0422: } else {
0423: appProperties.setProperty(DESCRIPTOR_ID_KEY,
0424: appIDHashKey);
0425: }
0426:
0427: // Retrieve the filename of the icon
0428: //String iconFileName = app.getIconPath().getFile();
0429: String iconFileName = app
0430: .getProperty("JUMPApplication_iconPath");
0431:
0432: // extract the icon image from the jar file and place it in
0433: // the icons/ directory within the app repository
0434: String iconPath = extractIconFromJar(jarPath, iconFileName
0435: .trim(), appIDHashKey);
0436:
0437: // create an app descriptor file in the menu/ directory for
0438: // the new app so that the appmanager can recognize it.
0439: // make sure the descriptor pathname is uniqe and doesn't exist.
0440: String descriptorsDir = REPOSITORY_DESCRIPTORS_DIRNAME + '/';
0441:
0442: appProperties.setProperty(DESCRIPTOR_BUNDLENAME_KEY,
0443: bundleName);
0444: appProperties.setProperty(DESCRIPTOR_APPMODEL_KEY, app
0445: .getProperty("JUMPApplication_appModel"));
0446: appProperties
0447: .setProperty(
0448: getInstallerInitialClassKey(),
0449: app
0450: .getProperty(getPropertyInstallerInitialClassKey()));
0451: appProperties.setProperty(DESCRIPTOR_JARPATH_KEY, jarPath);
0452: appProperties.setProperty(DESCRIPTOR_TITLE_KEY, appTitle);
0453: if (iconPath != null) {
0454: appProperties
0455: .setProperty(DESCRIPTOR_ICON_KEY, iconPath);
0456: }
0457: String securityLevel = desc.getSecurityLevel();
0458: if (securityLevel != null) {
0459: appProperties.setProperty(DESCRIPTOR_SECURITYLEVEL_KEY,
0460: securityLevel);
0461: }
0462:
0463: String appDescriptorPath = descriptorsDir + appTitle + '-'
0464: + appIDHashKey + APP_DESCRIPTOR_EXTENSION;
0465: if (new File(appDescriptorPath).exists()) {
0466: System.out
0467: .println("*** Error installing bundle: A descriptor with this name is already installed.");
0468: return null;
0469: }
0470: // create application descriptor file
0471: boolean result = createAppDescriptor(appDescriptorPath,
0472: appProperties);
0473: // create JUMPApplication object for the app
0474: if (result) {
0475: JUMPApplication module = createJUMPApplication(appDescriptorPath);
0476: trace("--> createJUMPApplication returns: "
0477: + module.toString());
0478: if (module != null) {
0479: installedContentHashtable.put(appIDHashKey, module);
0480: addInstalledAppIdEntry(appIDHashKey, module);
0481: }
0482: }
0483: }
0484:
0485: // return installed content
0486: int size = installedContentHashtable.size();
0487: if (size > 0) {
0488: Vector jumpAppObjectsVector = new Vector();
0489: JUMPContent content[] = new JUMPContent[size];
0490: for (Enumeration e = installedContentHashtable.keys(); e
0491: .hasMoreElements();) {
0492: Object key = e.nextElement();
0493: Object app = installedContentHashtable.get(key);
0494: jumpAppObjectsVector.add(app);
0495: }
0496: return (JUMPContent[]) jumpAppObjectsVector
0497: .toArray(new JUMPContent[] {});
0498: } else {
0499: return null;
0500: }
0501: };
0502:
0503: /**
0504: * Uninstall content.
0505: * @param content the object to be uninstalled
0506: */
0507: public void uninstall(JUMPContent content) {
0508: // sanity check
0509: if (content == null) {
0510: return;
0511: }
0512:
0513: JUMPApplication app = (JUMPApplication) content;
0514:
0515: String bundleName = getBundleName(app);
0516: JUMPApplication[] apps = getAppsInBundle(bundleName);
0517:
0518: if (apps == null || bundleName == null) {
0519: return;
0520: }
0521:
0522: // Currently, calling JUMPExecutive.getInstance returns null. Therefore, we
0523: // cannot use the JUMPUserInputManager APIs until this is fixed. When this is
0524: // fixed, the following code can be uncommented.
0525:
0526: // if (apps.length > 1) {
0527: // JUMPExecutive executive = JUMPExecutive.getInstance();
0528: // if (executive == null) {
0529: // System.out.println("ERROR: The JUMP Executive instance is null.");
0530: // return;
0531: // }
0532: // JUMPUserInputManager uiManager = executive.getUserInputManager();
0533: // String str = "This application belongs to the bundle: " + bundleName + " which contains multiple applications. Do you wish to remove the bundle and all of its applications?";
0534: // boolean rc = uiManager.showDialog(str, null, "OK", "Cancel");
0535: // if (!rc) {
0536: // return;
0537: // }
0538: // }
0539:
0540: if (apps.length > 1) {
0541: System.out.print(app.getAppType().toString());
0542: System.out.print(": " + app.getTitle()
0543: + " is contained within a bundle");
0544: System.out
0545: .println("that contains the following applications:");
0546: System.out.print(" ");
0547: for (int i = 0; i < apps.length; i++) {
0548: JUMPApplication application = (JUMPApplication) apps[i];
0549: System.out.print(application.getTitle());
0550: if (i < (apps.length - 1)) {
0551: System.out.print(", ");
0552: }
0553: }
0554: System.out.println("");
0555: // String value = System.getProperty("jump.installer.interactive");
0556: // if (value.toLowerCase().equals("true")) {
0557: // System.out.println("Deleting this bundle will remove all of the applications.");
0558: // while ( true ) {
0559: // String message = "Do you wish to proceed? [y/n]: ";
0560: // String answer = Utilities.promptUser(message);
0561: // if (answer.toLowerCase().equals("y")) {
0562: // break;
0563: // } else if (answer.toLowerCase().equals("n")){
0564: // return;
0565: // } else {
0566: // System.out.println("ERROR: Illegal response.");
0567: // }
0568: // }
0569: //
0570: // } else {
0571: System.out.println("All applications will be removed.");
0572: // }
0573: }
0574:
0575: trace(getString("AttemptingToRemove") + bundleName);
0576:
0577: // Get the path to the app bundle's jar file, which is assumed
0578: // to be the first entry in the classpath.
0579: String jarPath = getAppClasspath(app);
0580: boolean result1 = removeJarFile(jarPath);
0581: if (!result1) {
0582: trace(getString("CannotRemoveApplicationJar") + ": "
0583: + jarPath);
0584: }
0585:
0586: // Remove the icon and app descriptor for each app in the bundle
0587: for (int i = 0; i < apps.length; i++) {
0588:
0589: int appId = apps[i].getId();
0590: removeInstalledAppIdEntry(Integer.toString(appId));
0591:
0592: // Remove the icon and app descriptor for each app
0593: boolean result2 = removeAppDescriptor(apps[i]);
0594: if (!result2) {
0595: trace(getString("CouldNotRemoveAppDescriptor")
0596: + apps[i].getTitle());
0597: }
0598:
0599: boolean result3 = removeIcon(apps[i].getIconPath());
0600: if (!result3) {
0601: trace(getString("CouldNotRemoveIcon")
0602: + apps[i].getTitle());
0603: }
0604:
0605: if (result1 && result2 && result3) {
0606: System.out.println(getString("SuccessfulUninstall")
0607: + apps[i].getTitle());
0608: }
0609: }
0610: };
0611:
0612: /**
0613: * Update content from given location. For XLET and Main applications, the behavior is to uninstall the current bundle and install the new bundle.
0614: * @param content object to be updated
0615: * @param location URL location of content to update with
0616: * @param desc object describing the bundle to update with
0617: */
0618: public void update(JUMPContent content, URL location,
0619: JUMPDownloadDescriptor desc) {
0620: uninstall(content);
0621: install(location, desc);
0622: };
0623:
0624: /**
0625: * Get all installed content of type XLET
0626: * @return Array of JUMPApplication objects that are XLETs
0627: */
0628: public JUMPContent[] getInstalled() {
0629:
0630: Vector nodeVector = new Vector();
0631:
0632: storeHandle = openStore(true);
0633:
0634: // get the listing of all nodes starting at the root.
0635: JUMPNode.List list = null;
0636: try {
0637: list = (JUMPNode.List) storeHandle
0638: .getNode(REPOSITORY_DESCRIPTORS_DIRNAME);
0639: } catch (IOException e) {
0640: trace("Exception in getNode(): " + e.toString());
0641: }
0642:
0643: closeStore(storeHandle);
0644:
0645: if (list == null) {
0646: return null;
0647: }
0648:
0649: for (Iterator itn = list.getChildren(); itn.hasNext();) {
0650: JUMPNode node = (JUMPNode) itn.next();
0651: JUMPApplication app = createJUMPApplication(node.getURI());
0652:
0653: // Identify only the xlets or main apps, not both at the same time
0654: if (app != null
0655: && app.getAppType() == getInstallerAppModel()) {
0656: nodeVector.add(app);
0657: }
0658: }
0659: return (JUMPApplication[]) nodeVector
0660: .toArray(new JUMPApplication[] {});
0661: };
0662:
0663: /**
0664: * Given the application object, return the name of the bundle the application belongs to
0665: * @param app application object
0666: * @return the names of the bundle this application belongs to
0667: */
0668: protected String getBundleName(JUMPApplication app) {
0669: XLETApplication xletApp = (XLETApplication) app;
0670: return xletApp.getBundle();
0671: }
0672:
0673: /**
0674: * Given the bundle name, return the application objects within the bundle
0675: * @param bundle name of content bundle
0676: * @return the application objects belonging to the bundle
0677: */
0678: protected JUMPApplication[] getAppsInBundle(String bundle) {
0679: JUMPApplication[] apps = (JUMPApplication[]) getInstalled();
0680: Vector appsVector = new Vector();
0681: for (int i = 0; i < apps.length; i++) {
0682: XLETApplication xletApp = (XLETApplication) apps[i];
0683: if (xletApp.getBundle().equals(bundle)) {
0684: appsVector.add(apps[i]);
0685: }
0686: }
0687: Object[] objs = appsVector.toArray();
0688: JUMPApplication[] bundleApps = new JUMPApplication[objs.length];
0689: for (int i = 0; i < objs.length; i++) {
0690: bundleApps[i] = (JUMPApplication) objs[i];
0691: }
0692: return bundleApps;
0693: }
0694:
0695: private String extractIconFromJar(String jarFile, String iconFile) {
0696: return (extractIconFromJar(jarFile, iconFile, null));
0697: }
0698:
0699: private String extractIconFromJar(String jarFile, String iconFile,
0700: String id) {
0701:
0702: String iconFileName = null;
0703: String iconFilePath = null;
0704:
0705: JarFile jar = null;
0706:
0707: try {
0708: jar = new JarFile(jarFile);
0709: } catch (Exception e) {
0710: e.printStackTrace();
0711: return null;
0712: }
0713:
0714: trace("extractIconFromJar(): jarfile: " + jarFile + " icon: "
0715: + iconFile);
0716:
0717: ZipEntry entry = jar.getEntry(iconFile);
0718: if (entry == null) {
0719: trace(getString("CouldNotExtract") + iconFile);
0720: return null;
0721: }
0722:
0723: int index = iconFile.lastIndexOf('/');
0724: if (index != -1) {
0725: iconFileName = iconFile.substring(index + 1, iconFile
0726: .length());
0727: } else {
0728: iconFileName = iconFile;
0729: }
0730:
0731: if (id != null) {
0732: // Get extention of file
0733: int dotindex = iconFileName.lastIndexOf('.');
0734: if (dotindex == -1) {
0735: return null;
0736: }
0737:
0738: // The path up until the extention.
0739: String pathToExtention = iconFileName
0740: .substring(0, dotindex);
0741:
0742: // The extention
0743: String extention = iconFileName.substring(dotindex);
0744:
0745: iconFileName = pathToExtention + '-' + id + extention;
0746: }
0747:
0748: String iconsDir = contentStoreDir + REPOSITORY_ICONS_DIRNAME
0749: + '/';
0750: iconFilePath = iconsDir + iconFileName;
0751: if (new File(iconFilePath).exists()) {
0752: System.out
0753: .println("*** Warning installing bundle: An icon with this name is already installed.");
0754: return iconFilePath;
0755: } else {
0756: trace("Saving icon file: " + iconFilePath);
0757: }
0758:
0759: try {
0760:
0761: InputStream zis = jar.getInputStream(entry);
0762:
0763: int size = (int) entry.getSize();
0764: // -1 means unknown size.
0765: if (size == -1) {
0766: trace(getString("IconFileSizeError"));
0767: return null;
0768: }
0769:
0770: byte[] buffer = copyBuffer(zis, size);
0771:
0772: File f = new File(iconFilePath);
0773: FileOutputStream fos = new FileOutputStream(f);
0774: fos.write(buffer);
0775: fos.close();
0776:
0777: return iconFilePath;
0778:
0779: } catch (Exception e) {
0780: e.printStackTrace();
0781: }
0782: return null;
0783: }
0784:
0785: /**
0786: * This is necessary to avoid issues when downloading and installing the
0787: * same app on the device, or at least an app with the same exact name.
0788: * Simply concat a number, starting with 2, to the end of the path until
0789: * a path is found that doesn't already exist.
0790: */
0791: private String createUniquePathName(String original) {
0792:
0793: int NUM = 2;
0794: int LIMIT = 1000;
0795:
0796: // Get extention of file
0797: int dotindex = original.lastIndexOf('.');
0798: if (dotindex == -1) {
0799: return null;
0800: }
0801:
0802: // The path up until the extention.
0803: String pathToExtention = original.substring(0, dotindex);
0804:
0805: // The extention
0806: String extention = original.substring(dotindex);
0807:
0808: // Get the file name minus the extention
0809: int fileSeparatorIndex = original.lastIndexOf('/');
0810: String fileName = null;
0811: if (fileSeparatorIndex != -1) {
0812: fileName = original.substring(fileSeparatorIndex + 1,
0813: dotindex);
0814: } else {
0815: return null;
0816: }
0817:
0818: // First, check if the original name is unique
0819: String testPath = original;
0820: File testPathFile = new File(testPath);
0821: while (testPathFile.exists() && NUM < LIMIT) {
0822: String EXTRA = '(' + Integer.toString(NUM) + ')';
0823: testPath = pathToExtention + EXTRA + extention;
0824: testPathFile = new File(testPath);
0825:
0826: // Need to change the parent directory of the file if this
0827: // is a jarfile
0828: if (testPath.endsWith(".jar")) {
0829: String parentPath = testPathFile.getParent();
0830: testPath = parentPath + EXTRA + '/' + fileName + EXTRA
0831: + extention;
0832: testPathFile = new File(testPath);
0833: }
0834: NUM++;
0835: }
0836:
0837: if (NUM == LIMIT) {
0838: return null;
0839: }
0840:
0841: return testPath;
0842: }
0843:
0844: /**
0845: * Create an application descriptor for the given application
0846: * values. The application descriptor gets saved into the
0847: * application repository's menu directory.
0848: * @param descriptorPath the path within the content store to store the application descriptor
0849: * @param props object containing the properties of the application descriptor
0850: * @return boolean value indicating success or failure
0851: */
0852: private boolean createAppDescriptor(String descriptorPath,
0853: Properties props) {
0854:
0855: // Convert the paths to the relative path from the content store root,
0856: // we don't want to store the absolute path to the persistant store
0857: removeContentStorePath(props, DESCRIPTOR_ICON_KEY);
0858: removeContentStorePath(props, DESCRIPTOR_JARPATH_KEY);
0859:
0860: JUMPData propData = new JUMPData(props);
0861:
0862: try {
0863: storeHandle.createDataNode(descriptorPath, propData);
0864: } catch (RuntimeException re) {
0865: re.printStackTrace();
0866: return false;
0867: } catch (IOException ioe) {
0868: ioe.printStackTrace();
0869: return false;
0870: }
0871:
0872: return true;
0873: }
0874:
0875: /**
0876: * Retrieve an instance of JUMPApplication for the given application or menu.
0877: */
0878: private JUMPApplication createJUMPApplication(String descriptorPath) {
0879:
0880: storeHandle = openStore(true);
0881: JUMPNode appDescriptorNode = null;
0882: try {
0883: appDescriptorNode = storeHandle.getNode(descriptorPath);
0884: } catch (IOException e) {
0885: e.printStackTrace();
0886: }
0887: closeStore(storeHandle);
0888:
0889: if (appDescriptorNode == null) {
0890: trace(getString("AppDescriptorNotFound") + descriptorPath);
0891: return null;
0892: }
0893:
0894: JUMPData appDescriptorData = null;
0895: if (appDescriptorNode.containsData()) {
0896: appDescriptorData = ((JUMPNode.Data) appDescriptorNode)
0897: .getData();
0898: } else {
0899: return null;
0900: }
0901:
0902: Properties appDescriptorProps = (Properties) appDescriptorData
0903: .getValue();
0904: JUMPApplication module = null;
0905:
0906: // First, make sure the app type is correct
0907: String appType = (String) appDescriptorProps
0908: .getProperty(DESCRIPTOR_APPMODEL_KEY);
0909: if (appType == null
0910: || !appType.equals(getInstallerAppModel().getName())) {
0911: return null;
0912: }
0913:
0914: // Convert back the path to the absolute path
0915: addContentStorePath(appDescriptorProps, DESCRIPTOR_ICON_KEY);
0916: addContentStorePath(appDescriptorProps, DESCRIPTOR_JARPATH_KEY);
0917:
0918: // Now, obtain the values for specific keys
0919: String classPath = (String) appDescriptorProps
0920: .getProperty(DESCRIPTOR_JARPATH_KEY);
0921: if (classPath == null) {
0922: return null;
0923: }
0924:
0925: URL classPathURL = null;
0926: try {
0927: classPathURL = new URL("file", null, classPath);
0928: if (classPathURL == null) {
0929: return null;
0930: }
0931: } catch (MalformedURLException ex) {
0932: ex.printStackTrace();
0933: return null;
0934: }
0935:
0936: String bundleName = (String) appDescriptorProps
0937: .getProperty(DESCRIPTOR_BUNDLENAME_KEY);
0938: if (bundleName == null) {
0939: return null;
0940: }
0941:
0942: String title = (String) appDescriptorProps
0943: .getProperty(DESCRIPTOR_TITLE_KEY);
0944: if (title == null) {
0945: return null;
0946: }
0947:
0948: String iconPath = (String) appDescriptorProps
0949: .getProperty(DESCRIPTOR_ICON_KEY);
0950: URL iconPathURL = null;
0951: if (iconPath != null) {
0952: try {
0953: iconPathURL = new URL("file", null, iconPath);
0954: if (iconPathURL == null) {
0955: return null;
0956: }
0957: } catch (MalformedURLException ex) {
0958: ex.printStackTrace();
0959: return null;
0960: }
0961: } else {
0962: trace("Application: " + title + " doesn't have an icon.");
0963: }
0964:
0965: String clazz = (String) appDescriptorProps
0966: .getProperty(getInstallerInitialClassKey());
0967:
0968: String id = (String) appDescriptorProps.getProperty("id");
0969:
0970: module = createJUMPApplicationObject(bundleName, clazz,
0971: classPathURL, title, iconPathURL, Integer.parseInt(id));
0972:
0973: return module;
0974: }
0975:
0976: /**
0977: * Removes the content store dir path from the property represented by the key.
0978: * Used when creating .app file from the JUMPApplication properties list,
0979: * so that .app file in the content sore will not have a full path to store,
0980: * allowing the store to be moved from one location to another.
0981: */
0982: private void removeContentStorePath(Properties props, String key) {
0983: String value = (String) props.remove(key);
0984: if (value == null)
0985: return;
0986: props.put(key, value.substring(contentStoreDir.length()));
0987: }
0988:
0989: /**
0990: * Prepends the content store dir path to the property represented by the key.
0991: * Used when creating JUMPApplication properties from the .app file
0992: * in the content store, so that the JUMPApplication created have
0993: * proper absolute path corresponding to the current run's contentstore
0994: * root location.
0995: */
0996: private void addContentStorePath(Properties props, String key) {
0997: String value = (String) props.remove(key);
0998: if (value == null)
0999: return;
1000: props.put(key, contentStoreDir + value);
1001: }
1002:
1003: /**
1004: * Create an instance of JUMPApplication
1005: * @param bundle Name of application bundle that this application belongs to
1006: * @param clazz Initial class of application
1007: * @param classPathURL URL of the classpath of this application
1008: * @param title The user visible title of this application
1009: * @param iconPathURL URL to the path of the icon for this application
1010: * @return application object
1011: */
1012: protected JUMPApplication createJUMPApplicationObject(
1013: String bundle, String clazz, URL classPathURL,
1014: String title, URL iconPathURL, int id) {
1015: return new XLETApplication(contentStoreDir, bundle, clazz,
1016: classPathURL, title, iconPathURL, id);
1017: }
1018:
1019: /**
1020: * Return the application type that this installer module can install
1021: * @return the application type
1022: */
1023: protected JUMPAppModel getInstallerAppModel() {
1024: return JUMPAppModel.XLET;
1025: }
1026:
1027: /**
1028: * Get the key value used to specify an application's initial class
1029: * @return initial class key value
1030: */
1031: protected String getInstallerInitialClassKey() {
1032: return "xletName";
1033: }
1034:
1035: /**
1036: * Get the key value used to specify an application's initial class from Properties object
1037: * @return initial class key value
1038: */
1039: protected String getPropertyInstallerInitialClassKey() {
1040: return XLETApplication.INITIAL_CLASS_KEY;
1041: }
1042:
1043: /**
1044: * Remove the jar file from the content store.
1045: * The current implementation uses java.util.File methods.
1046: * @param jarPath path to the jar file within the content store
1047: * @return boolean value indicating success or failure
1048: */
1049: private boolean removeJarFile(String jarPath) {
1050:
1051: // Check to see that the jar file exists, and if so, start
1052: // removing things.
1053: File jarFile = new File(jarPath);
1054: trace("removeJarFile(): " + jarFile);
1055: if (jarFile != null && jarFile.exists()) {
1056: trace("jarFile: " + jarFile + " exists!!! ***");
1057: boolean jarFileDelete = false;
1058: boolean jarFileParentDirDelete = false;
1059: File jarFileParent = null;
1060: try {
1061: jarFileParent = jarFile.getParentFile();
1062: jarFileDelete = jarFile.delete();
1063: if (jarFileParent != null) {
1064: jarFileParentDirDelete = jarFileParent.delete();
1065: }
1066: } catch (Exception e) {
1067: e.printStackTrace();
1068: }
1069:
1070: if (!jarFileDelete) {
1071: trace(getString("CannotRemoveApplicationJar"));
1072: }
1073: // Print out a message if we cannot remove the parent directory,
1074: // but continue on...
1075: if (!jarFileParentDirDelete) {
1076: trace(getString("CouldNotRemoveApplicationDir"));
1077: }
1078: return true;
1079: } else {
1080: trace(getString("ApplicationJarDoesNotExist"));
1081: }
1082:
1083: return false;
1084: }
1085:
1086: /**
1087: * Remove application descriptor from content store
1088: * @param applicationName the application for which the application descriptor should be removed
1089: * @return boolean value indicating success or failure
1090: */
1091: private boolean removeAppDescriptor(JUMPApplication app) {
1092: String appTitle = app.getTitle();
1093: String id = Integer.toString(app.getId());
1094: String uri = REPOSITORY_DESCRIPTORS_DIRNAME + '/' + appTitle
1095: + '-' + id + APP_DESCRIPTOR_EXTENSION;
1096: storeHandle = openStore(true);
1097: if (storeHandle == null) {
1098: return false;
1099: }
1100: try {
1101: storeHandle.deleteNode(uri);
1102: } catch (IOException e) {
1103: return false;
1104: }
1105: closeStore(storeHandle);
1106: return true;
1107: }
1108:
1109: /**
1110: * Remove an icon file from within the content store
1111: * @param iconURL URL of icon file within the content store
1112: * @return boolean value indicating success or failure
1113: */
1114: private boolean removeIcon(URL iconURL) {
1115: String iconFileName = iconURL.getFile();
1116: trace("Trying to remove icon file: " + iconFileName);
1117: File iconFile = new File(iconFileName);
1118: if (iconFile != null && iconFile.exists()) {
1119: try {
1120: iconFile.delete();
1121: } catch (Exception e) {
1122: e.printStackTrace();
1123: return false;
1124: }
1125: } else {
1126: return false;
1127: }
1128: return true;
1129: }
1130:
1131: /**
1132: * read from an input stream into a byte buffer[]
1133: * @param is the input stream where the data is located
1134: * @param size the number of bytes to read
1135: * @return byte buffer of read data
1136: */
1137: private byte[] copyBuffer(InputStream is, int size) {
1138: if (is == null) {
1139: return null;
1140: }
1141:
1142: byte[] buffer = new byte[size];
1143: try {
1144:
1145: // Read data into buffer
1146: int rb = 0;
1147: int chunk = 0;
1148: while ((size - rb) > 0) {
1149: chunk = is.read(buffer, rb, size - rb);
1150: if (chunk == -1) {
1151: break;
1152: }
1153: rb += chunk;
1154: }
1155: } catch (IOException ex) {
1156: ex.printStackTrace();
1157: return null;
1158: }
1159:
1160: return buffer;
1161: }
1162:
1163: /**
1164: * Obtain the classpath value of the application object
1165: * @param app application object
1166: * @return classpath value of the application object
1167: */
1168: protected String getAppClasspath(JUMPApplication app) {
1169: if (app == null) {
1170: return null;
1171: }
1172: XLETApplication xletApp = (XLETApplication) app;
1173: return xletApp.getClasspath().getFile();
1174: }
1175:
1176: /**
1177: * Retrieve a String value from the module's resource bundle
1178: * @param key key value into resource bundle
1179: * @return value pertaining to the key field in the resource bundle
1180: */
1181: private String getString(String key) {
1182: return InstallerFactoryImpl.getString(key);
1183: }
1184:
1185: /**
1186: * Simple print command that prints messages if verbose is on
1187: * @param s string to print
1188: */
1189: private void trace(String s) {
1190: if (verbose) {
1191: System.out.println(s);
1192: }
1193: return;
1194: }
1195: }
|