| /******************************************************************************* |
| * Copyright (c) 2005, 2019 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.equinox.launcher; |
| |
| import java.io.IOException; |
| import java.lang.reflect.Field; |
| import java.lang.reflect.Modifier; |
| import java.net.*; |
| import java.util.*; |
| import java.util.jar.JarFile; |
| import java.util.jar.Manifest; |
| import java.util.zip.ZipFile; |
| |
| /** |
| * The launcher to start eclipse using webstart. To use this launcher, the client |
| * must accept to give all security permissions. |
| * <p> |
| * <b>Note:</b> This class should not be referenced programmatically by |
| * other Java code. This class exists only for the purpose of launching Eclipse |
| * using Java webstart. To launch Eclipse programmatically, use |
| * org.eclipse.core.runtime.adaptor.EclipseStarter. The fields and methods |
| * on this class are not API. |
| * |
| * @noextend This class is not intended to be subclassed by clients. |
| * @noinstantiate This class is not intended to be instantiated by clients. |
| * @noreference This class is not intended to be referenced by clients. |
| * |
| * @deprecated Java WebStart is removed in Java 11. |
| * |
| * This API is planned to be deleted, see https://bugs.eclipse.org/bugs/show_bug.cgi?id=544262 |
| * |
| */ |
| //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. |
| @Deprecated |
| 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_CHECK_CONFIG = "osgi.checkConfiguration"; //$NON-NLS-1$ |
| |
| private Map<String, List<BundleInfo>> allBundles = null; // Map of all the bundles found on the classpath. Id -> ArrayList of BundleInfo |
| private List<BundleInfo> bundleList = null; //The list of bundles found on the osgi.bundle list |
| |
| protected class BundleInfo { |
| String bsn; |
| String version; |
| String startData; |
| String location; |
| } |
| |
| /** |
| * @noreference This method is not intended to be referenced by clients. |
| */ |
| 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); |
| if (!Boolean.getBoolean(PROP_NOSHUTDOWN)) |
| System.exit(result); |
| } |
| |
| private void setDefaultBundles() { |
| if (System.getProperty(PROP_OSGI_BUNDLES) != null) |
| return; |
| System.getProperties().put(PROP_OSGI_BUNDLES, DEFAULT_OSGI_BUNDLES); |
| } |
| |
| @Override |
| protected void basicRun(String[] args) throws Exception { |
| setDefaultBundles(); |
| initializeBundleListStructure(); |
| discoverBundles(); |
| //Set the fwk location since the regular lookup would not find it |
| String fwkURL = searchFor(framework, null); |
| if (fwkURL == null) { |
| //MESSAGE CAN"T FIND THE FWK |
| } |
| allBundles.remove(framework); |
| System.getProperties().put(PROP_FRAMEWORK, fwkURL); |
| super.basicRun(args); |
| } |
| |
| @Override |
| protected void beforeFwkInvocation() { |
| // set the check config option so we pick up modified bundle jars (bug 152825) |
| if (System.getProperty(PROP_CHECK_CONFIG) == null) |
| System.getProperties().put(PROP_CHECK_CONFIG, "true"); //$NON-NLS-1$ |
| buildOSGiBundleList(); |
| cleanup(); |
| } |
| |
| /* |
| * Null out all the fields containing data |
| */ |
| private void cleanup() { |
| allBundles = null; |
| bundleList = null; |
| } |
| |
| /* |
| * Find the target bundle among all the bundles that are on the classpath. |
| * The start parameter is not used in this context |
| */ |
| @Override |
| protected String searchFor(final String target, String start) { |
| List<BundleInfo> matches = allBundles.get(target); |
| if (matches == null) |
| return null; |
| int numberOfMatches = matches.size(); |
| if (numberOfMatches == 1) { |
| return matches.get(0).location; |
| } |
| if (numberOfMatches == 0) |
| return null; |
| |
| String[] versions = new String[numberOfMatches]; |
| int highest = 0; |
| for (int i = 0; i < versions.length; i++) { |
| versions[i] = matches.get(i).version; |
| } |
| highest = findMax(null, versions); |
| return matches.get(highest).location; |
| } |
| |
| private BundleInfo findBundle(final String target, String version, boolean removeMatch) { |
| List<BundleInfo> matches = allBundles.get(target); |
| int numberOfMatches = matches != null ? matches.size() : 0; |
| if (numberOfMatches == 1) { |
| //TODO Need to check the version |
| return removeMatch ? matches.remove(0) : matches.get(0); |
| } |
| if (numberOfMatches == 0) |
| return null; |
| |
| if (version != null) { |
| for (Iterator<BundleInfo> iterator = matches.iterator(); iterator.hasNext();) { |
| BundleInfo bi = iterator.next(); |
| if (bi.version.equals(version)) { |
| if (removeMatch) |
| iterator.remove(); |
| return bi; |
| } |
| } |
| //TODO Need to log the fact that we could not find the version mentioned |
| return null; |
| } |
| String[] versions = new String[numberOfMatches]; |
| int highest = 0; |
| for (int i = 0; i < versions.length; i++) { |
| versions[i] = matches.get(i).version; |
| } |
| highest = findMax(null, versions); |
| return removeMatch ? matches.remove(highest) : matches.get(highest); |
| } |
| |
| /* |
| * Get all the bundles available on the webstart classpath |
| */ |
| private void discoverBundles() { |
| allBundles = new HashMap<>(); |
| try { |
| Enumeration<URL> resources = WebStartMain.class.getClassLoader().getResources(JarFile.MANIFEST_NAME); |
| while (resources.hasMoreElements()) { |
| BundleInfo found = getBundleInfo(resources.nextElement()); |
| if (found == null) |
| continue; |
| List<BundleInfo> matching = allBundles.get(found.bsn); |
| if (matching == null) { |
| matching = new ArrayList<>(1); |
| allBundles.put(found.bsn, matching); |
| } |
| matching.add(found); |
| } |
| } catch (IOException e) { |
| e.printStackTrace(); |
| } |
| } |
| |
| private String extractInnerURL(URL url) { |
| try { |
| URLConnection connection = null; |
| try { |
| connection = url.openConnection(); |
| if (connection instanceof JarURLConnection) { |
| JarFile jarFile = ((JarURLConnection) connection).getJarFile(); |
| String name = jarFile.getName(); |
| // Some VMs may not return a jar name as a security precaution |
| if (name == null || name.length() == 0) |
| name = getJarNameByReflection(jarFile); |
| |
| if (name != null && name.length() > 0) |
| return "file:" + name; //$NON-NLS-1$ |
| } |
| } finally { |
| if (connection != null) |
| connection.getInputStream().close(); |
| } |
| } catch (IOException e) { |
| //Ignore and return the external form |
| } |
| return url.toExternalForm(); |
| } |
| |
| /* |
| * Get a value of the ZipFile.name field using reflection. |
| * For this to succeed, we need the "suppressAccessChecks" permission. |
| */ |
| private String getJarNameByReflection(JarFile jarFile) { |
| if (jarFile == null) |
| return null; |
| |
| Field nameField = null; |
| try { |
| nameField = ZipFile.class.getDeclaredField("name"); //$NON-NLS-1$ |
| } catch (NoSuchFieldException e1) { |
| try { |
| nameField = ZipFile.class.getDeclaredField("fileName"); //$NON-NLS-1$ |
| } catch (NoSuchFieldException e) { |
| //ignore |
| } |
| } |
| |
| if (nameField == null || Modifier.isStatic(nameField.getModifiers()) || nameField.getType() != String.class) |
| return null; |
| |
| try { |
| nameField.setAccessible(true); |
| return (String) nameField.get(jarFile); |
| } catch (SecurityException | IllegalArgumentException | IllegalAccessException e) { |
| // Don't have permissions, ignore |
| // or |
| // Shouldn't happen |
| } |
| return null; |
| } |
| |
| /* |
| * Construct bundle info objects from items found on 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 ArrayList<>(0); |
| return; |
| } |
| |
| bundleList = new ArrayList<>(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; |
| BundleInfo toAdd = new BundleInfo(); |
| toAdd.bsn = bundleId; |
| if ((startLevelSeparator = token.lastIndexOf(STARTLEVEL_SEPARATOR)) != -1) { |
| toAdd.bsn = token.substring(0, startLevelSeparator); |
| toAdd.startData = token.substring(startLevelSeparator); |
| //Note that here we don't try to parse the start attribute since this info is then used to recompose the value for osgi.bundles |
| } |
| bundleList.add(toAdd); |
| } |
| } |
| |
| private BundleInfo getBundleInfo(URL manifestURL) { |
| final String BUNDLE_SYMBOLICNAME = "Bundle-SymbolicName"; //$NON-NLS-1$ |
| final String BUNDLE_VERSION = "Bundle-Version"; //$NON-NLS-1$ |
| final String DEFAULT_VERSION = "0.0.0"; //$NON-NLS-1$ |
| |
| Manifest mf; |
| try { |
| mf = new Manifest(manifestURL.openStream()); |
| String symbolicNameString = mf.getMainAttributes().getValue(BUNDLE_SYMBOLICNAME); |
| if (symbolicNameString == null) |
| return null; |
| |
| BundleInfo result = new BundleInfo(); |
| String version = mf.getMainAttributes().getValue(BUNDLE_VERSION); |
| result.version = (version != null) ? version : DEFAULT_VERSION; |
| result.location = extractInnerURL(manifestURL); |
| int pos = symbolicNameString.lastIndexOf(';'); |
| if (pos != -1) { |
| result.bsn = symbolicNameString.substring(0, pos); |
| return result; |
| } |
| result.bsn = symbolicNameString; |
| return result; |
| } catch (IOException e) { |
| if (debug) |
| e.printStackTrace(); |
| } |
| return null; |
| } |
| |
| //Build the osgi bundle list. The allbundles data structure is changed during the process. |
| private void buildOSGiBundleList() { |
| StringBuilder finalBundleList = new StringBuilder(allBundles.size() * 30); |
| //First go through all the bundles of the bundle |
| for (BundleInfo searched : bundleList) { |
| BundleInfo found = findBundle(searched.bsn, searched.version, true); |
| if (found != null) |
| finalBundleList.append(REFERENCE_SCHEME).append(found.location).append(searched.startData).append(','); |
| } |
| |
| if (!Boolean.FALSE.toString().equalsIgnoreCase(System.getProperties().getProperty(PROP_WEBSTART_AUTOMATIC_INSTALLATION))) { |
| for (List<BundleInfo> toAdd : allBundles.values()) { |
| for (BundleInfo bi : toAdd) { |
| finalBundleList.append(REFERENCE_SCHEME).append(bi.location).append(','); |
| } |
| } |
| } |
| System.getProperties().put(PROP_OSGI_BUNDLES, finalBundleList.toString()); |
| } |
| } |