1 /******************************************************************************* 2 * Copyright (c) 2000, 2017 IBM Corporation and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, 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 *******************************************************************************/ 14 package org.eclipse.update.internal.configurator; 15 16 import java.io.*; 17 import java.net.*; 18 import java.util.ArrayList; 19 import java.util.Arrays; 20 import java.util.Collection; 21 import java.util.Date; 22 import java.util.HashMap; 23 import java.util.Iterator; 24 import java.util.Map; 25 import java.util.zip.*; 26 27 import org.eclipse.core.runtime.*; 28 import org.eclipse.osgi.service.environment.*; 29 import org.eclipse.osgi.util.NLS; 30 import org.eclipse.update.configurator.*; 31 import org.eclipse.update.configurator.IPlatformConfiguration.*; 32 import org.w3c.dom.*; 33 import org.xml.sax.*; 34 35 36 public class SiteEntry implements IPlatformConfiguration.ISiteEntry, IConfigurationConstants{ 37 private static final String MAC_OS_MARKER = ".DS_Store"; //$NON-NLS-1$ 38 39 private URL url; // this is the external URL for the site 40 private URL resolvedURL; // this is the resolved URL used internally 41 private ISitePolicy policy; 42 private boolean updateable = true; 43 private Map<String, IFeatureEntry> featureEntries; 44 private ArrayList<PluginEntry> pluginEntries; 45 private long changeStamp; 46 private long featuresChangeStamp; 47 private long pluginsChangeStamp; 48 private String linkFileName; 49 private boolean enabled = true; 50 private Configuration config; 51 52 private static FeatureParser featureParser = new FeatureParser(); 53 private static PluginParser pluginParser = new PluginParser(); 54 private static boolean isMacOS = Utils.getOS().equals(Constants.OS_MACOSX); 55 SiteEntry(URL url)56 public SiteEntry(URL url) { 57 this(url,null); 58 } 59 SiteEntry(URL url, ISitePolicy policy)60 public SiteEntry(URL url, ISitePolicy policy) { 61 if (url == null) 62 try { 63 url = new URL("platform:/base/"); //$NON-NLS-1$ try using platform-relative URL 64 } catch (MalformedURLException e) { 65 url = PlatformConfiguration.getInstallURL(); // ensure we come up ... use absolute file URL 66 } 67 68 if (policy == null) 69 policy = new SitePolicy(PlatformConfiguration.getDefaultPolicy(), DEFAULT_POLICY_LIST); 70 71 if (url.getProtocol().equals("file")) { //$NON-NLS-1$ 72 try { 73 // TODO remove this when platform fixes local file url's 74 this.url = new File(url.getFile()).toURL(); 75 } catch (MalformedURLException e1) { 76 this.url = url; 77 } 78 } else 79 this.url = url; 80 81 this.policy = policy; 82 this.resolvedURL = this.url; 83 } 84 setConfig(Configuration config)85 public void setConfig(Configuration config) { 86 this.config = config; 87 if (url.getProtocol().equals("platform")) { //$NON-NLS-1$ 88 try { 89 // resolve the config location relative to the configURL 90 if (url.getPath().startsWith("/config")) { 91 URL configURL = config.getURL(); 92 URL config_loc = new URL(configURL, ".."); 93 resolvedURL = PlatformConfiguration.resolvePlatformURL(url, config_loc); // 19536 94 } 95 else 96 resolvedURL = PlatformConfiguration.resolvePlatformURL(url, config.getInstallURL()); // 19536 97 } catch (IOException e) { 98 // will use the baseline URL ... 99 } 100 } 101 } 102 getConfig()103 public Configuration getConfig() { 104 return config; 105 } 106 107 @Override getURL()108 public URL getURL() { 109 return url; 110 } 111 112 @Override getSitePolicy()113 public ISitePolicy getSitePolicy() { 114 return policy; 115 } 116 117 @Override setSitePolicy(ISitePolicy policy)118 public synchronized void setSitePolicy(ISitePolicy policy) { 119 if (policy == null) 120 throw new IllegalArgumentException(); 121 this.policy = policy; 122 } 123 124 @Override getFeatures()125 public String[] getFeatures() { 126 return getDetectedFeatures(); 127 } 128 129 @Override getPlugins()130 public String[] getPlugins() { 131 132 ISitePolicy policy = getSitePolicy(); 133 134 if (policy.getType() == ISitePolicy.USER_INCLUDE) 135 return policy.getList(); 136 137 if (policy.getType() == ISitePolicy.USER_EXCLUDE) { 138 ArrayList<String> detectedPlugins = new ArrayList<>(Arrays.asList(getDetectedPlugins())); 139 for (String excludedPlugin : policy.getList()) { 140 if (detectedPlugins.contains(excludedPlugin)) 141 detectedPlugins.remove(excludedPlugin); 142 } 143 return detectedPlugins.toArray(new String[0]); 144 } 145 146 if (policy.getType() == ISitePolicy.MANAGED_ONLY) { 147 PluginEntry[] managedPlugins = getManagedPlugins(); 148 String[] managedPluginsURLs = new String[managedPlugins.length]; 149 for (int i=0; i<managedPlugins.length; i++) 150 managedPluginsURLs[i] = managedPlugins[i].getURL(); 151 152 return managedPluginsURLs; 153 } 154 155 // bad policy type 156 return new String[0]; 157 } 158 getManagedPlugins()159 private PluginEntry[] getManagedPlugins() { 160 // Note: 161 // We detect all the plugins on the site, but it would be faster 162 // to just lookup the plugins that correspond to the entries found in each feature. 163 // TODO fix the above 164 if (pluginEntries == null) 165 detectPlugins(); 166 if (featureEntries == null) 167 detectFeatures(); 168 169 // cache all the plugin entries for faster lookup later 170 Map<VersionedIdentifier, PluginEntry> cachedPlugins = new HashMap<>(pluginEntries.size()); 171 for (PluginEntry p : pluginEntries) { 172 cachedPlugins.put(p.getVersionedIdentifier(), p); 173 } 174 175 ArrayList<PluginEntry> managedPlugins = new ArrayList<>(); 176 for (IFeatureEntry feature : featureEntries.values()) { 177 if (!(feature instanceof FeatureEntry)) 178 continue; 179 180 for (PluginEntry plugin : ((FeatureEntry)feature).getPluginEntries()) 181 if (cachedPlugins.containsKey(plugin.getVersionedIdentifier())) 182 managedPlugins.add(cachedPlugins.get(plugin.getVersionedIdentifier())); 183 184 } 185 return managedPlugins.toArray(new PluginEntry[managedPlugins.size()]); 186 } 187 getPluginEntries()188 public PluginEntry[] getPluginEntries() { 189 String[] pluginURLs = getPlugins(); 190 // hash the array, for faster lookups 191 HashMap<String, String> map = new HashMap<>(pluginURLs.length); 192 for (String pluginURL : pluginURLs) 193 map.put(pluginURL, pluginURL); 194 195 if (pluginEntries == null) 196 detectPlugins(); 197 198 ArrayList<PluginEntry> plugins = new ArrayList<>(pluginURLs.length); 199 for (int i=0; i<pluginEntries.size(); i++) { 200 PluginEntry p = pluginEntries.get(i); 201 if (map.containsKey(p.getURL())) 202 plugins.add(p); 203 } 204 return plugins.toArray(new PluginEntry[plugins.size()]); 205 } 206 207 @Override getChangeStamp()208 public long getChangeStamp() { 209 if (changeStamp == 0) 210 computeChangeStamp(); 211 return changeStamp; 212 } 213 214 @Override getFeaturesChangeStamp()215 public long getFeaturesChangeStamp() { 216 if (featuresChangeStamp == 0) 217 computeFeaturesChangeStamp(); 218 return featuresChangeStamp; 219 } 220 221 @Override getPluginsChangeStamp()222 public long getPluginsChangeStamp() { 223 if (pluginsChangeStamp == 0) 224 computePluginsChangeStamp(); 225 return pluginsChangeStamp; 226 } 227 228 @Override isUpdateable()229 public boolean isUpdateable() { 230 return updateable; 231 } 232 setUpdateable(boolean updateable)233 public void setUpdateable(boolean updateable) { 234 this.updateable = updateable; 235 } 236 237 @Override isNativelyLinked()238 public boolean isNativelyLinked() { 239 return isExternallyLinkedSite(); 240 } 241 getResolvedURL()242 public URL getResolvedURL() { 243 return resolvedURL; 244 } 245 246 /** 247 * Detect new features (timestamp > current site timestamp) 248 * and validates existing features (they might have been removed) 249 */ detectFeatures()250 private void detectFeatures() { 251 252 if (featureEntries != null) 253 validateFeatureEntries(); 254 else 255 featureEntries = new HashMap<>(); 256 257 if (!PlatformConfiguration.supportsDetection(resolvedURL, config.getInstallURL())) 258 return; 259 260 // locate feature entries on site 261 File siteRoot = new File(resolvedURL.getFile().replace('/', File.separatorChar)); 262 File featuresDir = new File(siteRoot, FEATURES); 263 if (featuresDir.exists()) { 264 // handle the installed features under the features directory 265 File[] dirs = featuresDir.listFiles((FileFilter) f -> { 266 // mac os folders contain a file .DS_Store in each folder, and we need to skip it (bug 76869) 267 if (isMacOS && f.getName().equals(MAC_OS_MARKER)) 268 return false; 269 boolean valid = f.isDirectory() && (new File(f,FEATURE_XML).exists()); 270 if (!valid) 271 Utils.log(NLS.bind(Messages.SiteEntry_cannotFindFeatureInDir, (new String[] { f.getAbsolutePath() }))); 272 return valid; 273 }); 274 275 for (File dir : dirs) { 276 try { 277 File featureXML = new File(dir, FEATURE_XML); 278 if (featureXML.lastModified() <= featuresChangeStamp && 279 dir.lastModified() <= featuresChangeStamp) 280 continue; 281 URL featureURL = featureXML.toURL(); 282 FeatureEntry featureEntry = featureParser.parse(featureURL); 283 if (featureEntry != null) 284 addFeatureEntry(featureEntry); 285 } catch (MalformedURLException e) { 286 Utils.log(NLS.bind(Messages.InstalledSiteParser_UnableToCreateURLForFile, (new String[] { featuresDir.getAbsolutePath() }))); 287 } 288 } 289 } 290 291 Utils.debug(resolvedURL.toString() + " located " + featureEntries.size() + " feature(s)"); //$NON-NLS-1$ //$NON-NLS-2$ 292 } 293 294 /** 295 * Detect new plugins (timestamp > current site timestamp) 296 * and validates existing plugins (they might have been removed) 297 */ detectPlugins()298 private void detectPlugins() { 299 boolean compareTimeStamps = false; 300 if (pluginEntries != null) { 301 validatePluginEntries(); 302 compareTimeStamps = true; // only pick up newer plugins 303 } else 304 pluginEntries = new ArrayList<>(); 305 306 if (!PlatformConfiguration.supportsDetection(resolvedURL, config.getInstallURL())) 307 return; 308 309 // locate plugin entries on site 310 File pluginsDir = new File(resolvedURL.getFile(), PLUGINS); 311 312 if (pluginsDir.exists() && pluginsDir.isDirectory()) { 313 for (File file : pluginsDir.listFiles()) { 314 if(file.isDirectory()){ 315 detectUnpackedPlugin(file, compareTimeStamps); 316 }else if(file.getName().endsWith(".jar")){ //$NON-NLS-1$ 317 detectPackedPlugin(file, compareTimeStamps); 318 }else{ 319 // not bundle file 320 } 321 } 322 } 323 324 Utils.debug(resolvedURL.toString() + " located " + pluginEntries.size() + " plugin(s)"); //$NON-NLS-1$ //$NON-NLS-2$ 325 } 326 327 /** 328 * @param file a plugin jar 329 * @param compareTimeStamps set to true when looking for plugins changed since last time they were detected 330 */ detectPackedPlugin(File file, boolean compareTimeStamps)331 private void detectPackedPlugin(File file, boolean compareTimeStamps) { 332 // plugin to run directly from jar 333 if (compareTimeStamps && file.lastModified() <= pluginsChangeStamp) { 334 return; 335 } 336 String entryName = META_MANIFEST_MF; 337 InputStream bundleManifestIn = null; 338 InputStream pluginManifestIn = null; 339 String pluginURL = PLUGINS + "/" + file.getName(); //$NON-NLS-1$ 340 try (ZipFile z = new ZipFile(file)){ 341 // First, check if has valid bundle manifest 342 343 if (z.getEntry(entryName) != null) { 344 bundleManifestIn = z.getInputStream(new ZipEntry(entryName)); 345 BundleManifest manifest = new BundleManifest(bundleManifestIn, 346 pluginURL); 347 if (manifest.exists()) { 348 addPluginEntry(manifest.getPluginEntry()); 349 return; 350 } 351 } 352 // no bundle manifest, check for plugin.xml or fragment.xml 353 entryName = PLUGIN_XML; 354 if (z.getEntry(entryName) == null) { 355 entryName = FRAGMENT_XML; 356 } 357 if (z.getEntry(entryName) != null) { 358 pluginManifestIn = z.getInputStream(new ZipEntry(entryName)); 359 PluginEntry entry1 = pluginParser.parse(pluginManifestIn, 360 pluginURL); 361 addPluginEntry(entry1); 362 } 363 } catch (IOException e5) { 364 String pluginFileString2 = pluginURL + "!" + entryName; //$NON-NLS-1$ 365 Utils.log(NLS.bind(Messages.InstalledSiteParser_ErrorAccessing, (new String[] { pluginFileString2 }))); 366 } catch (SAXException e3) { 367 String pluginFileString1 = pluginURL + "!" + entryName; //$NON-NLS-1$ 368 Utils.log(NLS.bind(Messages.InstalledSiteParser_ErrorParsingFile, (new String[] { pluginFileString1 }))); 369 } finally { 370 if (bundleManifestIn != null) { 371 try { 372 bundleManifestIn.close(); 373 } catch (IOException e4) { 374 } 375 } 376 if (pluginManifestIn != null) { 377 try { 378 pluginManifestIn.close(); 379 } catch (IOException e2) { 380 } 381 } 382 } 383 } 384 /** 385 * @param file a plugin directory 386 * @param compareTimeStamps set to true when looking for plugins changed since last time they were detected 387 */ detectUnpackedPlugin(File file, boolean compareTimeStamps)388 private void detectUnpackedPlugin(File file, boolean compareTimeStamps) { 389 // unpacked plugin 390 long dirTimestamp = file.lastModified(); 391 File pluginFile = new File(file, META_MANIFEST_MF); 392 try { 393 // First, check if has valid bundle manifest 394 BundleManifest bundleManifest = new BundleManifest(pluginFile); 395 if (bundleManifest.exists()) { 396 if (compareTimeStamps 397 && dirTimestamp <= pluginsChangeStamp 398 && pluginFile.lastModified() <= pluginsChangeStamp) 399 return; 400 PluginEntry entry = bundleManifest.getPluginEntry(); 401 addPluginEntry(entry); 402 } else { 403 // no bundle manifest, check for plugin.xml or fragment.xml 404 pluginFile = new File(file, PLUGIN_XML); 405 if (!pluginFile.exists()) { 406 pluginFile = new File(file, FRAGMENT_XML); 407 } 408 if (pluginFile.exists() && !pluginFile.isDirectory()) { 409 // TODO in the future, assume that the timestamps are not 410 // reliable, 411 // or that the user manually modified an existing plugin, 412 // so 413 // the apparently modifed plugin may actually be configured 414 // already. 415 // We will need to double check for this. END to do. 416 if (compareTimeStamps 417 && dirTimestamp <= pluginsChangeStamp 418 && pluginFile.lastModified() <= pluginsChangeStamp) 419 return; 420 PluginEntry entry = pluginParser.parse(pluginFile); 421 addPluginEntry(entry); 422 } 423 } 424 } catch (IOException e) { 425 String pluginFileString = pluginFile.getAbsolutePath(); 426 if (ConfigurationActivator.DEBUG) 427 Utils.log(Utils.newStatus(NLS.bind(Messages.InstalledSiteParser_ErrorParsingFile, (new String[] { pluginFileString })), e)); 428 else 429 Utils.log(NLS.bind(Messages.InstalledSiteParser_ErrorAccessing, (new String[] { pluginFileString }))); 430 } catch (SAXException e) { 431 String pluginFileString = pluginFile.getAbsolutePath(); 432 Utils.log(NLS.bind(Messages.InstalledSiteParser_ErrorParsingFile, (new String[] { pluginFileString }))); 433 } 434 } 435 436 /** 437 * @return list of feature url's (relative to site) 438 */ getDetectedFeatures()439 private synchronized String[] getDetectedFeatures() { 440 if (featureEntries == null) 441 detectFeatures(); 442 String[] features = new String[featureEntries.size()]; 443 Iterator<IFeatureEntry> iterator = featureEntries.values().iterator(); 444 for (int i=0; i<features.length; i++) 445 features[i] = ((FeatureEntry)iterator.next()).getURL(); 446 return features; 447 } 448 449 /** 450 * @return list of plugin url's (relative to site) 451 */ getDetectedPlugins()452 private synchronized String[] getDetectedPlugins() { 453 if (pluginEntries == null) 454 detectPlugins(); 455 456 String[] plugins = new String[pluginEntries.size()]; 457 for (int i=0; i<plugins.length; i++) 458 plugins[i] = pluginEntries.get(i).getURL(); 459 return plugins; 460 } 461 computeChangeStamp()462 private void computeChangeStamp() { 463 changeStamp = Math.max(computeFeaturesChangeStamp(), computePluginsChangeStamp()); 464 // changeStampIsValid = true; 465 } 466 computeFeaturesChangeStamp()467 private synchronized long computeFeaturesChangeStamp() { 468 if (featuresChangeStamp > 0) 469 return featuresChangeStamp; 470 471 long start = 0; 472 if (ConfigurationActivator.DEBUG) 473 start = (new Date()).getTime(); 474 String[] features = getFeatures(); 475 476 // compute stamp for the features directory 477 long dirStamp = 0; 478 if (PlatformConfiguration.supportsDetection(resolvedURL, config.getInstallURL())) { 479 File root = new File(resolvedURL.getFile().replace('/', File.separatorChar)); 480 File featuresDir = new File(root, FEATURES); 481 dirStamp = featuresDir.lastModified(); 482 } 483 featuresChangeStamp = Math.max(dirStamp, computeStamp(features)); 484 if (ConfigurationActivator.DEBUG) { 485 long end = (new Date()).getTime(); 486 Utils.debug(resolvedURL.toString() + " feature stamp: " + featuresChangeStamp + " in " + (end - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ 487 } 488 return featuresChangeStamp; 489 } 490 computePluginsChangeStamp()491 private synchronized long computePluginsChangeStamp() { 492 if (pluginsChangeStamp > 0) 493 return pluginsChangeStamp; 494 495 if (!PlatformConfiguration.supportsDetection(resolvedURL, config.getInstallURL())) { 496 Utils.log(NLS.bind(Messages.SiteEntry_computePluginStamp, (new String[] { resolvedURL.toExternalForm() }))); 497 return 0; 498 } 499 500 // compute stamp for the plugins directory 501 File root = new File(resolvedURL.getFile().replace('/', File.separatorChar)); 502 File pluginsDir = new File(root, PLUGINS); 503 if (!pluginsDir.exists() || !pluginsDir.isDirectory()) { 504 Utils.debug(NLS.bind(Messages.SiteEntry_pluginsDir, (new String[] { pluginsDir.getAbsolutePath() }))); 505 return 0; 506 } 507 508 pluginsChangeStamp = pluginsDir.lastModified(); 509 return pluginsChangeStamp; 510 } 511 computeStamp(String[] targets)512 private long computeStamp(String[] targets) { 513 514 long result = 0; 515 if (!PlatformConfiguration.supportsDetection(resolvedURL, config.getInstallURL())) { 516 // NOTE: this path should not be executed until we support running 517 // from an arbitrary URL (in particular from http server). For 518 // now just compute stamp across the list of names. Eventually 519 // when general URLs are supported we need to do better (factor 520 // in at least the existence of the target). However, given this 521 // code executes early on the startup sequence we need to be 522 // extremely mindful of performance issues. 523 // In fact, we should get the last modified from the connection 524 for (String target : targets) 525 result ^= target.hashCode(); 526 Utils.debug("*WARNING* computing stamp using URL hashcodes only"); //$NON-NLS-1$ 527 } else { 528 // compute stamp across local targets 529 File rootFile = new File(resolvedURL.getFile().replace('/', File.separatorChar)); 530 if (rootFile.exists()) { 531 File f = null; 532 for (String target : targets) { 533 f = new File(rootFile, target); 534 if (f.exists()) 535 result = Math.max(result, f.lastModified()); 536 } 537 } 538 } 539 540 return result; 541 } 542 setLinkFileName(String linkFileName)543 public void setLinkFileName(String linkFileName) { 544 this.linkFileName = linkFileName; 545 } 546 getLinkFileName()547 public String getLinkFileName() { 548 return linkFileName; 549 } 550 isExternallyLinkedSite()551 public boolean isExternallyLinkedSite() { 552 return (linkFileName != null && !linkFileName.trim().isEmpty()); 553 } 554 refresh()555 public synchronized void refresh() { 556 // reset computed values. Will be updated on next access. 557 featuresChangeStamp = 0; 558 pluginsChangeStamp = 0; 559 changeStamp = 0; 560 featureEntries = null; 561 pluginEntries = null; 562 } 563 refreshPlugins()564 public void refreshPlugins() { 565 // reset computed values. Will be updated on next access. 566 pluginsChangeStamp = 0; 567 changeStamp = 0; 568 pluginEntries = null; 569 } 570 addFeatureEntry(IFeatureEntry feature)571 public void addFeatureEntry(IFeatureEntry feature) { 572 if (featureEntries == null) 573 featureEntries = new HashMap<>(); 574 // Make sure we keep the larger version of same feature 575 IFeatureEntry existing = featureEntries.get(feature.getFeatureIdentifier()); 576 if (existing != null) { 577 VersionedIdentifier existingVersion = new VersionedIdentifier(existing.getFeatureIdentifier(), existing.getFeatureVersion()); 578 VersionedIdentifier newVersion = new VersionedIdentifier(feature.getFeatureIdentifier(), feature.getFeatureVersion()); 579 if (existingVersion.getVersion().compareTo(newVersion.getVersion()) < 0) { 580 featureEntries.put(feature.getFeatureIdentifier(), feature); 581 pluginsChangeStamp = 0; 582 } else if (existingVersion.equals(newVersion)) { 583 // log error if same feature version/id but a different url 584 if (feature instanceof FeatureEntry && existing instanceof FeatureEntry && 585 !((FeatureEntry)feature).getURL().equals(((FeatureEntry)existing).getURL())) 586 Utils.log(NLS.bind(Messages.SiteEntry_duplicateFeature, (new String[] { getURL().toExternalForm(), existing.getFeatureIdentifier() }))); 587 } 588 } else { 589 featureEntries.put(feature.getFeatureIdentifier(), feature); 590 pluginsChangeStamp = 0; 591 } 592 if (feature instanceof FeatureEntry) 593 ((FeatureEntry)feature).setSite(this); 594 } 595 getFeatureEntries()596 public FeatureEntry[] getFeatureEntries() { 597 if (featureEntries == null) 598 detectFeatures(); 599 600 if (featureEntries == null) 601 return new FeatureEntry[0]; 602 return featureEntries.values().toArray(new FeatureEntry[featureEntries.size()]); 603 } 604 addPluginEntry(PluginEntry plugin)605 public void addPluginEntry(PluginEntry plugin) { 606 if (pluginEntries == null) 607 pluginEntries = new ArrayList<>(); 608 // Note: we could use the latest version of the same plugin, like we do for features, but we let the runtime figure it out 609 pluginEntries.add(plugin); 610 } 611 getAllPluginEntries()612 public PluginEntry[] getAllPluginEntries() { 613 if (pluginEntries == null) 614 detectPlugins(); 615 return pluginEntries.toArray(new PluginEntry[pluginEntries.size()]); 616 } 617 loadFromDisk(long lastChange)618 public void loadFromDisk(long lastChange) throws CoreException{ 619 featuresChangeStamp = lastChange; 620 pluginsChangeStamp = lastChange; 621 detectFeatures(); 622 detectPlugins(); 623 } 624 625 /** 626 * Saves state as xml content in a given parent element 627 * @param doc 628 */ toXML(Document doc)629 public Element toXML(Document doc) { 630 631 Element siteElement = doc.createElement(CFG_SITE); 632 633 if (getURL() != null) { 634 URL toPersist = (config == null || config.isTransient()) ? getURL() : Utils.makeRelative(Utils.getInstallURL(), getURL()); 635 siteElement.setAttribute(CFG_URL, toPersist.toString()); 636 } 637 638 siteElement.setAttribute(CFG_ENABLED, isEnabled() ? "true" : "false"); //$NON-NLS-1$ //$NON-NLS-2$ 639 siteElement.setAttribute(CFG_UPDATEABLE, isUpdateable() ? "true" : "false"); //$NON-NLS-1$ //$NON-NLS-2$ 640 if (isExternallyLinkedSite()) 641 siteElement.setAttribute(CFG_LINK_FILE, getLinkFileName().trim().replace(File.separatorChar, '/')); 642 643 int type = getSitePolicy().getType(); 644 String typeString = CFG_POLICY_TYPE_UNKNOWN; 645 try { 646 typeString = CFG_POLICY_TYPE[type]; 647 } catch (IndexOutOfBoundsException e) { 648 // ignore bad attribute ... 649 } 650 siteElement.setAttribute(CFG_POLICY, typeString); 651 String[] list = getSitePolicy().getList(); 652 if (list.length > 0) { 653 StringBuilder sb = new StringBuilder(256); 654 for (int i=0; i<list.length-1; i++) { 655 sb.append(list[i]); 656 sb.append(','); 657 } 658 sb.append(list[list.length-1]); 659 siteElement.setAttribute(CFG_LIST, sb.toString()); 660 } 661 // // note: we don't save features inside the site element. 662 663 // collect feature entries 664 // configElement.setAttribute(CFG_FEATURE_ENTRY_DEFAULT, defaultFeature); 665 for (FeatureEntry feat : getFeatureEntries()) { 666 Element featureElement = feat.toXML(doc); 667 siteElement.appendChild(featureElement); 668 } 669 670 return siteElement; 671 } 672 validateFeatureEntries()673 private void validateFeatureEntries() { 674 File root = new File(resolvedURL.getFile().replace('/', File.separatorChar)); 675 Iterator<IFeatureEntry> iterator = featureEntries.values().iterator(); 676 Collection<String> deletedFeatures = new ArrayList<>(); 677 while(iterator.hasNext()) { 678 FeatureEntry feature = (FeatureEntry)iterator.next(); 679 // Note: in the future, we can check for absolute url as well. 680 // For now, feature url is features/org.eclipse.foo/feature.xml 681 File featureXML = new File(root, feature.getURL()); 682 if (!featureXML.exists()) 683 deletedFeatures.add(feature.getFeatureIdentifier()); 684 } 685 for (String string : deletedFeatures) { 686 featureEntries.remove(string); 687 } 688 } 689 validatePluginEntries()690 private void validatePluginEntries() { 691 File root = new File(resolvedURL.getFile().replace('/', File.separatorChar)); 692 Collection<PluginEntry> deletedPlugins = new ArrayList<>(); 693 for (PluginEntry plugin : pluginEntries) { 694 // Note: in the future, we can check for absolute url as well. 695 // For now, feature url is plugins/org.eclipse.foo/plugin.xml 696 File pluginLocation = new File(root, plugin.getURL()); 697 if (!pluginLocation.exists()) 698 deletedPlugins.add(plugin); 699 } 700 for (PluginEntry pluginEntry : deletedPlugins) { 701 pluginEntries.remove(pluginEntry); 702 } 703 } 704 isEnabled()705 public boolean isEnabled() { 706 return enabled; 707 } 708 setEnabled(boolean enable)709 public void setEnabled(boolean enable) { 710 this.enabled = enable; 711 } 712 getFeatureEntry(String id)713 public FeatureEntry getFeatureEntry(String id) { 714 for (FeatureEntry feature : getFeatureEntries()) 715 if (feature.getFeatureIdentifier().equals(id)) 716 return feature; 717 return null; 718 } 719 720 unconfigureFeatureEntry(IFeatureEntry feature)721 public boolean unconfigureFeatureEntry(IFeatureEntry feature) { 722 FeatureEntry existingFeature = getFeatureEntry(feature.getFeatureIdentifier()); 723 if (existingFeature != null) 724 featureEntries.remove(existingFeature.getFeatureIdentifier()); 725 return existingFeature != null; 726 } 727 728 /* 729 * This is a bit of a hack. 730 * When no features were added to the site, but the site is initialized from platform.xml 731 * we need to set the feature set to empty, so we don't try to detect them. 732 */ initialized()733 public void initialized() { 734 if (featureEntries == null) 735 featureEntries = new HashMap<>(); 736 } 737 } 738