1 /******************************************************************************* 2 * Copyright (c) 2008, 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 * Code 9 - ongoing development 14 *******************************************************************************/ 15 package org.eclipse.equinox.internal.p2.extensionlocation; 16 17 import java.io.*; 18 import java.net.URI; 19 import java.net.URISyntaxException; 20 import java.util.*; 21 import org.eclipse.core.runtime.*; 22 import org.eclipse.equinox.internal.p2.core.helpers.LogHelper; 23 import org.eclipse.equinox.internal.p2.publisher.eclipse.FeatureParser; 24 import org.eclipse.equinox.internal.p2.update.Site; 25 import org.eclipse.equinox.internal.provisional.p2.directorywatcher.*; 26 import org.eclipse.equinox.p2.core.ProvisionException; 27 import org.eclipse.equinox.p2.publisher.eclipse.*; 28 import org.eclipse.osgi.service.resolver.BundleDescription; 29 30 /** 31 * @since 1.0 32 */ 33 public class SiteListener extends DirectoryChangeListener { 34 35 public static final String SITE_POLICY = "org.eclipse.update.site.policy"; //$NON-NLS-1$ 36 public static final String SITE_LIST = "org.eclipse.update.site.list"; //$NON-NLS-1$ 37 private static final String FEATURES = "features"; //$NON-NLS-1$ 38 private static final String PLUGINS = "plugins"; //$NON-NLS-1$ 39 private static final String FEATURE_MANIFEST = "feature.xml"; //$NON-NLS-1$ 40 public static final Object UNINITIALIZED = "uninitialized"; //$NON-NLS-1$ 41 public static final Object INITIALIZING = "initializing"; //$NON-NLS-1$ 42 public static final Object INITIALIZED = "initialized"; //$NON-NLS-1$ 43 44 private String policy; 45 private String[] list; 46 private String siteLocation; 47 private DirectoryChangeListener delegate; 48 private String[] managedFiles; 49 private String[] toBeRemoved; 50 51 /* 52 * Return true if the given list contains the full path of the given file 53 * handle. Return false otherwise. 54 */ contains(String[] plugins, File file)55 private static boolean contains(String[] plugins, File file) { 56 String filename = file.getAbsolutePath(); 57 for (String plugin : plugins) { 58 if (filename.endsWith(plugin)) { 59 return true; 60 } 61 } 62 return false; 63 } 64 65 /** 66 * Converts a list of file names to a normalized form suitable for comparison. 67 */ normalize(String[] filenames)68 private String[] normalize(String[] filenames) { 69 for (int i = 0; i < filenames.length; i++) 70 filenames[i] = new File(filenames[i]).toString(); 71 return filenames; 72 } 73 74 /** 75 * Given one repo and a base location, ensure cause the other repo to be loaded and then 76 * poll the base location once updating the repositories accordingly. This method is used to 77 * ensure that both the metadata and artifact repos corresponding to one location are 78 * synchronized in one go. It is expected that both repos have been previously created 79 * so simply loading them here will work and that all their properties etc have been configured 80 * previously. 81 * @param metadataRepository 82 * @param artifactRepository 83 * @param base 84 */ synchronizeRepositories(ExtensionLocationMetadataRepository metadataRepository, ExtensionLocationArtifactRepository artifactRepository, File base)85 public static synchronized void synchronizeRepositories(ExtensionLocationMetadataRepository metadataRepository, ExtensionLocationArtifactRepository artifactRepository, File base) { 86 try { 87 if (metadataRepository == null) { 88 artifactRepository.reload(); 89 ExtensionLocationMetadataRepositoryFactory factory = new ExtensionLocationMetadataRepositoryFactory(); 90 factory.setAgent(artifactRepository.getProvisioningAgent()); 91 metadataRepository = (ExtensionLocationMetadataRepository) factory.load(artifactRepository.getLocation(), 0, null); 92 } else if (artifactRepository == null) { 93 metadataRepository.reload(); 94 ExtensionLocationArtifactRepositoryFactory factory = new ExtensionLocationArtifactRepositoryFactory(); 95 factory.setAgent(metadataRepository.getProvisioningAgent()); 96 artifactRepository = (ExtensionLocationArtifactRepository) factory.load(metadataRepository.getLocation(), 0, null); 97 } 98 } catch (ProvisionException e) { 99 // TODO need proper error handling here. What should we do if there is a failure 100 // when loading "the other" repo? 101 e.printStackTrace(); 102 return; 103 } 104 105 artifactRepository.state(INITIALIZING); 106 metadataRepository.state(INITIALIZING); 107 File plugins = new File(base, PLUGINS); 108 File features = new File(base, FEATURES); 109 DirectoryWatcher watcher = new DirectoryWatcher(new File[] {plugins, features}); 110 // here we have to sync with the inner repos as the extension location repos are 111 // read-only wrappers. 112 DirectoryChangeListener listener = new RepositoryListener(metadataRepository.metadataRepository, artifactRepository.artifactRepository); 113 if (metadataRepository.getProperties().get(SiteListener.SITE_POLICY) != null) 114 listener = new SiteListener(metadataRepository.getProperties(), metadataRepository.getLocation().toString(), new BundlePoolFilteredListener(listener)); 115 watcher.addListener(listener); 116 watcher.poll(); 117 artifactRepository.state(INITIALIZED); 118 metadataRepository.state(INITIALIZED); 119 } 120 121 /* 122 * Create a new site listener on the given site. 123 */ SiteListener(Map<String, String> properties, String url, DirectoryChangeListener delegate)124 public SiteListener(Map<String, String> properties, String url, DirectoryChangeListener delegate) { 125 this.siteLocation = url; 126 this.delegate = delegate; 127 this.policy = properties.get(SITE_POLICY); 128 Collection<String> listCollection = new HashSet<>(); 129 String listString = properties.get(SITE_LIST); 130 if (listString != null) 131 for (StringTokenizer tokenizer = new StringTokenizer(listString, ","); tokenizer.hasMoreTokens();) //$NON-NLS-1$ 132 listCollection.add(tokenizer.nextToken()); 133 this.list = normalize(listCollection.toArray(new String[listCollection.size()])); 134 } 135 136 @Override isInterested(File file)137 public boolean isInterested(File file) { 138 // make sure that our delegate and super-class are both interested in 139 // the file before we consider it 140 if (!delegate.isInterested(file)) 141 return false; 142 if (Site.POLICY_MANAGED_ONLY.equals(policy)) { 143 // we only want plug-ins referenced by features 144 return contains(getManagedFiles(), file); 145 } else if (Site.POLICY_USER_EXCLUDE.equals(policy)) { 146 // ensure the file doesn't refer to a plug-in in our list 147 if (contains(list, file)) 148 return false; 149 } else if (Site.POLICY_USER_INCLUDE.equals(policy)) { 150 // we are only interested in plug-ins in the list 151 if (!contains(list, file)) 152 return false; 153 } else { 154 // shouldn't happen... unknown policy type 155 return false; 156 } 157 // at this point we have either a user-include or user-exclude policy set 158 // and we think we are interested in the file. we should first check to 159 // see if it is in the list of things to be removed 160 return !isToBeRemoved(file); 161 } 162 163 /* 164 * Return a boolean value indicating whether or not the feature pointed to 165 * by the given file is in the update manager's list of features to be 166 * uninstalled in its clean-up phase. 167 */ isToBeRemoved(File file)168 private boolean isToBeRemoved(File file) { 169 String[] removed = getToBeRemoved(); 170 if (removed.length == 0) 171 return false; 172 Feature feature = getFeature(file); 173 if (feature == null) 174 return false; 175 for (String line : removed) { 176 // the line is a versioned identifier which is id_version 177 if (line.equals(feature.getId() + '_' + feature.getVersion())) 178 return true; 179 } 180 return false; 181 } 182 183 /* 184 * Parse and return the feature.xml file in the given location. 185 * Can return null. 186 */ getFeature(File location)187 private Feature getFeature(File location) { 188 if (location.isFile()) 189 return null; 190 File manifest = new File(location, FEATURE_MANIFEST); 191 if (!manifest.exists()) 192 return null; 193 FeatureParser parser = new FeatureParser(); 194 return parser.parse(location); 195 } 196 197 /* 198 * Return an array describing the list of features are are going 199 * to be removed by the update manager in its clean-up phase. 200 * The strings are in the format of versioned identifiers: id_version 201 */ getToBeRemoved()202 private String[] getToBeRemoved() { 203 if (toBeRemoved != null) 204 return toBeRemoved; 205 File configurationLocation = Activator.getConfigurationLocation(); 206 if (configurationLocation == null) { 207 LogHelper.log(new Status(IStatus.ERROR, Activator.ID, "Unable to compute the configuration location.")); //$NON-NLS-1$ 208 toBeRemoved = new String[0]; 209 return toBeRemoved; 210 } 211 File toBeUninstalledFile = new File(configurationLocation, "org.eclipse.update/toBeUninstalled"); //$NON-NLS-1$ 212 if (!toBeUninstalledFile.exists()) { 213 toBeRemoved = new String[0]; 214 return toBeRemoved; 215 } 216 // set it to be empty here in case we don't have a match in the file 217 toBeRemoved = new String[0]; 218 Properties properties = new Properties(); 219 try (InputStream input = new BufferedInputStream(new FileInputStream(toBeUninstalledFile))) { 220 properties.load(input); 221 } catch (IOException e) { 222 // TODO 223 } 224 String urlString = siteLocation; 225 if (urlString.endsWith(Constants.EXTENSION_LOCATION)) 226 urlString = urlString.substring(0, urlString.length() - Constants.EXTENSION_LOCATION.length()); 227 List<String> result = new ArrayList<>(); 228 for (Enumeration<Object> e = properties.elements(); e.hasMoreElements();) { 229 String line = (String) e.nextElement(); 230 StringTokenizer tokenizer = new StringTokenizer(line, ";"); //$NON-NLS-1$ 231 String targetSite = tokenizer.nextToken(); 232 try { 233 // the urlString is coming from the site location which is an encoded URI 234 // so we need to encode the targetSite string before we check for equality 235 if (!urlString.equals(URIUtil.fromString(targetSite).toString())) 236 continue; 237 } catch (URISyntaxException e1) { 238 // shouldn't happen 239 e1.printStackTrace(); 240 continue; 241 } 242 result.add(tokenizer.nextToken()); 243 } 244 toBeRemoved = result.toArray(new String[result.size()]); 245 return toBeRemoved; 246 } 247 248 /* 249 * Return an array of files which are managed. This includes all of the features 250 * for this site, as well as the locations for all the plug-ins referenced by those 251 * features. 252 */ getManagedFiles()253 private String[] getManagedFiles() { 254 if (managedFiles != null) 255 return managedFiles; 256 List<String> result = new ArrayList<>(); 257 File siteFile; 258 try { 259 siteFile = URIUtil.toFile(new URI(siteLocation)); 260 } catch (URISyntaxException e) { 261 LogHelper.log(new Status(IStatus.ERROR, Activator.ID, "Unable to create a URL from site location: " + siteLocation, e)); //$NON-NLS-1$ 262 return new String[0]; 263 } 264 Map<String, File> pluginCache = getPlugins(siteFile); 265 Map<File, Feature> featureCache = getFeatures(siteFile); 266 for (File featureFile : featureCache.keySet()) { 267 // add the feature path 268 result.add(featureFile.toString()); 269 Feature feature = featureCache.get(featureFile); 270 FeatureEntry[] entries = feature.getEntries(); 271 for (FeatureEntry entry : entries) { 272 // grab the right location from the plug-in cache 273 String key = entry.getId() + '/' + entry.getVersion(); 274 File pluginLocation = pluginCache.get(key); 275 if (pluginLocation != null) 276 result.add(pluginLocation.toString()); 277 } 278 } 279 managedFiles = normalize(result.toArray(new String[result.size()])); 280 return managedFiles; 281 } 282 283 /* 284 * Iterate over the feature directory and return a map of 285 * File to Feature objects (from the generator bundle) 286 */ getFeatures(File location)287 private Map<File, Feature> getFeatures(File location) { 288 Map<File, Feature> result = new HashMap<>(); 289 File featureDir = new File(location, FEATURES); 290 File[] children = featureDir.listFiles(); 291 for (int i = 0; children != null && i < children.length; i++) { 292 File featureLocation = children[i]; 293 if (featureLocation.isDirectory() && featureLocation.getParentFile() != null && featureLocation.getParentFile().getName().equals("features") && new File(featureLocation, "feature.xml").exists()) {//$NON-NLS-1$ //$NON-NLS-2$ 294 FeatureParser parser = new FeatureParser(); 295 Feature entry = parser.parse(featureLocation); 296 if (entry != null) 297 result.put(featureLocation, entry); 298 } 299 } 300 return result; 301 } 302 303 /* 304 * Iterate over the plugins directory and return a map of 305 * plug-in id/version to File locations. 306 */ getPlugins(File location)307 private Map<String, File> getPlugins(File location) { 308 File[] plugins = new File(location, PLUGINS).listFiles(); 309 Map<String, File> result = new HashMap<>(); 310 for (int i = 0; plugins != null && i < plugins.length; i++) { 311 File bundleLocation = plugins[i]; 312 if (bundleLocation.isDirectory() || bundleLocation.getName().endsWith(".jar")) { //$NON-NLS-1$ 313 BundleDescription description = BundlesAction.createBundleDescriptionIgnoringExceptions(bundleLocation); 314 if (description != null) { 315 String id = description.getSymbolicName(); 316 String version = description.getVersion().toString(); 317 result.put(id + '/' + version, bundleLocation); 318 } 319 } 320 } 321 return result; 322 } 323 324 @Override added(File file)325 public boolean added(File file) { 326 return delegate.added(file); 327 } 328 329 @Override changed(File file)330 public boolean changed(File file) { 331 return delegate.changed(file); 332 } 333 334 @Override getSeenFile(File file)335 public Long getSeenFile(File file) { 336 return delegate.getSeenFile(file); 337 } 338 339 @Override removed(File file)340 public boolean removed(File file) { 341 return delegate.removed(file); 342 } 343 344 @Override startPoll()345 public void startPoll() { 346 delegate.startPoll(); 347 } 348 349 @Override stopPoll()350 public void stopPoll() { 351 delegate.stopPoll(); 352 } 353 } 354