1 /******************************************************************************* 2 * Copyright (c) 2007, 2020 IBM Corporation and others. 3 * All rights reserved. 4 * This program and the accompanying materials are made available under the 5 * terms of the Eclipse Public License 2.0 which accompanies this distribution, 6 * and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * IBM Corporation - initial API and implementation 13 * Red Hat, Inc (Krzysztof Daniel) - Bug 421935: Extend simpleconfigurator to 14 * read .info files from many locations, Bug 460967 15 *******************************************************************************/ 16 package org.eclipse.equinox.internal.simpleconfigurator; 17 18 import java.io.*; 19 import java.net.*; 20 import java.util.*; 21 import java.util.concurrent.CountDownLatch; 22 import org.eclipse.equinox.internal.simpleconfigurator.utils.*; 23 import org.osgi.framework.*; 24 import org.osgi.framework.namespace.*; 25 import org.osgi.framework.startlevel.BundleStartLevel; 26 import org.osgi.framework.wiring.*; 27 import org.osgi.resource.Namespace; 28 import org.osgi.resource.Requirement; 29 import org.osgi.service.packageadmin.PackageAdmin; 30 31 class ConfigApplier { 32 private static final String LAST_BUNDLES_INFO = "last.bundles.info"; //$NON-NLS-1$ 33 private static final String PROP_DEVMODE = "osgi.dev"; //$NON-NLS-1$ 34 35 private final BundleContext manipulatingContext; 36 private final PackageAdmin packageAdminService; 37 private final FrameworkWiring frameworkWiring; 38 private final boolean runningOnEquinox; 39 private final boolean inDevMode; 40 41 private final Bundle callingBundle; 42 private final URI baseLocation; 43 ConfigApplier(BundleContext context, Bundle callingBundle)44 ConfigApplier(BundleContext context, Bundle callingBundle) { 45 manipulatingContext = context; 46 this.callingBundle = callingBundle; 47 runningOnEquinox = "Eclipse".equals(context.getProperty(Constants.FRAMEWORK_VENDOR)); //$NON-NLS-1$ 48 inDevMode = manipulatingContext.getProperty(PROP_DEVMODE) != null; 49 baseLocation = runningOnEquinox ? EquinoxUtils.getInstallLocationURI(context) : null; 50 51 ServiceReference<PackageAdmin> packageAdminRef = manipulatingContext.getServiceReference(PackageAdmin.class); 52 if (packageAdminRef == null) 53 throw new IllegalStateException("No PackageAdmin service is available."); //$NON-NLS-1$ 54 packageAdminService = manipulatingContext.getService(packageAdminRef); 55 56 frameworkWiring = manipulatingContext.getBundle(Constants.SYSTEM_BUNDLE_LOCATION).adapt(FrameworkWiring.class); 57 } 58 install(URL url, boolean exclusiveMode)59 void install(URL url, boolean exclusiveMode) throws IOException { 60 List<BundleInfo> bundleInfoList = SimpleConfiguratorUtils.readConfiguration(url, baseLocation); 61 if (Activator.DEBUG) 62 System.out.println("applyConfiguration() bundleInfoList.size()=" + bundleInfoList.size()); 63 if (bundleInfoList.size() == 0) 64 return; 65 66 BundleInfo[] expectedState = Utils.getBundleInfosFromList(bundleInfoList); 67 68 // check for an update to the system bundle 69 String systemBundleSymbolicName = manipulatingContext.getBundle(0).getSymbolicName(); 70 Version systemBundleVersion = manipulatingContext.getBundle(0).getVersion(); 71 if (systemBundleSymbolicName != null) { 72 for (BundleInfo element : expectedState) { 73 String symbolicName = element.getSymbolicName(); 74 if (!systemBundleSymbolicName.equals(symbolicName)) 75 continue; 76 77 Version version = Version.parseVersion(element.getVersion()); 78 if (!systemBundleVersion.equals(version)) 79 throw new IllegalStateException("The System Bundle was updated. The framework must be restarted to finalize the configuration change"); 80 } 81 } 82 83 HashSet<BundleInfo> toUninstall = null; 84 if (!exclusiveMode) { 85 BundleInfo[] lastInstalledBundles = getLastState(); 86 if (lastInstalledBundles != null) { 87 toUninstall = new HashSet<>(Arrays.asList(lastInstalledBundles)); 88 toUninstall.removeAll(Arrays.asList(expectedState)); 89 } 90 saveStateAsLast(url); 91 } 92 93 Set<Bundle> prevouslyResolved = getResolvedBundles(); 94 Collection<Bundle> toRefresh = new ArrayList<>(); 95 Collection<Bundle> toStart = new ArrayList<>(); 96 if (exclusiveMode) { 97 toRefresh.addAll(installBundles(expectedState, toStart)); 98 toRefresh.addAll(uninstallBundles(expectedState, packageAdminService)); 99 } else { 100 toRefresh.addAll(installBundles(expectedState, toStart)); 101 if (toUninstall != null) 102 toRefresh.addAll(uninstallBundles(toUninstall)); 103 } 104 refreshPackages(toRefresh.toArray(new Bundle[toRefresh.size()]), manipulatingContext); 105 if (toRefresh.size() > 0) { 106 Bundle[] additionalRefresh = getAdditionalRefresh(prevouslyResolved, toRefresh); 107 if (additionalRefresh.length > 0) 108 refreshPackages(additionalRefresh, manipulatingContext); 109 } 110 startBundles(toStart.toArray(new Bundle[toStart.size()])); 111 } 112 getAdditionalRefresh(Set<Bundle> previouslyResolved, Collection<Bundle> toRefresh)113 private Bundle[] getAdditionalRefresh(Set<Bundle> previouslyResolved, Collection<Bundle> toRefresh) { 114 // This is the luna equinox framework or a non-equinox framework. 115 // Use standard OSGi API. 116 final Set<Bundle> additionalRefresh = new HashSet<>(); 117 final Set<Bundle> originalRefresh = new HashSet<>(toRefresh); 118 for (Bundle bundle : toRefresh) { 119 BundleRevision revision = bundle.adapt(BundleRevision.class); 120 if (bundle.getState() == Bundle.INSTALLED && revision != null && (revision.getTypes() & BundleRevision.TYPE_FRAGMENT) != 0) { 121 // this is an unresolved fragment; look to see if it has additional payload requirements 122 boolean foundPayLoadReq = false; 123 BundleRequirement hostReq = null; 124 Collection<Requirement> requirements = revision.getRequirements(null); 125 for (Requirement requirement : requirements) { 126 BundleRequirement req = (BundleRequirement) requirement; 127 if (HostNamespace.HOST_NAMESPACE.equals(req.getNamespace())) { 128 hostReq = req; 129 } 130 if (!HostNamespace.HOST_NAMESPACE.equals(req.getNamespace()) && !ExecutionEnvironmentNamespace.EXECUTION_ENVIRONMENT_NAMESPACE.equals(req.getNamespace())) { 131 // found a payload requirement 132 foundPayLoadReq = true; 133 } 134 } 135 if (foundPayLoadReq) { 136 Collection<BundleCapability> candidates = frameworkWiring.findProviders(hostReq); 137 for (BundleCapability candidate : candidates) { 138 if (!originalRefresh.contains(candidate.getRevision().getBundle())) { 139 additionalRefresh.add(candidate.getRevision().getBundle()); 140 } 141 } 142 } 143 } 144 } 145 146 for (Bundle bundle : previouslyResolved) { 147 BundleRevision revision = bundle.adapt(BundleRevision.class); 148 BundleWiring wiring = revision == null ? null : revision.getWiring(); 149 if (wiring != null) { 150 Collection<BundleRequirement> reqs = revision.getDeclaredRequirements(null); 151 Set<BundleRequirement> optionalReqs = new HashSet<>(); 152 for (BundleRequirement req : reqs) { 153 String namespace = req.getNamespace(); 154 // only do this for package and bundle namespaces 155 if (PackageNamespace.PACKAGE_NAMESPACE.equals(namespace) || BundleNamespace.BUNDLE_NAMESPACE.equals(namespace)) { 156 if (Namespace.RESOLUTION_OPTIONAL.equals(req.getDirectives().get(Namespace.REQUIREMENT_RESOLUTION_DIRECTIVE))) { 157 optionalReqs.add(req); 158 } 159 } 160 } 161 if (!optionalReqs.isEmpty()) { 162 wiring = getHostWiring(wiring); 163 // check that all optional requirements are wired 164 Collection<BundleWire> requiredWires = wiring.getRequiredWires(null); 165 for (BundleWire requiredWire : requiredWires) { 166 optionalReqs.remove(requiredWire.getRequirement()); 167 } 168 if (!optionalReqs.isEmpty()) { 169 // there are a number of optional requirements not wired 170 for (BundleRequirement bundleRequirement : optionalReqs) { 171 Collection<BundleCapability> candidates = frameworkWiring.findProviders(bundleRequirement); 172 // Filter out candidates that were previously resolved or are currently not resolved. 173 // There is no need to refresh the resource if the candidate was previously available. 174 for (Iterator<BundleCapability> iCandidates = candidates.iterator(); iCandidates.hasNext();) { 175 BundleCapability candidate = iCandidates.next(); 176 Bundle candidateBundle = candidate.getRevision().getBundle(); 177 // The candidate is not from the original refresh set, but 178 // it could have just became resolved as a result of new bundles. 179 if (previouslyResolved.contains(candidateBundle) || candidateBundle.getState() == Bundle.INSTALLED) { 180 iCandidates.remove(); 181 } 182 } 183 if (!candidates.isEmpty()) { 184 additionalRefresh.add(wiring.getBundle()); 185 break; 186 } 187 } 188 } 189 } 190 } 191 } 192 return additionalRefresh.toArray(new Bundle[additionalRefresh.size()]); 193 } 194 getHostWiring(BundleWiring wiring)195 private BundleWiring getHostWiring(BundleWiring wiring) { 196 if ((wiring.getRevision().getTypes() & BundleRevision.TYPE_FRAGMENT) == 0) { 197 // not a fragment 198 return wiring; 199 } 200 Collection<BundleWire> hostWires = wiring.getRequiredWires(HostNamespace.HOST_NAMESPACE); 201 // just use the first host wiring 202 if (hostWires.isEmpty()) { 203 return wiring; 204 } 205 BundleWire hostWire = hostWires.iterator().next(); 206 return hostWire.getProviderWiring(); 207 } 208 getResolvedBundles()209 private Set<Bundle> getResolvedBundles() { 210 Set<Bundle> resolved = new HashSet<>(); 211 Bundle[] allBundles = manipulatingContext.getBundles(); 212 for (Bundle bundle : allBundles) 213 if ((bundle.getState() & (Bundle.INSTALLED | Bundle.UNINSTALLED)) == 0) 214 resolved.add(bundle); 215 return resolved; 216 } 217 uninstallBundles(HashSet<BundleInfo> toUninstall)218 private Collection<Bundle> uninstallBundles(HashSet<BundleInfo> toUninstall) { 219 Collection<Bundle> removedBundles = new ArrayList<>(toUninstall.size()); 220 for (BundleInfo current : toUninstall) { 221 Bundle[] matchingBundles = packageAdminService.getBundles(current.getSymbolicName(), getVersionRange(current.getVersion())); 222 for (int j = 0; matchingBundles != null && j < matchingBundles.length; j++) { 223 try { 224 removedBundles.add(matchingBundles[j]); 225 matchingBundles[j].uninstall(); 226 } catch (BundleException e) { 227 //TODO log in debug mode... 228 } 229 } 230 } 231 return removedBundles; 232 } 233 saveStateAsLast(URL url)234 private void saveStateAsLast(URL url) { 235 236 File lastBundlesTxt = getLastBundleInfo(); 237 try (OutputStream destinationStream = new FileOutputStream(lastBundlesTxt)) { 238 ArrayList<File> sourcesLocation = SimpleConfiguratorUtils.getInfoFiles(); 239 List<InputStream> sourceStreams = new ArrayList<>(sourcesLocation.size() + 1); 240 sourceStreams.add(url.openStream()); 241 if (Activator.EXTENDED) { 242 for (File source : sourcesLocation) { 243 sourceStreams.add(new FileInputStream(source)); 244 } 245 } 246 SimpleConfiguratorUtils.transferStreams(sourceStreams, destinationStream); 247 } catch (URISyntaxException e) { 248 // nothing, was discovered when starting framework 249 } catch (IOException e) { 250 //nothing 251 } 252 } 253 getLastBundleInfo()254 private File getLastBundleInfo() { 255 return manipulatingContext.getDataFile(LAST_BUNDLES_INFO); 256 } 257 getLastState()258 private BundleInfo[] getLastState() { 259 File lastBundlesInfo = getLastBundleInfo(); 260 if (!lastBundlesInfo.isFile()) 261 return null; 262 try { 263 return SimpleConfiguratorUtils.readConfiguration(lastBundlesInfo.toURL(), baseLocation).toArray(new BundleInfo[1]); 264 } catch (IOException e) { 265 return null; 266 } 267 } 268 installBundles(BundleInfo[] finalList, Collection<Bundle> toStart)269 private ArrayList<Bundle> installBundles(BundleInfo[] finalList, Collection<Bundle> toStart) { 270 ArrayList<Bundle> toRefresh = new ArrayList<>(); 271 272 String useReferenceProperty = manipulatingContext.getProperty(SimpleConfiguratorConstants.PROP_KEY_USE_REFERENCE); 273 boolean useReference = useReferenceProperty == null ? runningOnEquinox : Boolean.parseBoolean(useReferenceProperty); 274 275 for (BundleInfo element : finalList) { 276 if (element == null) 277 continue; 278 //TODO here we do not deal with bundles that don't have a symbolic id 279 //TODO Need to handle the case where getBundles return multiple value 280 281 String symbolicName = element.getSymbolicName(); 282 String version = element.getVersion(); 283 284 Bundle[] matches = null; 285 if (symbolicName != null && version != null) 286 matches = packageAdminService.getBundles(symbolicName, getVersionRange(version)); 287 288 String bundleLocation = SimpleConfiguratorUtils.getBundleLocation(element, useReference); 289 290 Bundle current = matches == null ? null : (matches.length == 0 ? null : matches[0]); 291 if (current == null) { 292 try { 293 current = manipulatingContext.installBundle(bundleLocation); 294 if (symbolicName != null && version != null) { 295 Version v; 296 try { 297 v = new Version(version); 298 if (!symbolicName.equals(current.getSymbolicName()) || !v.equals(current.getVersion())) { 299 // can happen if, for example, the new version of the bundle is installed 300 // to the same bundle location as the old version 301 current.update(); 302 } 303 } catch (IllegalArgumentException e) { 304 // invalid version string; should log 305 if (Activator.DEBUG) 306 e.printStackTrace(); 307 } 308 } 309 310 if (Activator.DEBUG) 311 System.out.println("installed bundle:" + element); //$NON-NLS-1$ 312 toRefresh.add(current); 313 } catch (BundleException e) { 314 if (Activator.DEBUG) { 315 System.err.println("Can't install " + symbolicName + '/' + version + " from location " + element.getLocation()); //$NON-NLS-1$ //$NON-NLS-2$ 316 e.printStackTrace(); 317 } 318 continue; 319 } 320 } else if (inDevMode && current.getBundleId() != 0 && current != manipulatingContext.getBundle() && !bundleLocation.equals(current.getLocation()) && !current.getLocation().startsWith("initial@")) { 321 // 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" 322 // The bundle exists; but the location is different. Uninstall the current and install the new one (bug 229700) 323 try { 324 current.uninstall(); 325 toRefresh.add(current); 326 } catch (BundleException e) { 327 if (Activator.DEBUG) { 328 System.err.println("Can't uninstall " + symbolicName + '/' + version + " from location " + current.getLocation()); //$NON-NLS-1$ //$NON-NLS-2$ 329 e.printStackTrace(); 330 } 331 continue; 332 } 333 try { 334 current = manipulatingContext.installBundle(bundleLocation); 335 if (Activator.DEBUG) 336 System.out.println("installed bundle:" + element); //$NON-NLS-1$ 337 toRefresh.add(current); 338 } catch (BundleException e) { 339 if (Activator.DEBUG) { 340 System.err.println("Can't install " + symbolicName + '/' + version + " from location " + element.getLocation()); //$NON-NLS-1$ //$NON-NLS-2$ 341 e.printStackTrace(); 342 } 343 continue; 344 } 345 } 346 347 // Mark Started 348 if (element.isMarkedAsStarted()) { 349 toStart.add(current); 350 } 351 352 // Set Start Level 353 int startLevel = element.getStartLevel(); 354 if (startLevel < 1) 355 continue; 356 if (current.getBundleId() == 0) 357 continue; 358 if (isFragment(current)) 359 continue; 360 if (SimpleConfiguratorConstants.TARGET_CONFIGURATOR_NAME.equals(current.getSymbolicName())) 361 continue; 362 363 try { 364 current.adapt(BundleStartLevel.class).setStartLevel(startLevel); 365 } catch (IllegalArgumentException ex) { 366 Utils.log(4, null, null, "Failed to set start level of Bundle:" + element, ex); //$NON-NLS-1$ 367 } 368 } 369 return toRefresh; 370 } 371 isFragment(Bundle current)372 private boolean isFragment(Bundle current) { 373 BundleRevision revision = current.adapt(BundleRevision.class); 374 return (revision != null) && ((revision.getTypes() & BundleRevision.TYPE_FRAGMENT) != 0); 375 } 376 refreshPackages(Bundle[] bundles, BundleContext context)377 private void refreshPackages(Bundle[] bundles, BundleContext context) { 378 if (bundles.length == 0 || packageAdminService == null) 379 return; 380 381 // Prior to Luna the Equinox framework would refresh all bundles with the same 382 // BSN automatically. This is no longer the case for Luna or other framework 383 // implementations. Here we want to make sure all existing bundles with the 384 // same BSN are refreshed also. 385 Set<Bundle> allSameBSNs = new LinkedHashSet<>(); // maintain order and avoid duplicates 386 for (Bundle bundle : bundles) { 387 allSameBSNs.add(bundle); 388 String bsn = bundle.getSymbolicName(); 389 if (bsn != null) { 390 // look for others with same BSN 391 Bundle[] sameBSNs = packageAdminService.getBundles(bsn, null); 392 if (sameBSNs != null) { 393 // likely contains the bundle we just added above but a set is used 394 allSameBSNs.addAll(Arrays.asList(sameBSNs)); 395 } 396 } 397 } 398 399 CountDownLatch latch = new CountDownLatch(1); 400 FrameworkListener listener = event -> { 401 if (event.getType() == FrameworkEvent.PACKAGES_REFRESHED) { 402 latch.countDown(); 403 } 404 }; 405 context.addFrameworkListener(listener); 406 packageAdminService.refreshPackages(allSameBSNs.toArray(new Bundle[0])); 407 408 try { 409 latch.await(); 410 } catch (InterruptedException e) { 411 // ignore 412 } 413 414 // if (DEBUG) { 415 // for (int i = 0; i < bundles.length; i++) { 416 // System.out.println(SimpleConfiguratorUtils.getBundleStateString(bundles[i])); 417 // } 418 // } 419 context.removeFrameworkListener(listener); 420 } 421 startBundles(Bundle[] bundles)422 private void startBundles(Bundle[] bundles) { 423 for (Bundle bundle : bundles) { 424 if (bundle.getState() == Bundle.UNINSTALLED) { 425 System.err.println("Could not start: " + bundle.getSymbolicName() + '(' + bundle.getLocation() + ':' + bundle.getBundleId() + ')' + ". It's state is uninstalled."); 426 continue; 427 } 428 if (bundle.getState() == Bundle.STARTING && (bundle == callingBundle || bundle == manipulatingContext.getBundle())) 429 continue; 430 if (isFragment(bundle)) 431 continue; 432 if (bundle.getBundleId() == 0) 433 continue; 434 435 try { 436 bundle.start(); 437 if (Activator.DEBUG) 438 System.out.println("started Bundle:" + bundle.getSymbolicName() + '(' + bundle.getLocation() + ':' + bundle.getBundleId() + ')'); //$NON-NLS-1$ 439 } catch (BundleException e) { 440 e.printStackTrace(); 441 // FrameworkLogEntry entry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, NLS.bind(EclipseAdaptorMsg.ECLIPSE_STARTUP_FAILED_START, bundle.getLocation()), 0, e, null); 442 // log.log(entry); 443 } 444 } 445 } 446 447 /** 448 * Uninstall bundles which are not listed on finalList. 449 * 450 * @param finalList bundles list not to be uninstalled. 451 * @param packageAdmin package admin service. 452 * @return Collection HashSet of bundles finally installed. 453 */ uninstallBundles(BundleInfo[] finalList, PackageAdmin packageAdmin)454 private Collection<Bundle> uninstallBundles(BundleInfo[] finalList, PackageAdmin packageAdmin) { 455 Bundle[] allBundles = manipulatingContext.getBundles(); 456 457 //Build a set with all the bundles from the system 458 Set<Bundle> removedBundles = new HashSet<>(allBundles.length); 459 // configurator.setPrerequisiteBundles(allBundles); 460 for (Bundle allBundle : allBundles) { 461 if (allBundle.getBundleId() == 0) { 462 continue; 463 } 464 removedBundles.add(allBundle); 465 } 466 467 //Remove all the bundles appearing in the final list from the set of installed bundles 468 for (BundleInfo element : finalList) { 469 if (element == null) 470 continue; 471 Bundle[] toAdd = packageAdmin.getBundles(element.getSymbolicName(), getVersionRange(element.getVersion())); 472 for (int j = 0; toAdd != null && j < toAdd.length; j++) { 473 removedBundles.remove(toAdd[j]); 474 } 475 } 476 477 for (Iterator<Bundle> iter = removedBundles.iterator(); iter.hasNext();) { 478 try { 479 Bundle bundle = iter.next(); 480 if (bundle.getLocation().startsWith("initial@")) { 481 if (Activator.DEBUG) 482 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$ 483 // Avoid uninstalling bundles that the boot strap code thinks should be installed (bug 232191) 484 iter.remove(); 485 continue; 486 } 487 bundle.uninstall(); 488 if (Activator.DEBUG) 489 System.out.println("uninstalled Bundle:" + bundle.getSymbolicName() + '(' + bundle.getLocation() + ':' + bundle.getBundleId() + ')'); //$NON-NLS-1$ 490 } catch (BundleException e) { 491 // TODO Auto-generated catch block 492 e.printStackTrace(); 493 } 494 } 495 496 return removedBundles; 497 } 498 getVersionRange(String version)499 private String getVersionRange(String version) { 500 return version == null ? null : new StringBuilder().append('[').append(version).append(',').append(version).append(']').toString(); 501 } 502 } 503