| /******************************************************************************* |
| * Copyright (c) 2005, 2006 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.core.launcher; |
| |
| import java.io.IOException; |
| import java.net.MalformedURLException; |
| import java.net.URL; |
| import java.util.*; |
| import java.util.jar.JarFile; |
| import java.util.jar.Manifest; |
| |
| /** |
| * The launcher ot start eclipse using webstart. |
| * To use this launcher, the client must accept to give all security permissions. |
| */ |
| //The bundles are discovered by finding all the jars on the classpath. Then they are added with their full path to the osgi.bundles list. |
| public class WebStartMain extends Main { |
| private static final String PROP_WEBSTART_AUTOMATIC_INSTALLATION = "eclipse.webstart.automaticInstallation"; //$NON-NLS-1$ |
| private static final String DEFAULT_OSGI_BUNDLES = "org.eclipse.equinox.common@2:start, org.eclipse.core.runtime@start"; //$NON-NLS-1$ |
| private static final String PROP_OSGI_BUNDLES = "osgi.bundles"; //$NON-NLS-1$ |
| private static final String PROP_WEBSTART_PRECISE_BUNDLEID = "eclipse.webstart.preciseBundleId"; //$NON-NLS-1$ |
| private static final String JAVA_6 = "1.6"; |
| |
| private String[] allJars = null; //List all the jars that are on the classpath |
| private Map bundleList = null; //Map an entry (the part before the @) from the osgi.bundles list to a list of URLs. Ie: org.eclipse.core.runtime --> file:c:/foo/org.eclipse.core.runtime_3.1.0/..., file:c:/bar/org.eclipse.core.runtime/... |
| private Map bundleStartInfo = null; //Keep track of the start level info for each bundle from the osgi.bundle list. |
| |
| private boolean preciseIdExtraction = false; //Flag indicating if the extraction of the id must be done by looking at bundle ids. |
| |
| public static void main(String[] args) { |
| System.setSecurityManager(null); //TODO Hack so that when the classloader loading the fwk is created we don't have funny permissions. This should be revisited. |
| int result = new WebStartMain().run(args); |
| System.exit(result); |
| } |
| |
| private void setDefaultBundles() { |
| if (System.getProperty(PROP_OSGI_BUNDLES) != null) |
| return; |
| System.getProperties().put(PROP_OSGI_BUNDLES, DEFAULT_OSGI_BUNDLES); |
| } |
| |
| protected void basicRun(String[] args) throws Exception { |
| preciseIdExtraction = Boolean.getBoolean(PROP_WEBSTART_PRECISE_BUNDLEID); |
| if (checkVersion(System.getProperty("java.version"), JAVA_6)) |
| preciseIdExtraction = true; |
| setDefaultBundles(); |
| addOSGiBundle(); |
| initializeBundleListStructure(); |
| mapURLsToBundleList(); |
| //Set the fwk location since the regular lookup would not find it |
| String fwkURL = searchFor(framework, null); |
| System.getProperties().put(PROP_FRAMEWORK, fwkURL); |
| super.basicRun(args); |
| } |
| |
| private void addOSGiBundle() { |
| //Add osgi to the bundle list, so we can beneficiate of the infrastructure to find its location. It will be removed from the list later on |
| System.getProperties().put(PROP_OSGI_BUNDLES, System.getProperty(PROP_OSGI_BUNDLES) + ',' + framework); |
| } |
| |
| protected URL[] getBootPath(String base) throws IOException { |
| URL[] result = super.getBootPath(base); |
| buildOSGiBundleList(); |
| cleanup(); |
| return result; |
| } |
| |
| /* |
| * Null out all the fields containing data |
| */ |
| private void cleanup() { |
| allJars = null; |
| bundleList = null; |
| bundleStartInfo = null; |
| } |
| |
| /* |
| * Find the target bundle among all the jars that are on the classpath. |
| * The start parameter is not used in this context |
| */ |
| protected String searchFor(final String target, String start) { |
| ArrayList matches = (ArrayList) bundleList.get(target); |
| int numberOfURLs = matches.size(); |
| if (numberOfURLs == 1) { |
| return extractInnerURL((String) matches.get(0)); |
| } |
| if (numberOfURLs == 0) |
| return null; |
| String urls[] = new String[numberOfURLs]; |
| return extractInnerURL(urls[findMax((String[]) matches.toArray(urls))]); |
| } |
| |
| /* |
| * Get all the jars available on the webstart classpath |
| */ |
| private String[] getAllJars() { |
| if (allJars != null) |
| return allJars; |
| |
| ArrayList collector = new ArrayList(); |
| try { |
| Enumeration resources = WebStartMain.class.getClassLoader().getResources(JarFile.MANIFEST_NAME); |
| while (resources.hasMoreElements()) { |
| collector.add(((URL) resources.nextElement()).toExternalForm()); |
| } |
| } catch (IOException e) { |
| e.printStackTrace(); |
| } |
| allJars = new String[collector.size()]; |
| collector.toArray(allJars); |
| if (debug) |
| printArray("Jars found on the webstart path:\n", allJars); //$NON-//$NON-NLS-1$ |
| |
| return allJars; |
| } |
| |
| /* |
| * Extract the inner URL from a string representing a JAR url string. |
| */ |
| private String extractInnerURL(String url) { |
| if (url.startsWith(JAR_SCHEME)) { |
| url = url.substring(url.indexOf(JAR_SCHEME) + 4); |
| } |
| int lastBang = url.lastIndexOf('!'); |
| if (lastBang != -1) { |
| url = url.substring(0, lastBang); |
| } |
| return decode(url); |
| } |
| |
| private void printArray(String header, String[] values) { |
| System.err.println(header); |
| for (int i = 0; i < values.length; i++) { |
| System.err.println("\t" + values[i]); //$NON-NLS-1$ |
| } |
| } |
| |
| |
| /* |
| * Initialize the data structure corresponding to the osgi.bundles list |
| */ |
| private void initializeBundleListStructure() { |
| final char STARTLEVEL_SEPARATOR = '@'; |
| |
| //In webstart the bundles list can only contain bundle names with or without a version. |
| String prop = System.getProperty(PROP_OSGI_BUNDLES); |
| if (prop == null || prop.trim().equals("")) { //$NON-NLS-1$ |
| bundleList = new HashMap(0); |
| return; |
| } |
| |
| bundleList = new HashMap(10); |
| bundleStartInfo = new HashMap(10); |
| StringTokenizer tokens = new StringTokenizer(prop, ","); //$NON-NLS-1$ |
| while (tokens.hasMoreTokens()) { |
| String token = tokens.nextToken().trim(); |
| String bundleId = token; |
| if (token.equals("")) //$NON-NLS-1$ |
| continue; |
| int startLevelSeparator; |
| if ((startLevelSeparator = token.lastIndexOf(STARTLEVEL_SEPARATOR)) != -1) { |
| bundleId = token.substring(0, startLevelSeparator); |
| bundleStartInfo.put(bundleId, token.substring(startLevelSeparator)); |
| } |
| bundleList.put(bundleId, new ArrayList(1)); // put a list with one element as it is likely that the element will be present |
| } |
| |
| } |
| |
| /* |
| * Associate urls from the list of jars with a bundle from the bundle list |
| */ |
| private void mapURLsToBundleList() { |
| String[] allJars = getAllJars(); |
| for (int i = 0; i < allJars.length; i++) { |
| Object[] bundleInfo = extractBundleId(allJars[i]); |
| if (bundleInfo == null) |
| continue; |
| String bsn = (String) bundleInfo[0]; |
| if (bsn == null) |
| continue; |
| String version = (String) bundleInfo[1]; |
| ArrayList bundleURLs = null; |
| if (bsn != null && version != null) { |
| bundleURLs = (ArrayList) bundleList.get(bsn+ '_' + version); |
| } |
| |
| if (bundleURLs == null) { |
| bundleURLs = (ArrayList) bundleList.get(bsn); |
| if (bundleURLs == null) |
| continue; |
| } |
| bundleURLs.add(allJars[i]); |
| allJars[i] = null; //Remove the entry from the list |
| } |
| } |
| |
| /* |
| * return a string of the form <bundle>_<version> |
| */ |
| private Object[] extractBundleId(String url) { |
| if (preciseIdExtraction) |
| return extractBundleIdFromManifest(url); |
| else |
| return extractBundleIdFromBundleURL(url); |
| } |
| |
| private Object[] extractBundleIdFromManifest(String url) { |
| final String BUNDLE_SYMBOLICNAME = "Bundle-SymbolicName"; //$NON-NLS-1$ |
| final String BUNDLE_VERSION = "Bundle-Version"; //$NON-NLS-1$ |
| |
| Manifest mf; |
| try { |
| mf = new Manifest(new URL(url).openStream()); |
| String symbolicNameString = mf.getMainAttributes().getValue(BUNDLE_SYMBOLICNAME); |
| if (symbolicNameString==null) |
| return null; |
| |
| String bundleVersion = mf.getMainAttributes().getValue(BUNDLE_VERSION); |
| |
| int pos = symbolicNameString.lastIndexOf(';'); |
| if (pos != -1) |
| return new Object[] {symbolicNameString.substring(0, pos), bundleVersion}; |
| return new Object[] {symbolicNameString, bundleVersion}; |
| } catch (MalformedURLException e) { |
| e.printStackTrace(); |
| //Ignore |
| } catch (IOException e) {e.printStackTrace(); |
| //Ignore |
| } |
| return null; |
| |
| } |
| |
| private Object[] extractBundleIdFromBundleURL(String url) { |
| //First extract the relevant part of the URL |
| int lastBang = url.lastIndexOf('!'); |
| if (lastBang == -1) |
| return null; |
| boolean jarSuffix = url.regionMatches(true, lastBang - 4, ".jar", 0, 4); //$NON-NLS-1$ |
| int bundleIdStart = url.lastIndexOf('/', lastBang); |
| String fileName = url.substring(bundleIdStart + 3, lastBang - (jarSuffix ? 4 : 0)); // + 3 because URLs from webstart have a funny prefix |
| |
| //Separate the version from the bsn |
| String bsn = null; |
| String version = null; |
| int underScore = fileName.indexOf('_'); |
| while (underScore >= 0) { |
| bsn = fileName.substring(0, underScore); |
| version = fileName.substring(underScore + 1); |
| if (! isValidVersion(version)) { |
| underScore = fileName.indexOf('_', underScore + 1); |
| } else { |
| break; |
| } |
| } |
| return new Object[] {bsn, version}; |
| } |
| |
| private void buildOSGiBundleList() { |
| //Remove the framework from the bundle list because it does not need to be installed. See addOSGiBundle |
| bundleList.remove(framework); |
| |
| String[] jarsOnClasspath = getAllJars(); |
| StringBuffer finalBundleList = new StringBuffer(jarsOnClasspath.length * 25); |
| |
| //Add the bundles from the bundle list. |
| Collection allSelectedBundles = bundleList.entrySet(); |
| for (Iterator iter = allSelectedBundles.iterator(); iter.hasNext();) { |
| Map.Entry entry = (Map.Entry) iter.next(); |
| ArrayList matches = (ArrayList) entry.getValue(); |
| int numberOfURLs = matches.size(); |
| |
| //Get the start info |
| String startInfo = (String) bundleStartInfo.get(entry.getKey()); |
| if (startInfo == null) |
| startInfo = ""; //$NON-NLS-1$ |
| |
| if (numberOfURLs == 1) { |
| finalBundleList.append(REFERENCE_SCHEME).append(extractInnerURL((String) matches.get(0))).append(startInfo).append(','); |
| continue; |
| } |
| if (numberOfURLs == 0) |
| continue; |
| String urls[] = new String[numberOfURLs]; |
| int found = findMax((String[]) matches.toArray(urls)); |
| for (int i = 0; i < urls.length; i++) { |
| if (i != found) |
| continue; |
| finalBundleList.append(REFERENCE_SCHEME).append(extractInnerURL((String) urls[found])).append(startInfo).append(','); |
| } |
| } |
| |
| //Add all the other bundles if required - the common case is to add those |
| if (! Boolean.FALSE.toString().equalsIgnoreCase(System.getProperties().getProperty(PROP_WEBSTART_AUTOMATIC_INSTALLATION))) { |
| for (int i = 0; i < jarsOnClasspath.length; i++) { |
| if (jarsOnClasspath[i] != null) |
| finalBundleList.append(REFERENCE_SCHEME).append(extractInnerURL(jarsOnClasspath[i])).append(','); |
| } |
| } |
| |
| System.getProperties().put(PROP_OSGI_BUNDLES, finalBundleList.toString()); |
| if (debug) |
| log(finalBundleList.toString()); |
| } |
| |
| private boolean isValidVersion(String version) { |
| int major = 0; |
| int minor = 0; |
| int micro = 0; |
| String qualifier = ""; //$NON-NLS-1$ |
| final String SEPARATOR = "."; |
| |
| try { |
| StringTokenizer st = new StringTokenizer(version, SEPARATOR, true); |
| major = Integer.parseInt(st.nextToken()); |
| |
| if (st.hasMoreTokens()) { |
| st.nextToken(); // consume delimiter |
| minor = Integer.parseInt(st.nextToken()); |
| |
| if (st.hasMoreTokens()) { |
| st.nextToken(); // consume delimiter |
| micro = Integer.parseInt(st.nextToken()); |
| |
| if (st.hasMoreTokens()) { |
| st.nextToken(); // consume delimiter |
| qualifier = st.nextToken(); |
| |
| if (st.hasMoreTokens()) { |
| return false; |
| } |
| } |
| } |
| } |
| } |
| catch (NoSuchElementException e) { |
| return false; |
| } |
| catch (NumberFormatException e) { |
| return false; |
| } |
| |
| return isValidVersionSegment(major, minor, micro, qualifier); |
| } |
| |
| private boolean isValidVersionSegment(int major, int minor, int micro, String qualifier) { |
| if (major < 0) { |
| return false; |
| } |
| if (minor < 0) { |
| return false; |
| } |
| if (micro < 0) { |
| } |
| int length = qualifier.length(); |
| for (int i = 0; i < length; i++) { |
| if ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-".indexOf(qualifier.charAt(i)) == -1) { //$NON-NLS-1$ |
| return false; |
| } |
| } |
| return true; |
| } |
| } |