/*******************************************************************************
* Copyright (c) 2007, 2009 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.emrys.webosgi.launcher.configurator;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.emrys.webosgi.launcher.configurator.utils.BundleInfo;
import org.emrys.webosgi.launcher.configurator.utils.EquinoxUtils;
import org.emrys.webosgi.launcher.configurator.utils.SimpleConfiguratorConstants;
import org.emrys.webosgi.launcher.configurator.utils.SimpleConfiguratorUtils;
import org.emrys.webosgi.launcher.configurator.utils.StateResolverUtils;
import org.emrys.webosgi.launcher.configurator.utils.Utils;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.Constants;
import org.osgi.framework.FrameworkEvent;
import org.osgi.framework.FrameworkListener;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.Version;
import org.osgi.service.packageadmin.PackageAdmin;
import org.osgi.service.startlevel.StartLevel;
class ConfigApplier {
private static final String LAST_BUNDLES_INFO = "last.bundles.info"; //$NON-NLS-1$
private static final String PROP_DEVMODE = "osgi.dev"; //$NON-NLS-1$
private final BundleContext manipulatingContext;
private final PackageAdmin packageAdminService;
private final StartLevel startLevelService;
private final boolean runningOnEquinox;
private final boolean inDevMode;
private final Bundle callingBundle;
private final URI baseLocation;
ConfigApplier(BundleContext context, Bundle callingBundle) {
manipulatingContext = context;
this.callingBundle = callingBundle;
runningOnEquinox = "Eclipse".equals(context.getProperty(Constants.FRAMEWORK_VENDOR)); //$NON-NLS-1$
inDevMode = manipulatingContext.getProperty(PROP_DEVMODE) != null;
baseLocation = runningOnEquinox ? EquinoxUtils
.getInstallLocationURI(context) : null;
ServiceReference packageAdminRef = manipulatingContext
.getServiceReference(PackageAdmin.class.getName());
if (packageAdminRef == null)
throw new IllegalStateException(
"No PackageAdmin service is available."); //$NON-NLS-1$
packageAdminService = (PackageAdmin) manipulatingContext
.getService(packageAdminRef);
ServiceReference startLevelRef = manipulatingContext
.getServiceReference(StartLevel.class.getName());
if (startLevelRef == null)
throw new IllegalStateException(
"No StartLevelService service is available."); //$NON-NLS-1$
startLevelService = (StartLevel) manipulatingContext
.getService(startLevelRef);
}
void install(URL url, boolean exclusiveMode) throws IOException {
List bundleInfoList = SimpleConfiguratorUtils.readConfiguration(url,
baseLocation);
if (bundleInfoList.size() == 0)
return;
BundleInfo[] expectedState = Utils
.getBundleInfosFromList(bundleInfoList);
// check for an update to the system bundle
String systemBundleSymbolicName = manipulatingContext.getBundle(0)
.getSymbolicName();
// To support equinox3.4, donn't use Bundle.getVersion() method.
Version systemBundleVersion = Version.parseVersion(manipulatingContext
.getBundle(0).getHeaders().get(Constants.BUNDLE_VERSION)
.toString());
if (systemBundleSymbolicName != null) {
for (int i = 0; i < expectedState.length; i++) {
String symbolicName = expectedState[i].getSymbolicName();
if (!systemBundleSymbolicName.equals(symbolicName))
continue;
Version version = Version.parseVersion(expectedState[i]
.getVersion());
if (!systemBundleVersion.equals(version)) {
if (Activator.DEBUG) {
System.out
.print("\nBundles Config: Skip all bundles for system bundle found: "
+ symbolicName + " Version:" + version);
throw new IllegalStateException(
"The System Bundle was updated. The framework must be restarted to finalize the configuration change");
}
}
}
}
HashSet toUninstall = null;
if (!exclusiveMode) {
BundleInfo[] lastInstalledBundles = getLastState();
if (lastInstalledBundles != null) {
toUninstall = new HashSet(Arrays.asList(lastInstalledBundles));
toUninstall.removeAll(Arrays.asList(expectedState));
}
saveStateAsLast(url);
}
Collection prevouslyResolved = getResolvedBundles();
Collection toRefresh = new ArrayList();
Collection toStart = new ArrayList();
if (exclusiveMode) {
toRefresh.addAll(installBundles(expectedState, toStart));
toRefresh.addAll(uninstallBundles(expectedState,
packageAdminService));
} else {
toRefresh.addAll(installBundles(expectedState, toStart));
if (toUninstall != null)
toRefresh.addAll(uninstallBundles(toUninstall));
}
refreshPackages((Bundle[]) toRefresh.toArray(new Bundle[toRefresh
.size()]), manipulatingContext);
if (toRefresh.size() > 0)
try {
manipulatingContext.getBundle().loadClass(
"org.eclipse.osgi.service.resolver.PlatformAdmin"); //$NON-NLS-1$
// now see if there are any currently resolved bundles with
// option imports which
// could be resolved or
// if there are fragments with additional constraints which
// conflict with an already
// resolved host
Bundle[] additionalRefresh = StateResolverUtils
.getAdditionalRefresh(prevouslyResolved,
manipulatingContext);
if (additionalRefresh.length > 0)
refreshPackages(additionalRefresh, manipulatingContext);
} catch (ClassNotFoundException cnfe) {
// do nothing; no resolver package available
}
startBundles((Bundle[]) toStart.toArray(new Bundle[toStart.size()]));
if (Activator.DEBUG)
System.out.println();
}
private Collection getResolvedBundles() {
Collection resolved = new HashSet();
Bundle[] allBundles = manipulatingContext.getBundles();
for (int i = 0; i < allBundles.length; i++)
if ((allBundles[i].getState() & (Bundle.INSTALLED | Bundle.UNINSTALLED)) == 0)
resolved.add(allBundles[i]);
return resolved;
}
private Collection uninstallBundles(HashSet toUninstall) {
Collection removedBundles = new ArrayList(toUninstall.size());
for (Iterator iterator = toUninstall.iterator(); iterator.hasNext();) {
BundleInfo current = (BundleInfo) iterator.next();
Bundle[] matchingBundles = packageAdminService.getBundles(current
.getSymbolicName(), getVersionRange(current.getVersion()));
for (int j = 0; matchingBundles != null
&& j < matchingBundles.length; j++) {
try {
removedBundles.add(matchingBundles[j]);
matchingBundles[j].uninstall();
} catch (BundleException e) {
// TODO log in debug mode...
}
}
}
return removedBundles;
}
private void saveStateAsLast(URL url) {
InputStream sourceStream = null;
OutputStream destinationStream = null;
File lastBundlesTxt = getLastBundleInfo();
try {
try {
destinationStream = new FileOutputStream(lastBundlesTxt);
sourceStream = url.openStream();
SimpleConfiguratorUtils.transferStreams(sourceStream,
destinationStream);
} finally {
if (destinationStream != null)
destinationStream.close();
if (sourceStream != null)
sourceStream.close();
}
} catch (IOException e) {
// nothing
}
}
private File getLastBundleInfo() {
return manipulatingContext.getDataFile(LAST_BUNDLES_INFO);
}
private BundleInfo[] getLastState() {
File lastBundlesInfo = getLastBundleInfo();
if (!lastBundlesInfo.isFile())
return null;
try {
return (BundleInfo[]) SimpleConfiguratorUtils.readConfiguration(
lastBundlesInfo.toURL(), baseLocation).toArray(
new BundleInfo[1]);
} catch (IOException e) {
return null;
}
}
private ArrayList installBundles(BundleInfo[] finalList, Collection toStart) {
ArrayList toRefresh = new ArrayList();
String useReferenceProperty = manipulatingContext
.getProperty(SimpleConfiguratorConstants.PROP_KEY_USE_REFERENCE);
boolean useReference = useReferenceProperty == null ? runningOnEquinox
: Boolean.valueOf(useReferenceProperty).booleanValue();
if (Activator.DEBUG)
System.out.print("\nBundles installation Started.");
for (int i = 0; i < finalList.length; i++) {
if (finalList[i] == null)
continue;
// TODO here we do not deal with bundles that don't have a symbolic
// id
// TODO Need to handle the case where getBundles return multiple
// value
String symbolicName = finalList[i].getSymbolicName();
String version = finalList[i].getVersion();
if (Activator.DEBUG)
System.out.print("\n" + (i + 1) + ": "
+ finalList[i].getLocation());
Bundle[] matches = null;
if (symbolicName != null && version != null)
matches = packageAdminService.getBundles(symbolicName,
getVersionRange(version));
String bundleLocation = SimpleConfiguratorUtils.getBundleLocation(
finalList[i], useReference);
Bundle current = matches == null ? null
: (matches.length == 0 ? null : matches[0]);
if (current == null) {
try {
current = manipulatingContext.installBundle(bundleLocation);
toRefresh.add(current);
} catch (BundleException e) {
if (Activator.DEBUG) {
System.out.print(" Failed."); //$NON-NLS-1$
System.err
.println("Can't install " + symbolicName + '/' + version + " from location " + finalList[i].getLocation()); //$NON-NLS-1$ //$NON-NLS-2$
e.printStackTrace();
}
continue;
}
} else if (inDevMode && current.getBundleId() != 0
&& current != manipulatingContext.getBundle()
&& !bundleLocation.equals(current.getLocation())
&& !current.getLocation().startsWith("initial@")) {
if (Activator.DEBUG) {
System.out.println(" Allready Installed, Update.");
}
// We do not do this for the system bundle (id==0), the
// manipulating bundle or any
// bundle installed from the osgi.bundles list (locations
// starting with "@initial"
// The bundle exists; but the location is different. Uninstall
// the current and
// install the new one (bug 229700)
try {
current.uninstall();
toRefresh.add(current);
} catch (BundleException e) {
if (Activator.DEBUG) {
System.out.println(" Failed");
System.err
.println("Can't uninstall " + symbolicName + '/' + version + " from location " + current.getLocation()); //$NON-NLS-1$ //$NON-NLS-2$
e.printStackTrace();
}
continue;
}
try {
current = manipulatingContext.installBundle(bundleLocation);
toRefresh.add(current);
} catch (BundleException e) {
if (Activator.DEBUG) {
System.out.println(" Failed"); //$NON-NLS-1$
System.err
.println("Can't install " + symbolicName + '/' + version + " from location " + finalList[i].getLocation()); //$NON-NLS-1$ //$NON-NLS-2$
e.printStackTrace();
}
continue;
}
}
// Mark Started
if (finalList[i].isMarkedAsStarted()) {
toStart.add(current);
}
// Set Start Level
int startLevel = finalList[i].getStartLevel();
if (startLevel < 1)
continue;
if (current.getBundleId() == 0)
continue;
if (packageAdminService.getBundleType(current) == PackageAdmin.BUNDLE_TYPE_FRAGMENT)
continue;
if (SimpleConfiguratorConstants.TARGET_CONFIGURATOR_NAME
.equals(current.getSymbolicName()))
continue;
try {
startLevelService.setBundleStartLevel(current, startLevel);
if (Activator.DEBUG)
System.out.print(" OK."); //$NON-NLS-1$
} catch (IllegalArgumentException ex) {
Utils
.log(
4,
null,
null,
"Failed to set start level of Bundle:" + finalList[i], ex); //$NON-NLS-1$
}
}
if (Activator.DEBUG) {
System.out.println("\nBundles installation completed.\n");
}
return toRefresh;
}
private void refreshPackages(Bundle[] bundles, BundleContext context) {
if (bundles.length == 0 || packageAdminService == null)
return;
final boolean[] flag = new boolean[] { false };
FrameworkListener listener = new FrameworkListener() {
public void frameworkEvent(FrameworkEvent event) {
if (event.getType() == FrameworkEvent.PACKAGES_REFRESHED) {
synchronized (flag) {
flag[0] = true;
flag.notifyAll();
}
}
}
};
context.addFrameworkListener(listener);
packageAdminService.refreshPackages(bundles);
synchronized (flag) {
while (!flag[0]) {
try {
flag.wait();
} catch (InterruptedException e) {
// ignore
}
}
}
// if (DEBUG) {
// for (int i = 0; i < bundles.length; i++) {
// System.out.println(SimpleConfiguratorUtils.getBundleStateString(bundles[i]));
// }
// }
context.removeFrameworkListener(listener);
}
private void startBundles(Bundle[] bundles) {
for (int i = 0; i < bundles.length; i++) {
Bundle bundle = bundles[i];
if (bundle.getState() == Bundle.UNINSTALLED) {
System.err.println("Could not start: "
+ bundle.getSymbolicName() + '(' + bundle.getLocation()
+ ':' + bundle.getBundleId() + ')'
+ ". It's state is uninstalled.");
continue;
}
if (bundle.getState() == Bundle.STARTING
&& (bundle == callingBundle || bundle == manipulatingContext
.getBundle()))
continue;
if (packageAdminService.getBundleType(bundle) == PackageAdmin.BUNDLE_TYPE_FRAGMENT)
continue;
try {
bundle.start();
if (Activator.DEBUG)
System.out.println("Start Bundle: "
+ bundle.getSymbolicName() + " OK.");
} catch (BundleException e) {
if (Activator.DEBUG)
System.out.println("Start Bundle: "
+ bundle.getSymbolicName() + " Failed.");
e.printStackTrace();
// FrameworkLogEntry entry = new
// FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME,
// NLS.bind(EclipseAdaptorMsg.ECLIPSE_STARTUP_FAILED_START,
// bundle.getLocation()),
// 0, e, null);
// log.log(entry);
}
}
}
/**
* Uninstall bundles which are not listed on finalList.
*
* @param finalList
* bundles list not to be uninstalled.
* @param packageAdmin
* package admin service.
* @return Collection HashSet of bundles finally installed.
*/
private Collection uninstallBundles(BundleInfo[] finalList,
PackageAdmin packageAdmin) {
Bundle[] allBundles = manipulatingContext.getBundles();
// Build a set with all the bundles from the system
Set removedBundles = new HashSet(allBundles.length);
// configurator.setPrerequisiteBundles(allBundles);
for (int i = 0; i < allBundles.length; i++) {
if (allBundles[i].getBundleId() == 0)
continue;
removedBundles.add(allBundles[i]);
}
// Remove all the bundles appearing in the final list from the set of
// installed bundles
for (int i = 0; i < finalList.length; i++) {
if (finalList[i] == null)
continue;
Bundle[] toAdd = packageAdmin.getBundles(finalList[i]
.getSymbolicName(), getVersionRange(finalList[i]
.getVersion()));
for (int j = 0; toAdd != null && j < toAdd.length; j++) {
removedBundles.remove(toAdd[j]);
}
}
for (Iterator iter = removedBundles.iterator(); iter.hasNext();) {
try {
Bundle bundle = ((Bundle) iter.next());
if (bundle.getLocation().startsWith("initial@")) {
if (Activator.DEBUG)
System.out
.println("Simple configurator thinks a bundle installed by the boot strap should be uninstalled:" + bundle.getSymbolicName() + '(' + bundle.getLocation() + ':' + bundle.getBundleId() + ')'); //$NON-NLS-1$
// Avoid uninstalling bundles that the boot strap code
// thinks should be
// installed (bug 232191)
iter.remove();
continue;
}
bundle.uninstall();
if (Activator.DEBUG)
System.out
.println("uninstalled Bundle:" + bundle.getSymbolicName() + '(' + bundle.getLocation() + ':' + bundle.getBundleId() + ')'); //$NON-NLS-1$
} catch (BundleException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return removedBundles;
}
private String getVersionRange(String version) {
return version == null ? null : new StringBuffer().append('[').append(
version).append(',').append(version).append(']').toString();
}
}