| /******************************************************************************* |
| * Copyright (c) 2000, 2015 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 |
| * Sergey Prigogin (Google) - use parameterized types (bug 442021) |
| *******************************************************************************/ |
| package org.eclipse.core.internal.boot; |
| |
| import java.io.*; |
| import java.net.URL; |
| import java.net.URLConnection; |
| import java.util.Enumeration; |
| import java.util.Properties; |
| import org.eclipse.core.internal.runtime.Activator; |
| import org.eclipse.core.internal.runtime.CommonMessages; |
| import org.eclipse.osgi.service.debug.DebugOptions; |
| import org.eclipse.osgi.util.NLS; |
| import org.osgi.framework.Version; |
| |
| /** |
| * Platform URL support |
| */ |
| public abstract class PlatformURLConnection extends URLConnection { |
| |
| // URL access |
| private boolean isInCache = false; |
| private boolean isJar = false; |
| |
| // protected URL url; // declared in super (platform: URL) |
| private URL resolvedURL = null; // resolved file URL (e.g. http: URL) |
| private URL cachedURL = null; // file URL in cache (file: URL) |
| |
| private URLConnection connection = null; // actual connection |
| |
| // local cache |
| private static Properties cacheIndex = new Properties(); |
| private static String cacheLocation; |
| private static String indexName; |
| private static String filePrefix; |
| |
| // constants |
| private static final int BUF_SIZE = 32768; |
| private static final Object NOT_FOUND = new Object(); // marker |
| private static final String CACHE_PROP = ".cache.properties"; //$NON-NLS-1$ |
| private static final String CACHE_LOCATION_PROP = "location"; //$NON-NLS-1$ |
| private static final String CACHE_INDEX_PROP = "index"; //$NON-NLS-1$ |
| private static final String CACHE_PREFIX_PROP = "prefix"; //$NON-NLS-1$ |
| private static final String CACHE_INDEX = ".index.properties"; //$NON-NLS-1$ |
| private static final String CACHE_DIR = ".eclipse-" + PlatformURLHandler.PROTOCOL + File.separator; //$NON-NLS-1$ |
| |
| // debug tracing |
| private static final String OPTION_DEBUG = "org.eclipse.core.runtime/url/debug"; //$NON-NLS-1$; |
| private static final String OPTION_DEBUG_CONNECT = OPTION_DEBUG + "/connect"; //$NON-NLS-1$; |
| private static final String OPTION_DEBUG_CACHE_LOOKUP = OPTION_DEBUG + "/cachelookup"; //$NON-NLS-1$; |
| private static final String OPTION_DEBUG_CACHE_COPY = OPTION_DEBUG + "/cachecopy"; //$NON-NLS-1$; |
| |
| public final static boolean DEBUG; |
| public final static boolean DEBUG_CONNECT; |
| public final static boolean DEBUG_CACHE_LOOKUP; |
| public final static boolean DEBUG_CACHE_COPY; |
| |
| static { |
| Activator activator = Activator.getDefault(); |
| if (activator == null) { |
| DEBUG = DEBUG_CONNECT = DEBUG_CACHE_LOOKUP = DEBUG_CACHE_COPY = false; |
| } else { |
| DebugOptions debugOptions = activator.getDebugOptions(); |
| if (debugOptions != null) { |
| DEBUG = debugOptions.getBooleanOption(OPTION_DEBUG, false); |
| DEBUG_CONNECT = debugOptions.getBooleanOption(OPTION_DEBUG_CONNECT, true); |
| DEBUG_CACHE_LOOKUP = debugOptions.getBooleanOption(OPTION_DEBUG_CACHE_LOOKUP, true); |
| DEBUG_CACHE_COPY = debugOptions.getBooleanOption(OPTION_DEBUG_CACHE_COPY, true); |
| } else |
| DEBUG = DEBUG_CONNECT = DEBUG_CACHE_LOOKUP = DEBUG_CACHE_COPY = false; |
| } |
| } |
| |
| protected PlatformURLConnection(URL url) { |
| super(url); |
| } |
| |
| protected boolean allowCaching() { |
| return false; |
| } |
| |
| @Override |
| public void connect() throws IOException { |
| connect(false); |
| } |
| |
| private synchronized void connect(boolean asLocal) throws IOException { |
| if (connected) |
| return; |
| |
| if (shouldCache(asLocal)) { |
| try { |
| URL inCache = getURLInCache(); |
| if (inCache != null) |
| connection = inCache.openConnection(); |
| } catch (IOException e) { |
| // failed to cache ... will use resolved URL instead |
| } |
| } |
| |
| // use resolved URL |
| if (connection == null) |
| connection = resolvedURL.openConnection(); |
| connected = true; |
| if (DEBUG && DEBUG_CONNECT) |
| debug("Connected as " + connection.getURL()); //$NON-NLS-1$ |
| } |
| |
| //TODO consider refactoring this method... it is too long |
| //TODO avoid cryptic identifiers such as ix, tgt, tmp, srcis, tgtos... |
| private void copyToCache() throws IOException { |
| |
| if (isInCache | cachedURL == null) |
| return; |
| String tmp; |
| int ix; |
| |
| // cache entry key |
| String key; |
| if (isJar) { |
| tmp = url.getFile(); |
| ix = tmp.lastIndexOf(PlatformURLHandler.JAR_SEPARATOR); |
| if (ix != -1) |
| tmp = tmp.substring(0, ix); |
| key = tmp; |
| } else |
| key = url.getFile(); |
| |
| // source url |
| URL src; |
| if (isJar) { |
| tmp = resolvedURL.getFile(); |
| ix = tmp.lastIndexOf(PlatformURLHandler.JAR_SEPARATOR); |
| if (ix != -1) |
| tmp = tmp.substring(0, ix); |
| src = new URL(tmp); |
| } else |
| src = resolvedURL; |
| InputStream srcis = null; |
| |
| // cache target |
| String tgt; |
| if (isJar) { |
| tmp = cachedURL.getFile(); |
| ix = tmp.indexOf(PlatformURLHandler.PROTOCOL_SEPARATOR); |
| if (ix != -1) |
| tmp = tmp.substring(ix + 1); |
| ix = tmp.lastIndexOf(PlatformURLHandler.JAR_SEPARATOR); |
| if (ix != -1) |
| tmp = tmp.substring(0, ix); |
| tgt = tmp; |
| } else |
| tgt = cachedURL.getFile(); |
| File tgtFile = null; |
| FileOutputStream tgtos = null; |
| |
| boolean error = false; |
| long total = 0; |
| |
| try { |
| if (DEBUG && DEBUG_CACHE_COPY) { |
| if (isJar) |
| debug("Caching jar as " + tgt); //$NON-NLS-1$ |
| else |
| debug("Caching as " + tgt); //$NON-NLS-1$ |
| } |
| |
| srcis = src.openStream(); |
| byte[] buf = new byte[BUF_SIZE]; |
| int count = srcis.read(buf); |
| |
| tgtFile = new File(tgt); |
| tgtos = new FileOutputStream(tgtFile); |
| |
| while (count != -1) { |
| total += count; |
| tgtos.write(buf, 0, count); |
| count = srcis.read(buf); |
| } |
| |
| srcis.close(); |
| srcis = null; |
| tgtos.flush(); |
| tgtos.getFD().sync(); |
| tgtos.close(); |
| tgtos = null; |
| |
| // add cache entry |
| cacheIndex.put(key, tgt); |
| isInCache = true; |
| } catch (IOException e) { |
| error = true; |
| cacheIndex.put(key, NOT_FOUND); |
| // mark cache entry for this execution |
| if (DEBUG && DEBUG_CACHE_COPY) |
| debug("Failed to cache due to " + e); //$NON-NLS-1$ |
| throw e; |
| } finally { |
| if (!error && DEBUG && DEBUG_CACHE_COPY) |
| debug(total + " bytes copied"); //$NON-NLS-1$ |
| if (srcis != null) |
| srcis.close(); |
| if (tgtos != null) |
| tgtos.close(); |
| } |
| } |
| |
| protected void debug(String s) { |
| System.out.println("URL " + getURL().toString() + "^" + Integer.toHexString(Thread.currentThread().hashCode()) + " " + s); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| } |
| |
| private static void debugStartup(String s) { |
| System.out.println("URL " + s); //$NON-NLS-1$ |
| } |
| |
| @Override |
| public synchronized InputStream getInputStream() throws IOException { |
| if (!connected) |
| connect(); |
| return connection.getInputStream(); |
| } |
| |
| public URL getResolvedURL() { |
| return resolvedURL; |
| } |
| |
| public URL getURLAsLocal() throws IOException { |
| connect(true); // connect and force caching if necessary |
| URL u = connection.getURL(); |
| String up = u.getProtocol(); |
| if (!up.equals(PlatformURLHandler.FILE) && !up.equals(PlatformURLHandler.JAR) && !up.startsWith(PlatformURLHandler.BUNDLE)) |
| throw new IOException(NLS.bind(CommonMessages.url_noaccess, up)); |
| return u; |
| } |
| |
| //TODO consider refactoring this method... it is too long |
| private URL getURLInCache() throws IOException { |
| |
| if (!allowCaching()) |
| return null; // target should not be cached |
| |
| if (isInCache) |
| return cachedURL; |
| |
| if (cacheLocation == null | cacheIndex == null) |
| return null; // not caching |
| |
| // check if we are dealing with a .jar/ .zip |
| String file = ""; //$NON-NLS-1$ |
| String jarEntry = null; |
| if (isJar) { |
| file = url.getFile(); |
| int ix = file.lastIndexOf(PlatformURLHandler.JAR_SEPARATOR); |
| if (ix != -1) { |
| jarEntry = file.substring(ix + PlatformURLHandler.JAR_SEPARATOR.length()); |
| file = file.substring(0, ix); |
| } |
| } else { |
| file = url.getFile(); |
| } |
| |
| // check for cached entry |
| String tmp = (String) cacheIndex.get(file); |
| |
| // check for "not found" marker |
| if (tmp != null && tmp == NOT_FOUND) |
| throw new IOException(); |
| |
| // validate cache entry |
| if (tmp != null && !(new File(tmp)).exists()) { |
| tmp = null; |
| cacheIndex.remove(url.getFile()); |
| } |
| |
| // found in cache |
| if (tmp != null) { |
| if (isJar) { |
| if (DEBUG && DEBUG_CACHE_LOOKUP) |
| debug("Jar located in cache as " + tmp); //$NON-NLS-1$ |
| tmp = PlatformURLHandler.FILE + PlatformURLHandler.PROTOCOL_SEPARATOR + tmp + PlatformURLHandler.JAR_SEPARATOR + jarEntry; |
| cachedURL = new URL(PlatformURLHandler.JAR, null, -1, tmp); |
| } else { |
| if (DEBUG && DEBUG_CACHE_LOOKUP) |
| debug("Located in cache as " + tmp); //$NON-NLS-1$ |
| cachedURL = new URL(PlatformURLHandler.FILE, null, -1, tmp); |
| } |
| isInCache = true; |
| } else { |
| // attempt to cache |
| int ix = file.lastIndexOf("/"); //$NON-NLS-1$ |
| tmp = file.substring(ix + 1); |
| tmp = cacheLocation + filePrefix + Long.toString((new java.util.Date()).getTime()) + "_" + tmp; //$NON-NLS-1$ |
| tmp = tmp.replace(File.separatorChar, '/'); |
| if (isJar) { |
| tmp = PlatformURLHandler.FILE + PlatformURLHandler.PROTOCOL_SEPARATOR + tmp + PlatformURLHandler.JAR_SEPARATOR + jarEntry; |
| cachedURL = new URL(PlatformURLHandler.JAR, null, -1, tmp); |
| } else |
| cachedURL = new URL(PlatformURLHandler.FILE, null, -1, tmp); |
| copyToCache(); |
| } |
| |
| return cachedURL; |
| } |
| |
| /* |
| * to be implemented by subclass |
| * @return URL resolved URL |
| */ |
| protected URL resolve() throws IOException { |
| // TODO throw UnsupportedOperationException instead - this is a bug in subclass, not an actual failure |
| throw new IOException(); |
| } |
| |
| protected static String getId(String spec) { |
| String id = (String) parse(spec)[0]; |
| return id == null ? spec : id; |
| } |
| |
| protected static String getVersion(String spec) { |
| Version version = (Version) parse(spec)[1]; |
| return version == null ? "" : version.toString(); //$NON-NLS-1$ |
| } |
| |
| private static Object[] parse(String spec) { |
| String bsn = null; |
| Version version = null; |
| int underScore = spec.indexOf('_'); |
| while (underScore >= 0) { |
| try { |
| version = Version.parseVersion(spec.substring(underScore + 1)); |
| } catch (IllegalArgumentException iae) { |
| // continue to next underscore |
| underScore = spec.indexOf('_', underScore + 1); |
| continue; |
| } |
| bsn = spec.substring(0, underScore); |
| break; |
| } |
| return new Object[] {bsn, version}; |
| } |
| |
| void setResolvedURL(URL url) throws IOException { |
| if (url == null) |
| throw new IOException(); |
| if (resolvedURL != null) |
| return; |
| int ix = url.getFile().lastIndexOf(PlatformURLHandler.JAR_SEPARATOR); |
| isJar = -1 != ix; |
| // Resolved URLs containing !/ separator are assumed to be jar URLs. |
| // If the resolved protocol is not jar, new jar URL is created. |
| if (isJar && !url.getProtocol().equals(PlatformURLHandler.JAR)) |
| url = new URL(PlatformURLHandler.JAR, "", -1, url.toExternalForm()); //$NON-NLS-1$ |
| resolvedURL = url; |
| } |
| |
| private boolean shouldCache(boolean asLocal) { |
| |
| // don't cache files that are known to be local |
| String rp = resolvedURL.getProtocol(); |
| String rf = resolvedURL.getFile(); |
| if (rp.equals(PlatformURLHandler.FILE)) |
| return false; |
| if (rp.equals(PlatformURLHandler.JAR) && (rf.startsWith(PlatformURLHandler.FILE))) |
| return false; |
| |
| // for other files force caching if local connection was requested |
| if (asLocal) |
| return true; |
| |
| // for now cache all files |
| // XXX: add cache policy support |
| return true; |
| } |
| |
| static void shutdown() { |
| if (indexName != null && cacheLocation != null) { |
| // weed out "not found" entries |
| Enumeration<Object> keys = cacheIndex.keys(); |
| String key; |
| Object value; |
| while (keys.hasMoreElements()) { |
| key = (String) keys.nextElement(); |
| value = cacheIndex.get(key); |
| if (value == NOT_FOUND) |
| cacheIndex.remove(key); |
| } |
| //if the cache index is empty we don't need to save it |
| if (cacheIndex.size() == 0) |
| return; |
| try { |
| // try to save cache index |
| FileOutputStream fos = null; |
| fos = new FileOutputStream(cacheLocation + indexName); |
| try { |
| cacheIndex.store(fos, null); |
| fos.flush(); |
| fos.getFD().sync(); |
| } finally { |
| fos.close(); |
| } |
| } catch (IOException e) { |
| // failed to store cache index ... ignore |
| } |
| } |
| } |
| |
| //TODO consider splitting this method into two or more steps - it is too long |
| static void startup(String location, String os, String ws, String nl) { |
| verifyLocation(location); // check for platform location, ignore errors |
| String cacheProps = location.trim(); |
| if (!cacheProps.endsWith(File.separator)) |
| cacheProps += File.separator; |
| cacheProps += CACHE_PROP; |
| File cachePropFile = new File(cacheProps); |
| Properties props = null; |
| FileInputStream fis; |
| |
| if (cachePropFile.exists()) { |
| // load existing properties |
| try { |
| props = new Properties(); |
| fis = new FileInputStream(cachePropFile); |
| try { |
| props.load(fis); |
| } finally { |
| fis.close(); |
| } |
| } catch (IOException e) { |
| props = null; |
| } |
| } |
| |
| if (props == null) { |
| // first time up, or failed to load previous settings |
| props = new Properties(); |
| |
| String tmp = System.getProperty("user.home"); //$NON-NLS-1$ |
| if (!tmp.endsWith(File.separator)) |
| tmp += File.separator; |
| tmp += CACHE_DIR; |
| props.put(CACHE_LOCATION_PROP, tmp); |
| |
| tmp = Long.toString((new java.util.Date()).getTime()); |
| props.put(CACHE_PREFIX_PROP, tmp); |
| |
| tmp += CACHE_INDEX; |
| props.put(CACHE_INDEX_PROP, tmp); |
| |
| // save for next time around |
| FileOutputStream fos = null; |
| try { |
| fos = new FileOutputStream(cachePropFile); |
| try { |
| props.store(fos, null); |
| fos.flush(); |
| fos.getFD().sync(); |
| } finally { |
| fos.close(); |
| } |
| } catch (IOException e) { |
| // failed to store cache location metadata ... ignore |
| } |
| } |
| |
| // remember settings for shutdown processing |
| filePrefix = (String) props.get(CACHE_PREFIX_PROP); |
| indexName = (String) props.get(CACHE_INDEX_PROP); |
| cacheLocation = (String) props.get(CACHE_LOCATION_PROP); |
| |
| if (DEBUG) { |
| debugStartup("Cache location: " + cacheLocation); //$NON-NLS-1$ |
| debugStartup("Cache index: " + indexName); //$NON-NLS-1$ |
| debugStartup("Cache file prefix: " + filePrefix); //$NON-NLS-1$ |
| } |
| |
| // create cache directory structure if needed |
| if (!verifyLocation(cacheLocation)) { |
| indexName = null; |
| cacheLocation = null; |
| if (DEBUG) |
| debugStartup("Failed to create cache directory structure. Caching suspended"); //$NON-NLS-1$ |
| return; |
| } |
| |
| // attempt to initialize cache index |
| if (cacheLocation != null && indexName != null) { |
| try { |
| fis = new FileInputStream(cacheLocation + indexName); |
| try { |
| cacheIndex.load(fis); |
| } finally { |
| fis.close(); |
| } |
| } catch (IOException e) { |
| if (DEBUG) |
| debugStartup("Failed to initialize cache"); //$NON-NLS-1$ |
| } |
| } |
| } |
| |
| private static boolean verifyLocation(String location) { |
| // verify cache directory exists. Create if needed |
| File cacheDir = new File(location); |
| if (cacheDir.exists()) |
| return true; |
| return cacheDir.mkdirs(); |
| } |
| } |