iter)
{
while (iter.hasNext()) {
iter.next().clearMarker();
}
}
/**
* Downloads a new copy of CONFIG_FILE.
*/
protected void downloadConfigFile ()
throws IOException
{
downloadControlFile(CONFIG_FILE, 0);
}
/**
* @return true if gettingdown.lock was unlocked, already locked by this application or if
* we're not locking at all.
*/
public synchronized boolean lockForUpdates ()
{
if (_lock != null && _lock.isValid()) {
return true;
}
try {
_lockChannel = new RandomAccessFile(getLocalPath("gettingdown.lock"), "rw").getChannel();
} catch (FileNotFoundException e) {
log.warning("Unable to create lock file", "message", e.getMessage(), e);
return false;
}
try {
_lock = _lockChannel.tryLock();
} catch (IOException e) {
log.warning("Unable to create lock", "message", e.getMessage(), e);
return false;
} catch (OverlappingFileLockException e) {
log.warning("The lock is held elsewhere in this JVM", e);
return false;
}
log.info("Able to lock for updates: " + (_lock != null));
return _lock != null;
}
/**
* Release gettingdown.lock
*/
public synchronized void releaseLock ()
{
if (_lock != null) {
log.info("Releasing lock");
try {
_lock.release();
} catch (IOException e) {
log.warning("Unable to release lock", "message", e.getMessage(), e);
}
try {
_lockChannel.close();
} catch (IOException e) {
log.warning("Unable to close lock channel", "message", e.getMessage(), e);
}
_lockChannel = null;
_lock = null;
}
}
/**
* Downloads the digest files and validates their signature.
* @throws IOException
*/
protected void downloadDigestFiles ()
throws IOException
{
for (int version = 1; version <= Digest.VERSION; version++) {
downloadControlFile(Digest.digestFile(version), version);
}
}
/**
* Downloads a new copy of the specified control file, optionally validating its signature.
* If the download is successful, moves it over the old file on the filesystem.
*
* TODO: Switch to PKCS #7 or CMS.
*
* @param sigVersion if {@code 0} no validation will be performed, if {@code > 0} then this
* should indicate the version of the digest file being validated which indicates which
* algorithm to use to verify the signature. See {@link Digest#VERSION}.
*/
protected void downloadControlFile (String path, int sigVersion)
throws IOException
{
File target = downloadFile(path);
if (sigVersion > 0) {
if (_envc.certs.isEmpty()) {
log.info("No signing certs, not verifying digest.txt", "path", path);
} else {
File signatureFile = downloadFile(path + SIGNATURE_SUFFIX);
byte[] signature = null;
try (FileInputStream signatureStream = new FileInputStream(signatureFile)) {
signature = StreamUtil.toByteArray(signatureStream);
} finally {
FileUtil.deleteHarder(signatureFile); // delete the file regardless
}
byte[] buffer = new byte[8192];
int length, validated = 0;
for (Certificate cert : _envc.certs) {
try (FileInputStream dataInput = new FileInputStream(target)) {
Signature sig = Signature.getInstance(Digest.sigAlgorithm(sigVersion));
sig.initVerify(cert);
while ((length = dataInput.read(buffer)) != -1) {
sig.update(buffer, 0, length);
}
if (!sig.verify(Base64.decode(signature, Base64.DEFAULT))) {
log.info("Signature does not match", "cert", cert.getPublicKey());
continue;
} else {
log.info("Signature matches", "cert", cert.getPublicKey());
validated++;
}
} catch (IOException ioe) {
log.warning("Failure validating signature of " + target + ": " + ioe);
} catch (GeneralSecurityException gse) {
// no problem!
}
}
// if we couldn't find a key that validates our digest, we are the hosed!
if (validated == 0) {
// delete the temporary digest file as we know it is invalid
FileUtil.deleteHarder(target);
throw new IOException("m.corrupt_digest_signature_error");
}
}
}
// now move the temporary file over the original
File original = getLocalPath(path);
if (!FileUtil.renameTo(target, original)) {
throw new IOException("Failed to rename(" + target + ", " + original + ")");
}
}
/**
* Download a path to a temporary file, returning a {@link File} instance with the path
* contents.
*/
protected File downloadFile (String path)
throws IOException
{
File target = getLocalPath(path + "_new");
URL targetURL = null;
try {
targetURL = getRemoteURL(path);
} catch (Exception e) {
log.warning("Requested to download invalid control file",
"appbase", _vappbase, "path", path, "error", e);
throw (IOException) new IOException("Invalid path '" + path + "'.").initCause(e);
}
log.info("Attempting to refetch '" + path + "' from '" + targetURL + "'.");
// stream the URL into our temporary file
URLConnection uconn = ConnectionUtil.open(proxy, targetURL, 0, 0);
// we have to tell Java not to use caches here, otherwise it will cache any request for
// same URL for the lifetime of this JVM (based on the URL string, not the URL object);
// if the getdown.txt file, for example, changes in the meanwhile, we would never hear
// about it; turning off caches is not a performance concern, because when Getdown asks
// to download a file, it expects it to come over the wire, not from a cache
uconn.setUseCaches(false);
uconn.setRequestProperty("Accept-Encoding", "gzip");
try (InputStream fin = uconn.getInputStream()) {
String encoding = uconn.getContentEncoding();
boolean gzip = "gzip".equalsIgnoreCase(encoding);
try (InputStream fin2 = (gzip ? new GZIPInputStream(fin) : fin)) {
try (FileOutputStream fout = new FileOutputStream(target)) {
StreamUtil.copy(fin2, fout);
}
}
}
return target;
}
/** Helper function for creating {@link Resource} instances. */
protected Resource createResource (String path, EnumSet attrs)
throws MalformedURLException
{
return new Resource(path, getRemoteURL(path), getLocalPath(path), attrs);
}
/** Helper function to add all values in {@code values} (if non-null) to {@code target}. */
protected static void addAll (String[] values, List target) {
if (values != null) {
for (String value : values) {
target.add(value);
}
}
}
/**
* Make an immutable List from the specified int array.
*/
public static List intsToList (int[] values)
{
List list = new ArrayList<>(values.length);
for (int val : values) {
list.add(val);
}
return Collections.unmodifiableList(list);
}
/**
* Make an immutable List from the specified String array.
*/
public static List stringsToList (String[] values)
{
return values == null ? null : Collections.unmodifiableList(Arrays.asList(values));
}
/** Used to parse resources with the specified name. */
protected void parseResources (Config config, String name, EnumSet attrs,
List list)
{
String[] rsrcs = config.getMultiValue(name);
if (rsrcs == null) {
return;
}
for (String rsrc : rsrcs) {
try {
list.add(createResource(rsrc, attrs));
} catch (Exception e) {
log.warning("Invalid resource '" + rsrc + "'. " + e);
}
}
}
/** Possibly generates and returns a google analytics tracking cookie. */
protected String getGATrackingCode ()
{
if (_trackingGAHash == null) {
return "";
}
long time = System.currentTimeMillis() / 1000;
if (_trackingStart == 0) {
_trackingStart = time;
}
if (_trackingId == 0) {
int low = 100000000, high = 1000000000;
_trackingId = low + _rando.nextInt(high-low);
}
StringBuilder cookie = new StringBuilder("&utmcc=__utma%3D").append(_trackingGAHash);
cookie.append(".").append(_trackingId);
cookie.append(".").append(_trackingStart).append(".").append(_trackingStart);
cookie.append(".").append(time).append(".1%3B%2B");
cookie.append("__utmz%3D").append(_trackingGAHash).append(".");
cookie.append(_trackingStart).append(".1.1.");
cookie.append("utmcsr%3D(direct)%7Cutmccn%3D(direct)%7Cutmcmd%3D(none)%3B");
int low = 1000000000, high = 2000000000;
cookie.append("&utmn=").append(_rando.nextInt(high-low));
return cookie.toString();
}
/**
* Encodes a path for use in a URL.
*/
protected static String encodePath (String path)
{
try {
// we want to keep slashes because we're encoding an entire path; also we need to turn
// + into %20 because web servers don't like + in paths or file names, blah
return URLEncoder.encode(path, "UTF-8").replace("%2F", "/").replace("+", "%20");
} catch (UnsupportedEncodingException ue) {
log.warning("Failed to URL encode " + path + ": " + ue);
return path;
}
}
protected File getLocalPath (File appdir, String path)
{
return new File(appdir, path);
}
public static void setStartupFilesFromParameters(List parameters) {
// multiple files *might* be passed in as space separated quoted filenames
String q = "\"";
for (String p: parameters) {
if (!StringUtil.isBlank(p)) {
String filename;
// split quoted params or treat as single string array
if (p.startsWith(q) && p.endsWith(q)) {
filename = p.substring(q.length(),p.length()-q.length());
} else {
// single unquoted filename
filename = p;
}
/* Now letting Jalview convert these URIs.
// convert jalviewX://... URLs
if (HttpUtils.isJalviewSchemeUri(filename)) {
filename = HttpUtils.equivalentJalviewUrl(filename);
}
*/
// check for locator file. Only allow one locator file to be double clicked (if multiple files opened, ignore locator files)
if (filename.toLowerCase(Locale.ROOT).endsWith("."+Application.LOCATOR_FILE_EXTENSION)) {
// convert jalviewX://... JVL files
setLocatorFile(HttpUtils.equivalentJalviewUrl(filename));
} else {
// skip any other locator files in a multiple file list
addStartupFile(filename);
}
}
}
}
public static String getStartupFilesParameterString(List parameters, boolean changeJalviewSchemeUris) {
StringBuilder sb = new StringBuilder();
boolean first = true;
for(String f : parameters) {
if (first) {
first = false;
} else {
sb.append(' ');
}
String p = changeJalviewSchemeUris ? HttpUtils.equivalentJalviewUrl(f) : f;
if (p.contains(" ")) {
sb.append('"');
sb.append(p);
sb.append('"');
} else {
sb.append(p);
}
}
return sb.toString();
}
public static void setLocatorFile(String filename) {
_locatorFile = filename;
}
public static void addStartupFile(String filename) {
_startupFiles.add(filename);
}
public static void setJalviewUri(String uri) {
_jalviewUri = uri;
}
private Config createLocatorConfig(Config.ParseOpts opts) {
if (_locatorFile == null) {
return null;
}
Config locatorConfig = null;
try {
Config tmpConfig = null;
Map tmpData = new HashMap<>();
if (HttpUtils.startsWithHttpOrHttps(_locatorFile)) {
URL locatorUrl = new URL(_locatorFile);
try (InputStream in = ConnectionUtil.open(proxy, locatorUrl, 0, 0).getInputStream();
InputStreamReader reader = new InputStreamReader(in, UTF_8);
BufferedReader bin = new BufferedReader(reader)) {
tmpConfig = Config.parseConfig(bin, opts);
}
} else if (new File(_locatorFile).exists()) {
tmpConfig = Config.parseConfig(new File(_locatorFile), opts);
}
if (tmpConfig != null) {
// appbase is sanitised in HostWhitelist
Map tmpConfigData = tmpConfig.getData();
if (tmpConfig != null) {
for (Map.Entry entry : tmpConfigData.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
String mkey = key.indexOf('.') > -1 ? key.substring(key.indexOf('.') + 1) : key;
if (Config.allowedReplaceKeys.contains(mkey) || Config.allowedMergeKeys.contains(mkey)) {
tmpData.put(key, value);
}
}
} else {
log.warning("Error occurred reading config file", "file", _locatorFile);
}
} else {
log.warning("Given locator file does not exist", "file", _locatorFile);
}
locatorConfig = new Config(tmpData);
} catch (Exception e) {
log.warning("Failure reading locator file", "file", _locatorFile, e);
}
return locatorConfig;
}
public String getAppbase() {
return _appbase;
}
protected final EnvConfig _envc;
protected File _config;
protected File _backupConfig;
protected Digest _digest;
protected long _version = -1;
protected long _targetVersion = -1;
protected String _appbase;
protected URL _vappbase;
protected URL _latest;
protected String _class;
protected String _dockName;
protected String _dockIconPath;
protected boolean _strictComments;
protected boolean _windebug;
protected boolean _allowOffline;
protected int _maxConcDownloads;
protected String _trackingURL;
protected Set _trackingPcts;
protected String _trackingCookieName;
protected String _trackingCookieProperty;
protected String _trackingURLSuffix;
protected String _trackingGAHash;
protected long _trackingStart;
protected int _trackingId;
protected String _javaVersionProp = "java.version";
protected String _javaVersionRegex = "(\\d+)(?:\\.(\\d+)(?:\\.(\\d+)(_\\d+)?)?)?";
protected long _javaMinVersion, _javaMaxVersion;
protected boolean _javaExactVersionRequired;
protected String _javaLocation;
protected List _codes = new ArrayList<>();
protected List _resources = new ArrayList<>();
protected boolean _useCodeCache;
protected int _codeCacheRetentionDays;
protected Map _auxgroups = new HashMap<>();
protected Map _auxactive = new HashMap<>();
protected List _jvmargs = new ArrayList<>();
protected List _appargs = new ArrayList<>();
protected String[] _optimumJvmArgs;
protected List _txtJvmArgs = new ArrayList<>();
/** If a warning has been issued about not being able to set modtimes. */
protected boolean _warnedAboutSetLastModified;
/** Locks gettingdown.lock in the app dir. Held the entire time updating is going on.*/
protected FileLock _lock;
/** Channel to the file underlying _lock. Kept around solely so the lock doesn't close. */
protected FileChannel _lockChannel;
protected Random _rando = new Random();
protected static final String[] EMPTY_STRING_ARRAY = new String[0];
protected static final String ENV_VAR_PREFIX = "%ENV.";
protected static final Pattern ENV_VAR_PATTERN = Pattern.compile("%ENV\\.(.*?)%");
protected static String _locatorFile;
protected static List _startupFiles = new ArrayList<>();
protected static String _jalviewUri;
public static final String LOCATOR_FILE_EXTENSION = "jvl";
private boolean _initialised = false;
private Config _initialisedConfig = null;
public static String i4jVersion = null;
private String jvmmempc = null;
private String jvmmemmax = null;
}