blob: 41cbca8359b9db1e8826d81919499b14c3ebcfd0 [file] [log] [blame]
package org.eclipse.update.internal.core;
/*
* (c) Copyright IBM Corp. 2000, 2001.
* All Rights Reserved.
*/
import java.io.*;
import java.net.URL;
import java.util.*;
import java.util.Date;
import java.util.HashMap;
import org.eclipse.core.boot.*;
import org.eclipse.core.internal.runtime.InternalPlatform;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.Platform;
import org.eclipse.update.configuration.IConfiguredSite;
import org.eclipse.update.core.*;
import org.eclipse.update.core.SiteManager;
/**
* Update manager reconciler application.
* This application is invoked by the platform startup support
* whenever changes are detected in the installation that require
* the update state to be reconciled. Typically, this will be as
* a result of installation actions that were done directly
* in the file system bypassing the Update Manager.
* When triggered, this application performs the following:
* <ul>
* <li>call update manager reconciler
* <li>if plugin path changed as a result of this call, trigger
* restart ("exit code 23")
* <li>if plugin path has not changed as a result of this call,
* start this original application
* </ul>
*/
public class UpdateManagerReconciler implements IPlatformRunnable {
// NOTE: originalApplication is set to a dummy string to prevent "silent"
// failures in case of internal errors
private String originalApplication = "org.eclipse.update.UNKNOWN"; //$NON-NLS-1$
private boolean initialize = false;
private boolean firstUse = false;
private boolean optimistic = false;
private boolean dumpfile = false;
private boolean DEBUG = false;
private String dumpFilename = "";
private static final String RECONCILER_APP = "org.eclipse.update.core.reconciler";
private static final String APPLICATION = "-application"; //$NON-NLS-1$
private static final String INITIALIZE = "-initialize"; //$NON-NLS-1$
private static final String DUMPFILE = "-dumpfile"; //$NON-NLS-1$
private static final String FIRSTUSE = "-firstuse"; //$NON-NLS-1$
private static final String NEWUPDATES = "-newUpdates"; //$NON-NLS-1$
private static final String CHANGES_MARKER = ".newupdates"; //$NON-NLS-1$
/**
* @see IPlatformRunnable#run(Object)
*/
public Object run(Object args) throws Exception {
new Exception().printStackTrace();
// get debug setting and process command line arguments
DEBUG = UpdateCore.DEBUG_SHOW_RECONCILER;
processCommandLine((String[]) args);
// obtain current platform configuration and save stamps
IPlatformConfiguration cfg = BootLoader.getCurrentPlatformConfiguration();
// URL[] originalPluginPath = cfg.getPluginPath();
// perform reconciliation
long start = 0;
if (DEBUG) {
start = new Date().getTime();
debug("begin"); //$NON-NLS-1$
}
if (initialize || firstUse)
optimistic = true; // initialize and first-time startup ... optimistic
// (changes are auto-configured)
else
optimistic = false; // all other ... pessimistic (changes are
// reflected in state but not configured)
boolean changes = reconcile(cfg);
if (DEBUG) {
long delta = (new Date().getTime()) - start;
debug("end [" + delta + "ms]"); //$NON-NLS-1$ //$NON-NLS-2$
}
// if we need to write the features available, do it now
if (dumpfile) {
dumpFeatures();
}
// if we are completing post-install initialization (-initialize)
// just exit
if (initialize) {
Platform.endSplash();
return EXIT_OK;
}
// if the reconciler app was explicitly invoked, mark changes and
// just exit
if (RECONCILER_APP.equals(originalApplication)) {
Platform.endSplash();
if (changes)
markChanges(cfg);
return EXIT_OK; // just exit if original app was reconciler
}
// if the reconciler app was invoked, with -dump
// just exit
if (dumpfile) {
Platform.endSplash();
if (changes)
markChanges(cfg);
return EXIT_OK; // just exit
}
// see if plugins have changed
// if (pluginPathChanged(cfg, originalPluginPath)) {
// plugins lineup changed ... need to restart
Platform.endSplash();
if (changes)
markChanges(cfg);
if (DEBUG)
debug("restarting ..." + new Date()); //$NON-NLS-1$
return EXIT_RESTART;
// } else {
// // plugins lineup did not change ... no need to restart,
// // just continue starting up the original app. Original
// // app will take down splash
// IPlatformRunnable originalRunnable = getRunnable(originalApplication);
// if (originalRunnable == null)
// throw new IllegalArgumentException(Policy.bind("Reconciler.appNotFound",originalApplication)); //$NON-NLS-1$
// if (DEBUG)
// debug("invoking " + originalApplication + " ..."); //$NON-NLS-1$ //$NON-NLS-2$
// // indicate we have new updates (-newUpdates)
// if (changes)
// appArgs = markChanges(appArgs);
// return originalRunnable.run(appArgs);
// }
}
/*
* get the list of all installed features in all the sites
* dump the identifier and version in a file
*/
private void dumpFeatures() {
if (dumpFilename == null)
return;
List features = new ArrayList();
// find all features
IConfiguredSite[] configuredSites = new IConfiguredSite[0];
try {
configuredSites = SiteManager.getLocalSite().getCurrentConfiguration().getConfiguredSites();
} catch (Exception e) {
// eat the error
}
for (int i = 0; i < configuredSites.length; i++) {
IFeatureReference[] ref = configuredSites[i].getSite().getFeatureReferences();
try {
for (int j = 0; j < ref.length; j++) {
features.add(ref[j].getVersionedIdentifier());
}
} catch (Exception e) {
// eat the exception
}
}
// dump them
File dumpFile = new File(dumpFilename);
FileOutputStream out = null;
try {
out = new FileOutputStream(dumpFile);
for (Iterator iter = features.iterator(); iter.hasNext();) {
VersionedIdentifier element = (VersionedIdentifier) iter.next();
try {
String feature = element.getIdentifier() + "," + element.getVersion() + '\n';
out.write(feature.getBytes());
} catch (Exception e) {
// eat the exception
}
}
} catch (Exception e) {
// eat the exception
return;
} finally {
if (out != null) {
try {
out.close();
} catch (Exception e) {
// eat the exception
}
}
}
}
private boolean reconcile(IPlatformConfiguration cfg) {
boolean changes = true;
// do the reconciliation
try {
if (DEBUG)
debug("mode: " + (optimistic ? "optimistic" : "pessimistic")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
changes = InternalSiteManager.reconcile(optimistic);
if (DEBUG)
debug(changes ? "changes detected" : "no changes detected"); //$NON-NLS-1$ //$NON-NLS-2$
} catch (CoreException e) {
UpdateCore.warn(null, e);
} catch (Exception e) {
UpdateCore.warn(null, e);
}
cfg.refresh(); // recompute stamps and plugin path
return changes;
}
private boolean pluginPathChanged(IPlatformConfiguration cfg, URL[] originalPluginPath) {
URL[] currentPluginPath = cfg.getPluginPath();
HashMap originalMap = new HashMap();
HashMap currentMap = new HashMap();
// populate maps
for (int i = 0; i < originalPluginPath.length; i++) {
originalMap.put(originalPluginPath[i].toExternalForm(), null);
}
for (int i = 0; i < currentPluginPath.length; i++) {
currentMap.put(currentPluginPath[i].toExternalForm(), null);
}
// check for deletions
for (int i = 0; i < originalPluginPath.length; i++) {
String key = originalPluginPath[i].toExternalForm();
if (!currentMap.containsKey(key))
return true;
}
// check for additions
for (int i = 0; i < currentPluginPath.length; i++) {
String key = currentPluginPath[i].toExternalForm();
if (!originalMap.containsKey(key))
return true;
}
return false;
}
private void markChanges(IPlatformConfiguration cfg) {
// indicate we have changes in restart scenario ... converted to -newUpdates on restart
FileOutputStream fos = null;
try {
URL markerLocation = new URL(cfg.getConfigurationLocation(), CHANGES_MARKER);
fos = new FileOutputStream(new File(markerLocation.getFile()));
fos.write(0);
fos.close();
} catch (IOException e) {
if (fos != null)
try {
fos.close();
} catch (IOException e2) {
}
}
}
private String[] markChanges(String[] args) {
// indicate we have changes in continue-startup scenario ... adds -newUpdate
String[] newArgs = new String[args.length + 1];
newArgs[0] = NEWUPDATES;
System.arraycopy(args, 0, newArgs, 1, args.length);
return newArgs;
}
private String[] processCommandLine(String[] args) {
int[] configArgs = new int[100];
configArgs[0] = -1; // need to initialize the first element to something that could not be an index.
int configArgIndex = 0;
for (int i = 0; i < args.length; i++) {
boolean found = false;
// check for args without parameters (i.e., a flag arg)
// look for the initialization flag
if (args[i].equalsIgnoreCase(INITIALIZE)) {
initialize = true;
found = true;
}
// look for first use flag
if (args[i].equalsIgnoreCase(FIRSTUSE)) {
firstUse = true;
found = true;
}
if (found) {
configArgs[configArgIndex++] = i;
continue;
}
// check for args with parameters. If we are at the last argument or if the next one
// has a '-' as the first character, then we can't have an arg with a parm so continue.
if (i == args.length - 1 || args[i + 1].startsWith("-")) { //$NON-NLS-1$
continue;
}
String arg = args[++i];
// look for the application argument
if (args[i - 1].equalsIgnoreCase(APPLICATION)) {
found = true;
originalApplication = arg;
}
// look for the dumpfile argument
if (args[i - 1].equalsIgnoreCase(DUMPFILE)) {
found = true;
dumpfile = true;
dumpFilename = arg;
}
// done checking for args. Remember where an arg was found
if (found) {
configArgs[configArgIndex++] = i - 1;
configArgs[configArgIndex++] = i;
}
}
// remove all the arguments consumed by this argument parsing
if (configArgIndex == 0)
return args;
String[] passThruArgs = new String[args.length - configArgIndex];
configArgIndex = 0;
int j = 0;
for (int i = 0; i < args.length; i++) {
if (i == configArgs[configArgIndex])
configArgIndex++;
else
passThruArgs[j++] = args[i];
}
return passThruArgs;
}
private IPlatformRunnable getRunnable(String application) {
// NOTE: we need to get the runnable for the original application.
// We can either lookup the application extension point (duplicate
// the InternalPlatform logic here), or make the internal call
return InternalPlatform.loaderGetRunnable(application);
}
private void debug(String s) {
UpdateCore.debug(this.getClass().getName() + ": " + s); //$NON-NLS-1$
}
}