1 /******************************************************************************* 2 * Copyright (c) 2007, 2018 IBM Corporation and others. 3 * 4 * This 5 * program and the accompanying materials are made available under the terms of 6 * the Eclipse Public License 2.0 which accompanies this distribution, and is 7 * available at 8 * https://www.eclipse.org/legal/epl-2.0/ 9 * 10 * SPDX-License-Identifier: EPL-2.0 11 * 12 * Contributors: 13 * IBM Corporation - initial API and implementation 14 * Ericsson AB - ongoing development 15 * Red Hat, Inc. - fragments support added, Bug 460967 16 ******************************************************************************/ 17 package org.eclipse.equinox.internal.p2.engine; 18 19 import java.io.*; 20 import java.lang.ref.SoftReference; 21 import java.net.URI; 22 import java.util.*; 23 import java.util.Map.Entry; 24 import java.util.zip.GZIPInputStream; 25 import java.util.zip.GZIPOutputStream; 26 import javax.xml.parsers.ParserConfigurationException; 27 import org.eclipse.core.runtime.*; 28 import org.eclipse.core.runtime.jobs.Job; 29 import org.eclipse.equinox.internal.p2.core.helpers.*; 30 import org.eclipse.equinox.internal.p2.metadata.TranslationSupport; 31 import org.eclipse.equinox.internal.provisional.p2.core.eventbus.IProvisioningEventBus; 32 import org.eclipse.equinox.p2.core.*; 33 import org.eclipse.equinox.p2.core.spi.IAgentService; 34 import org.eclipse.equinox.p2.engine.*; 35 import org.eclipse.equinox.p2.metadata.*; 36 import org.eclipse.equinox.p2.query.IQueryResult; 37 import org.eclipse.equinox.p2.query.QueryUtil; 38 import org.eclipse.osgi.service.datalocation.Location; 39 import org.eclipse.osgi.util.NLS; 40 import org.osgi.framework.BundleContext; 41 import org.osgi.framework.ServiceReference; 42 import org.xml.sax.InputSource; 43 import org.xml.sax.SAXException; 44 45 public class SimpleProfileRegistry implements IProfileRegistry, IAgentService { 46 47 private static final String SIMPLE_PROFILE_REGISTRY_INTERNAL = "_simpleProfileRegistry_internal_"; //$NON-NLS-1$ 48 private static final String PROFILE_REGISTRY = "profile registry"; //$NON-NLS-1$ 49 private static final String PROFILE_PROPERTIES_FILE = "state.properties"; //$NON-NLS-1$ 50 51 private static final String PROFILE_EXT = ".profile"; //$NON-NLS-1$ 52 private static final String PROFILE_GZ_EXT = ".profile.gz"; //$NON-NLS-1$ 53 public static final String DEFAULT_STORAGE_DIR = "profileRegistry"; //$NON-NLS-1$ 54 private static final String DATA_EXT = ".data"; //$NON-NLS-1$ 55 56 //Internal constant used to keep track of the newly created timestamp 57 private static final String SERVICE_SHARED_INSTALL_NEW_TIMESTAMP = IProfileRegistry.class.getName() + '_' + "NEW_SELF_TIMESTAMP"; //$NON-NLS-1$ 58 59 protected final IProvisioningAgent agent; 60 61 /** 62 * Reference to Map of String(Profile id)->Profile. 63 */ 64 private SoftReference<Map<String, Profile>> profiles; 65 private Map<String, ProfileLock> profileLocks = new HashMap<>(); 66 67 private String self; 68 69 //Whether the registry should update the self profile when the registry is restored 70 private boolean updateSelfProfile; 71 72 private File store; 73 74 ISurrogateProfileHandler surrogateProfileHandler; 75 76 private IProvisioningEventBus eventBus; 77 // cache of last accessed profile state properties 78 private ProfileStateProperties lastAccessedProperties; 79 SimpleProfileRegistry(IProvisioningAgent agent, File registryDirectory)80 public SimpleProfileRegistry(IProvisioningAgent agent, File registryDirectory) { 81 this(agent, registryDirectory, new SurrogateProfileHandler(agent), true); 82 } 83 SimpleProfileRegistry(IProvisioningAgent agent, File registryDirectory, ISurrogateProfileHandler handler, boolean updateSelfProfile)84 public SimpleProfileRegistry(IProvisioningAgent agent, File registryDirectory, ISurrogateProfileHandler handler, boolean updateSelfProfile) { 85 this.agent = agent; 86 store = registryDirectory; 87 surrogateProfileHandler = handler; 88 Assert.isNotNull(store, "Profile registry requires a directory"); //$NON-NLS-1$ 89 findSelf(); 90 this.updateSelfProfile = updateSelfProfile; 91 } 92 93 /** 94 * Determine the id of the "self" profile. This is only applicable for the registry 95 * of the currently running system. 96 */ findSelf()97 private void findSelf() { 98 //the location for the currently running system is registered as a service 99 final BundleContext context = EngineActivator.getContext(); 100 if (context == null) 101 return; 102 ServiceReference<IAgentLocation> ref = context.getServiceReference(IAgentLocation.class); 103 if (ref == null) 104 return; 105 IAgentLocation location = context.getService(ref); 106 if (location == null) 107 return; 108 if (store.equals(getDefaultRegistryDirectory(location))) { 109 //we are the registry for the currently running system 110 self = context.getProperty("eclipse.p2.profile"); //$NON-NLS-1$ 111 } else if (agent.getService(IProvisioningAgent.SHARED_CURRENT_AGENT) != null) { 112 // In shared mode, _SELF_ is the value of the current running profile for both agents current and shared 113 if (((IProvisioningAgent) agent.getService(IProvisioningAgent.SHARED_CURRENT_AGENT)).getService(IProvisioningAgent.SHARED_BASE_AGENT) == agent) { 114 self = context.getProperty("eclipse.p2.profile"); //$NON-NLS-1$ 115 } 116 } 117 if (self == null) 118 self = (String) agent.getService("FORCED_SELF"); //$NON-NLS-1$ 119 context.ungetService(ref); 120 } 121 getDefaultRegistryDirectory(IAgentLocation agent)122 public static File getDefaultRegistryDirectory(IAgentLocation agent) { 123 File registryDirectory = null; 124 if (agent == null) 125 throw new IllegalStateException("Profile Registry inialization failed: Agent Location is not available"); //$NON-NLS-1$ 126 final URI engineDataArea = agent.getDataArea(EngineActivator.ID); 127 URI registryURL = URIUtil.append(engineDataArea, DEFAULT_STORAGE_DIR); 128 registryDirectory = new File(registryURL); 129 registryDirectory.mkdirs(); 130 return registryDirectory; 131 } 132 133 /** 134 * If the current profile for self is marked as a roaming profile, we need 135 * to update its install and bundle pool locations. 136 */ updateSelfProfile(Map<String, Profile> profileMap)137 private void updateSelfProfile(Map<String, Profile> profileMap) { 138 if (profileMap == null) 139 return; 140 Profile selfProfile = profileMap.get(self); 141 if (selfProfile == null) 142 return; 143 144 //register default locale provider where metadata translations are found 145 //TODO ideally this should not be hard-coded to the current profile 146 TranslationSupport.getInstance().setTranslationSource(selfProfile); 147 148 if (DebugHelper.DEBUG_PROFILE_REGISTRY) 149 DebugHelper.debug(PROFILE_REGISTRY, "SimpleProfileRegistry.updateSelfProfile"); //$NON-NLS-1$ 150 boolean changed = false; 151 //only update if self is a roaming profile 152 if (Boolean.parseBoolean(selfProfile.getProperty(IProfile.PROP_ROAMING))) 153 changed = updateRoamingProfile(selfProfile); 154 155 if (changed) 156 saveProfile(selfProfile); 157 } 158 updateRoamingProfile(Profile selfProfile)159 private boolean updateRoamingProfile(Profile selfProfile) { 160 if (DebugHelper.DEBUG_PROFILE_REGISTRY) 161 DebugHelper.debug(PROFILE_REGISTRY, "SimpleProfileRegistry.updateRoamingProfile"); //$NON-NLS-1$ 162 Location installLocation = ServiceHelper.getService(EngineActivator.getContext(), Location.class, Location.INSTALL_FILTER); 163 File location = URLUtil.toFile(installLocation.getURL()); 164 if (location == null) { 165 // fallback: use only path of the URL if the protocol is not 'file' 166 location = new File(installLocation.getURL().getPath()); 167 } 168 boolean changed = false; 169 if (!location.equals(new File(selfProfile.getProperty(IProfile.PROP_INSTALL_FOLDER)))) { 170 selfProfile.setProperty(IProfile.PROP_INSTALL_FOLDER, location.getAbsolutePath()); 171 changed = true; 172 } 173 String propCache = selfProfile.getProperty(IProfile.PROP_CACHE); 174 if (propCache != null && !location.equals(new File(propCache))) { 175 selfProfile.setProperty(IProfile.PROP_CACHE, location.getAbsolutePath()); 176 changed = true; 177 } 178 if (DebugHelper.DEBUG_PROFILE_REGISTRY) 179 DebugHelper.debug(PROFILE_REGISTRY, "SimpleProfileRegistry.updateRoamingProfile(changed=" + changed + ')'); //$NON-NLS-1$ 180 return changed; 181 } 182 183 @Override toString()184 public synchronized String toString() { 185 return "Profile registry for location: " + store.getAbsolutePath() + "\n" + getProfileMap().toString(); //$NON-NLS-1$ //$NON-NLS-2$ 186 } 187 188 @Override getProfile(String id)189 public synchronized IProfile getProfile(String id) { 190 Profile profile = internalGetProfile(id); 191 if (profile == null) 192 return null; 193 return profile.snapshot(); 194 } 195 196 @Override getProfile(String id, long timestamp)197 public synchronized IProfile getProfile(String id, long timestamp) { 198 if (SELF.equals(id)) 199 id = self; 200 201 if (profiles != null) { 202 IProfile profile = getProfile(id); 203 if (profile != null && profile.getTimestamp() == timestamp) 204 return profile; 205 } 206 207 File profileDirectory = getProfileFolder(id); 208 if (!profileDirectory.isDirectory()) 209 return null; 210 211 File profileFile = new File(profileDirectory, Long.toString(timestamp) + PROFILE_GZ_EXT); 212 if (!profileFile.exists()) { 213 profileFile = new File(profileDirectory, Long.toString(timestamp) + PROFILE_EXT); 214 if (!profileFile.exists()) 215 return null; 216 } 217 218 Parser parser = new Parser(EngineActivator.getContext(), EngineActivator.ID); 219 try { 220 parser.parse(profileFile); 221 } catch (IOException e) { 222 LogHelper.log(new Status(IStatus.ERROR, EngineActivator.ID, NLS.bind(Messages.error_parsing_profile, profileFile), e)); 223 } 224 return parser.getProfileMap().get(id); 225 } 226 227 @Override listProfileTimestamps(String id)228 public synchronized long[] listProfileTimestamps(String id) { 229 if (SELF.equals(id)) 230 id = self; 231 //guard against null self profile 232 if (id == null) 233 return new long[0]; 234 235 File profileDirectory = getProfileFolder(id); 236 if (!profileDirectory.isDirectory()) 237 return new long[0]; 238 239 File[] profileFiles = profileDirectory.listFiles((FileFilter) pathname -> (pathname.getName().endsWith(PROFILE_EXT) || pathname.getName().endsWith(PROFILE_GZ_EXT)) && pathname.isFile() && !pathname.getName().startsWith("._")); 240 241 long[] timestamps = new long[profileFiles.length]; 242 for (int i = 0; i < profileFiles.length; i++) { 243 String filename = profileFiles[i].getName(); 244 int extensionIndex = filename.lastIndexOf(PROFILE_EXT); 245 try { 246 timestamps[i] = Long.parseLong(filename.substring(0, extensionIndex)); 247 } catch (NumberFormatException e) { 248 throw new IllegalStateException("Incompatible profile file name. Expected format is {timestamp}" + PROFILE_GZ_EXT + " (or {timestamp}" + PROFILE_EXT + ") but was " + filename + "."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ 249 } 250 } 251 Arrays.sort(timestamps); 252 return timestamps; 253 } 254 255 /** 256 * Returns the profile with the given ID, or {@code null} if no such profile exists. 257 */ internalGetProfile(String id)258 private Profile internalGetProfile(String id) { 259 if (SELF.equals(id)) 260 id = self; 261 Profile profile = getProfileMap().get(id); 262 if (self != null && self.equals(id)) { 263 boolean resetProfile = false; 264 if (profile != null && ignoreExistingProfile(profile)) { 265 internalSetProfileStateProperty(profile, profile.getTimestamp(), IProfile.STATE_PROP_SHARED_INSTALL, IProfile.STATE_SHARED_INSTALL_VALUE_BEFOREFLUSH); 266 profile = null; 267 resetProfile = true; 268 } 269 if (profile == null) { 270 profile = createSurrogateProfile(id); 271 if (profile == null) 272 return null; 273 274 if (resetProfile) { 275 //Now that we created a new profile. Tag it, override the property and register the timestamp in the agent registry for pickup by other 276 internalSetProfileStateProperty(profile, profile.getTimestamp(), IProfile.STATE_PROP_SHARED_INSTALL, IProfile.STATE_SHARED_INSTALL_VALUE_NEW); 277 internalSetProfileStateProperty(profile, profile.getTimestamp(), SIMPLE_PROFILE_REGISTRY_INTERNAL + getBaseTimestamp(profile.getProfileId()), getBaseTimestamp(id)); 278 //fragments support - remeber the property 279 internalSetProfileStateProperty(profile, profile.getTimestamp(), SIMPLE_PROFILE_REGISTRY_INTERNAL + getExtTimeStamp(), getExtTimeStamp()); 280 agent.registerService(SERVICE_SHARED_INSTALL_NEW_TIMESTAMP, Long.toString(profile.getTimestamp())); 281 } else { 282 //This is the first time we create the shared profile. Tag it as such and also remember the timestamp of the base 283 internalSetProfileStateProperty(profile, profile.getTimestamp(), IProfile.STATE_PROP_SHARED_INSTALL, IProfile.STATE_SHARED_INSTALL_VALUE_INITIAL); 284 String baseTimestamp = getBaseTimestamp(id); 285 if (baseTimestamp != null) 286 internalSetProfileStateProperty(profile, profile.getTimestamp(), SIMPLE_PROFILE_REGISTRY_INTERNAL + baseTimestamp, baseTimestamp); 287 String extTimestamp = getExtTimeStamp(); 288 internalSetProfileStateProperty(profile, profile.getTimestamp(), SIMPLE_PROFILE_REGISTRY_INTERNAL + extTimestamp, extTimestamp); 289 } 290 } 291 } 292 return profile; 293 } 294 295 // get timestamp of fragments (extensions) getExtTimeStamp()296 private String getExtTimeStamp() { 297 long result = -1; 298 if (!EngineActivator.EXTENDED) { 299 return Long.toString(result); 300 } 301 File[] extensions = EngineActivator.getExtensionsDirectories(); 302 for (File extension : extensions) { 303 if (extension.lastModified() > result) { 304 result = extension.lastModified(); 305 } 306 } 307 return Long.toString(result); 308 } 309 ignoreExistingProfile(IProfile profile)310 private boolean ignoreExistingProfile(IProfile profile) { 311 if (agent.getService(SERVICE_SHARED_INSTALL_NEW_TIMESTAMP) != null) 312 return false; 313 314 String baseTimestamp = getBaseTimestamp(profile.getProfileId()); 315 String extTimestamp = getExtTimeStamp(); 316 if (baseTimestamp == null) { 317 return false; 318 } 319 320 boolean extensionOK = true; 321 if (surrogateProfileHandler != null && surrogateProfileHandler.isSurrogate(profile)) { 322 extensionOK = (internalGetProfileStateProperties(profile, SIMPLE_PROFILE_REGISTRY_INTERNAL + extTimestamp, false).size() != 0); 323 } 324 325 if ((internalGetProfileStateProperties(profile, SIMPLE_PROFILE_REGISTRY_INTERNAL + baseTimestamp, false).size() != 0) && extensionOK) 326 return false; 327 328 return true; 329 } 330 getBaseTimestamp(String id)331 private String getBaseTimestamp(String id) { 332 IProvisioningAgent baseAgent = (IProvisioningAgent) agent.getService(IProvisioningAgent.SHARED_BASE_AGENT); 333 if (baseAgent == null) 334 return null; 335 IProfileRegistry registry = baseAgent.getService(IProfileRegistry.class); 336 if (registry == null) 337 return null; 338 long[] revisions = registry.listProfileTimestamps(id); 339 if (revisions.length >= 1) { 340 return Long.toString(revisions[revisions.length - 1]); 341 } 342 return null; 343 } 344 createSurrogateProfile(String id)345 private Profile createSurrogateProfile(String id) { 346 if (surrogateProfileHandler == null) 347 return null; 348 349 Profile profile = (Profile) surrogateProfileHandler.createProfile(id); 350 if (profile == null) 351 return null; 352 353 saveProfile(profile); 354 resetProfiles(); 355 return getProfileMap().get(id); 356 } 357 358 @Override getProfiles()359 public synchronized IProfile[] getProfiles() { 360 Map<String, Profile> profileMap = getProfileMap(); 361 Profile[] result = new Profile[profileMap.size()]; 362 int i = 0; 363 for (Profile profile : profileMap.values()) { 364 result[i++] = profile.snapshot(); 365 } 366 return result; 367 } 368 369 /** 370 * Returns an initialized map of String(Profile id)->Profile. 371 */ getProfileMap()372 protected Map<String, Profile> getProfileMap() { 373 if (profiles != null) { 374 Map<String, Profile> result = profiles.get(); 375 if (result != null) 376 return result; 377 } 378 Map<String, Profile> result = restore(); 379 if (result == null) 380 result = new LinkedHashMap<>(8); 381 profiles = new SoftReference<>(result); 382 if (updateSelfProfile) { 383 //update self profile on first load 384 updateSelfProfile(result); 385 } 386 return result; 387 } 388 updateProfile(Profile profile)389 public synchronized void updateProfile(Profile profile) { 390 String id = profile.getProfileId(); 391 Profile current = getProfileMap().get(id); 392 if (current == null) 393 throw new IllegalArgumentException(NLS.bind(Messages.profile_does_not_exist, id)); 394 395 ProfileLock lock = profileLocks.get(id); 396 lock.checkLocked(); 397 398 current.clearLocalProperties(); 399 current.clearInstallableUnits(); 400 401 current.addProperties(profile.getLocalProperties()); 402 IQueryResult<IInstallableUnit> queryResult = profile.query(QueryUtil.createIUAnyQuery(), null); 403 for (IInstallableUnit iu : queryResult) { 404 current.addInstallableUnit(iu); 405 Map<String, String> iuProperties = profile.getInstallableUnitProperties(iu); 406 if (iuProperties != null) 407 current.addInstallableUnitProperties(iu, iuProperties); 408 } 409 saveProfile(current); 410 profile.clearOrphanedInstallableUnitProperties(); 411 profile.setTimestamp(current.getTimestamp()); 412 broadcastChangeEvent(id, IProfileEvent.CHANGED); 413 } 414 415 @Override addProfile(String id)416 public IProfile addProfile(String id) throws ProvisionException { 417 return addProfile(id, null, null); 418 } 419 420 @Override addProfile(String id, Map<String, String> profileProperties)421 public IProfile addProfile(String id, Map<String, String> profileProperties) throws ProvisionException { 422 return addProfile(id, profileProperties, null); 423 } 424 addProfile(String id, Map<String, String> profileProperties, String parentId)425 public synchronized IProfile addProfile(String id, Map<String, String> profileProperties, String parentId) throws ProvisionException { 426 if (SELF.equals(id)) 427 id = self; 428 Map<String, Profile> profileMap = getProfileMap(); 429 if (profileMap.get(id) != null) 430 throw new ProvisionException(NLS.bind(Messages.Profile_Duplicate_Root_Profile_Id, id)); 431 432 Profile parent = null; 433 if (parentId != null) { 434 if (SELF.equals(parentId)) 435 parentId = self; 436 parent = profileMap.get(parentId); 437 if (parent == null) 438 throw new ProvisionException(NLS.bind(Messages.Profile_Parent_Not_Found, parentId)); 439 } 440 441 Profile profile = new Profile(agent, id, parent, profileProperties); 442 if (surrogateProfileHandler != null && surrogateProfileHandler.isSurrogate(profile)) 443 profile.setSurrogateProfileHandler(surrogateProfileHandler); 444 profileMap.put(id, profile); 445 saveProfile(profile); 446 broadcastChangeEvent(id, IProfileEvent.ADDED); 447 return profile.snapshot(); 448 } 449 450 @Override removeProfile(String profileId)451 public synchronized void removeProfile(String profileId) { 452 if (SELF.equals(profileId)) 453 profileId = self; 454 //note we need to maintain a reference to the profile map until it is persisted to prevent gc 455 Map<String, Profile> profileMap = getProfileMap(); 456 Profile profile = profileMap.get(profileId); 457 if (profile == null) 458 return; 459 460 List<String> subProfileIds = profile.getSubProfileIds(); 461 for (String subProfileId : subProfileIds) { 462 removeProfile(subProfileId); 463 } 464 internalLockProfile(profile); 465 // The above call recursively locked the parent(s). So save it away to rewind the locking process. 466 IProfile savedParent = profile.getParentProfile(); 467 try { 468 profile.setParent(null); 469 } finally { 470 internalUnlockProfile(profile); 471 // The above call will not recurse since parent is now null. So do it explicitly. 472 if (savedParent != null) { 473 internalUnlockProfile(savedParent); 474 } 475 } 476 profileMap.remove(profileId); 477 profileLocks.remove(profileId); 478 // deleting the profile removes the folder and subsequently all 479 // the profile state properties as well since they are stored in a file in the folder. 480 deleteProfile(profileId); 481 broadcastChangeEvent(profileId, IProfileEvent.REMOVED); 482 } 483 484 @Override removeProfile(String id, long timestamp)485 public synchronized void removeProfile(String id, long timestamp) throws ProvisionException { 486 if (SELF.equals(id)) 487 id = self; 488 489 if (profiles != null) { 490 IProfile profile = getProfile(id); 491 if (profile != null && profile.getTimestamp() == timestamp) 492 throw new ProvisionException(Messages.SimpleProfileRegistry_CannotRemoveCurrentSnapshot); 493 } 494 495 File profileDirectory = getProfileFolder(id); 496 if (!profileDirectory.isDirectory()) 497 return; 498 499 File profileFile = new File(profileDirectory, Long.toString(timestamp) + PROFILE_GZ_EXT); 500 if (!profileFile.exists()) { 501 profileFile = new File(profileDirectory, Long.toString(timestamp) + PROFILE_EXT); 502 if (!profileFile.exists()) 503 return; 504 } 505 FileUtils.deleteAll(profileFile); 506 // Ignore the return value here. If there was a problem removing the profile state 507 // properties we don't want to fail the whole operation since the profile state itself 508 // was removed successfully 509 removeProfileStateProperties(id, timestamp, null); 510 } 511 broadcastChangeEvent(String profileId, int reason)512 private void broadcastChangeEvent(String profileId, int reason) { 513 if (eventBus != null) 514 eventBus.publishEvent(new ProfileEvent(profileId, reason)); 515 } 516 517 /** 518 * Restores the profile registry from disk, and returns the loaded profile map. 519 * Returns <code>null</code> if unable to read the registry. 520 */ restore()521 private Map<String, Profile> restore() { 522 if (store == null || !store.isDirectory()) 523 throw new IllegalStateException(NLS.bind(Messages.reg_dir_not_available, store)); 524 525 Parser parser = new Parser(EngineActivator.getContext(), EngineActivator.ID); 526 File[] profileDirectories = store.listFiles((FileFilter) pathname -> pathname.getName().endsWith(PROFILE_EXT) && pathname.isDirectory()); 527 // protect against NPE 528 if (profileDirectories == null) { 529 parser.getProfileMap(); 530 } 531 for (File profileDirectorie : profileDirectories) { 532 String directoryName = profileDirectorie.getName(); 533 String profileId = unescape(directoryName.substring(0, directoryName.lastIndexOf(PROFILE_EXT))); 534 ProfileLock lock = profileLocks.get(profileId); 535 if (lock == null) { 536 lock = new ProfileLock(this, profileDirectorie); 537 profileLocks.put(profileId, lock); 538 } 539 540 boolean locked = false; 541 if (lock.processHoldsLock() || (locked = lock.lock())) { 542 try { 543 File profileFile = findLatestProfileFile(profileDirectorie); 544 if (profileFile != null) { 545 try { 546 parser.parse(profileFile); 547 } catch (IOException e) { 548 LogHelper.log(new Status(IStatus.ERROR, EngineActivator.ID, NLS.bind(Messages.error_parsing_profile, profileFile), e)); 549 } 550 } 551 } finally { 552 if (locked) 553 lock.unlock(); 554 } 555 } else { 556 // could not lock the profile, so add a place holder 557 parser.addProfilePlaceHolder(profileId); 558 } 559 } 560 return parser.getProfileMap(); 561 } 562 findLatestProfileFile(File profileDirectory)563 private File findLatestProfileFile(File profileDirectory) { 564 File latest = null; 565 long latestTimestamp = 0; 566 File[] profileFiles = profileDirectory.listFiles((FileFilter) pathname -> (pathname.getName().endsWith(PROFILE_GZ_EXT) || pathname.getName().endsWith(PROFILE_EXT)) && !pathname.isDirectory()); 567 // protect against NPE 568 if (profileFiles == null) 569 return null; 570 for (File profileFile : profileFiles) { 571 String fileName = profileFile.getName(); 572 try { 573 long timestamp = Long.parseLong(fileName.substring(0, fileName.indexOf(PROFILE_EXT))); 574 if (timestamp > latestTimestamp) { 575 latestTimestamp = timestamp; 576 latest = profileFile; 577 } 578 } catch (NumberFormatException e) { 579 // ignore 580 } 581 } 582 return latest; 583 } 584 saveProfile(Profile profile)585 private void saveProfile(Profile profile) { 586 File profileDirectory = getProfileFolder(profile.getProfileId()); 587 profileDirectory.mkdir(); 588 589 long previousTimestamp = profile.getTimestamp(); 590 long currentTimestamp = System.currentTimeMillis(); 591 if (currentTimestamp <= previousTimestamp) 592 currentTimestamp = previousTimestamp + 1; 593 boolean shouldGzipFile = shouldGzipFile(profile); 594 File profileFile = new File(profileDirectory, Long.toString(currentTimestamp) + (shouldGzipFile ? PROFILE_GZ_EXT : PROFILE_EXT)); 595 596 // Log a stack trace to see who is writing the profile. 597 if (DebugHelper.DEBUG_PROFILE_REGISTRY) 598 DebugHelper.debug(PROFILE_REGISTRY, "Saving profile to: " + profileFile.getAbsolutePath()); //$NON-NLS-1$ 599 600 profile.setTimestamp(currentTimestamp); 601 profile.setChanged(false); 602 OutputStream os = null; 603 try { 604 if (shouldGzipFile) 605 os = new BufferedOutputStream(new GZIPOutputStream(new FileOutputStream(profileFile))); 606 else 607 os = new BufferedOutputStream(new FileOutputStream(profileFile)); 608 Writer writer = new Writer(os); 609 writer.writeProfile(profile); 610 } catch (IOException e) { 611 profile.setTimestamp(previousTimestamp); 612 profileFile.delete(); 613 LogHelper.log(new Status(IStatus.ERROR, EngineActivator.ID, NLS.bind(Messages.error_persisting_profile, profile.getProfileId()), e)); 614 } finally { 615 try { 616 if (os != null) 617 os.close(); 618 } catch (IOException e) { 619 // ignore 620 } 621 } 622 } 623 setEventBus(IProvisioningEventBus bus)624 public void setEventBus(IProvisioningEventBus bus) { 625 this.eventBus = bus; 626 } 627 628 /** 629 * Returns whether the profile file for the given profile should be written in gzip format. 630 */ shouldGzipFile(Profile profile)631 private boolean shouldGzipFile(Profile profile) { 632 //check system property controlling compression 633 String format = EngineActivator.getContext().getProperty(EngineActivator.PROP_PROFILE_FORMAT); 634 if (format != null && format.equals(EngineActivator.PROFILE_FORMAT_UNCOMPRESSED)) 635 return false; 636 637 //check whether the profile contains the p2 engine from 3.5.0 or earlier 638 return profile.available(QueryUtil.createIUQuery("org.eclipse.equinox.p2.engine", VersionRange.create("[0.0.0, 1.0.101)")), null).isEmpty(); //$NON-NLS-1$//$NON-NLS-2$ 639 } 640 deleteProfile(String profileId)641 private void deleteProfile(String profileId) { 642 File profileDirectory = getProfileFolder(profileId); 643 FileUtils.deleteAll(profileDirectory); 644 } 645 646 /** 647 * Converts a profile id into a string that can be used as a file name in any file system. 648 */ escape(String toEscape)649 public static String escape(String toEscape) { 650 StringBuilder buffer = new StringBuilder(); 651 int length = toEscape.length(); 652 for (int i = 0; i < length; ++i) { 653 char ch = toEscape.charAt(i); 654 switch (ch) { 655 case '\\' : 656 case '/' : 657 case ':' : 658 case '*' : 659 case '?' : 660 case '"' : 661 case '<' : 662 case '>' : 663 case '|' : 664 case '%' : 665 buffer.append("%" + (int) ch + ";"); //$NON-NLS-1$ //$NON-NLS-2$ 666 break; 667 default : 668 buffer.append(ch); 669 } 670 } 671 return buffer.toString(); 672 } 673 unescape(String text)674 public static String unescape(String text) { 675 if (text.indexOf('%') == -1) 676 return text; 677 678 StringBuilder buffer = new StringBuilder(); 679 int length = text.length(); 680 for (int i = 0; i < length; ++i) { 681 char ch = text.charAt(i); 682 if (ch == '%') { 683 int colon = text.indexOf(';', i); 684 if (colon == -1) 685 throw new IllegalStateException("error unescaping the sequence at character (" + i + ") for " + text + ". Expected %{int};."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ 686 ch = (char) Integer.parseInt(text.substring(i + 1, colon)); 687 i = colon; 688 } 689 buffer.append(ch); 690 } 691 return buffer.toString(); 692 } 693 694 static class Writer extends ProfileWriter { 695 Writer(OutputStream output)696 public Writer(OutputStream output) { 697 super(output, new ProcessingInstruction[] {ProcessingInstruction.makeTargetVersionInstruction(PROFILE_TARGET, ProfileXMLConstants.CURRENT_VERSION)}); 698 } 699 } 700 701 /* 702 * Parser for the contents of a SimpleProfileRegistry, 703 * as written by the Writer class. 704 */ 705 class Parser extends ProfileParser { 706 private final Map<String, ProfileHandler> profileHandlers = new HashMap<>(); 707 getProfileHandlers()708 public Map<String, ProfileHandler> getProfileHandlers() { 709 return Collections.unmodifiableMap(profileHandlers); 710 } 711 Parser(BundleContext context, String bundleId)712 public Parser(BundleContext context, String bundleId) { 713 super(context, bundleId); 714 } 715 addProfilePlaceHolder(String profileId)716 public void addProfilePlaceHolder(String profileId) { 717 profileHandlers.put(profileId, new ProfileHandler(profileId)); 718 } 719 parse(File file)720 public void parse(File file) throws IOException { 721 InputStream is; 722 if (file.getName().endsWith(PROFILE_GZ_EXT)) { 723 is = new BufferedInputStream(new GZIPInputStream(new FileInputStream(file))); 724 } else { // backward compatibility. SimpleProfileRegistry doesn't write non-gzipped profiles any more. 725 is = new BufferedInputStream(new FileInputStream(file)); 726 } 727 parse(is); 728 } 729 parse(InputStream stream)730 public synchronized void parse(InputStream stream) throws IOException { 731 this.status = null; 732 try { 733 // TODO: currently not caching the parser since we make no assumptions 734 // or restrictions on concurrent parsing 735 getParser(); 736 ProfileHandler profileHandler = new ProfileHandler(); 737 xmlReader.setContentHandler(new ProfileDocHandler(PROFILE_ELEMENT, profileHandler)); 738 xmlReader.parse(new InputSource(stream)); 739 profileHandlers.put(profileHandler.getProfileId(), profileHandler); 740 } catch (SAXException e) { 741 IOException ioException = new IOException(e.getMessage()); 742 ioException.initCause(e); 743 throw ioException; 744 } catch (ParserConfigurationException e) { 745 IOException ioException = new IOException(e.getMessage()); 746 ioException.initCause(e); 747 throw ioException; 748 } finally { 749 stream.close(); 750 } 751 } 752 753 @Override getRootObject()754 protected Object getRootObject() { 755 return this; 756 } 757 getProfileMap()758 public Map<String, Profile> getProfileMap() { 759 Map<String, Profile> profileMap = new HashMap<>(); 760 for (String profileId : profileHandlers.keySet()) { 761 addProfile(profileId, profileMap); 762 } 763 return profileMap; 764 } 765 addProfile(String profileId, Map<String, Profile> profileMap)766 private void addProfile(String profileId, Map<String, Profile> profileMap) { 767 if (profileMap.containsKey(profileId)) 768 return; 769 770 ProfileHandler profileHandler = profileHandlers.get(profileId); 771 Profile parentProfile = null; 772 773 String parentId = profileHandler.getParentId(); 774 if (parentId != null) { 775 addProfile(parentId, profileMap); 776 parentProfile = profileMap.get(parentId); 777 } 778 779 Profile profile = new Profile(agent, profileId, parentProfile, profileHandler.getProperties()); 780 if (surrogateProfileHandler != null && surrogateProfileHandler.isSurrogate(profile)) 781 profile.setSurrogateProfileHandler(surrogateProfileHandler); 782 783 profile.setTimestamp(profileHandler.getTimestamp()); 784 785 IInstallableUnit[] ius = profileHandler.getInstallableUnits(); 786 if (ius != null) { 787 for (IInstallableUnit iu : ius) { 788 profile.addInstallableUnit(iu); 789 Map<String, String> iuProperties = profileHandler.getIUProperties(iu); 790 if (iuProperties != null) { 791 for (Entry<String, String> entry : iuProperties.entrySet()) { 792 profile.setInstallableUnitProperty(iu, entry.getKey(), entry.getValue()); 793 } 794 } 795 } 796 } 797 profile.setChanged(false); 798 profileMap.put(profileId, profile); 799 } 800 801 private final class ProfileDocHandler extends DocHandler { 802 ProfileDocHandler(String rootName, RootHandler rootHandler)803 public ProfileDocHandler(String rootName, RootHandler rootHandler) { 804 super(rootName, rootHandler); 805 } 806 807 @Override processingInstruction(String target, String data)808 public void processingInstruction(String target, String data) throws SAXException { 809 if (ProfileXMLConstants.PROFILE_TARGET.equals(target)) { 810 Version repositoryVersion = extractPIVersion(target, data); 811 if (!ProfileXMLConstants.XML_TOLERANCE.isIncluded(repositoryVersion)) { 812 throw new SAXException(NLS.bind(Messages.SimpleProfileRegistry_Parser_Has_Incompatible_Version, repositoryVersion, ProfileXMLConstants.XML_TOLERANCE)); 813 } 814 } 815 } 816 } 817 818 @Override getErrorMessage()819 protected String getErrorMessage() { 820 return Messages.SimpleProfileRegistry_Parser_Error_Parsing_Registry; 821 } 822 823 @Override toString()824 public String toString() { 825 // TODO: 826 return null; 827 } 828 829 } 830 831 @Override isCurrent(IProfile profile)832 public synchronized boolean isCurrent(IProfile profile) { 833 Profile internalProfile = getProfileMap().get(profile.getProfileId()); 834 if (internalProfile == null) 835 throw new IllegalArgumentException(NLS.bind(Messages.profile_not_registered, profile.getProfileId())); 836 837 if (!internalLockProfile(internalProfile)) 838 throw new IllegalStateException(Messages.SimpleProfileRegistry_Profile_in_use); 839 840 try { 841 return (!((Profile) profile).isChanged() && checkTimestamps(profile, internalProfile)); 842 } finally { 843 internalUnlockProfile(internalProfile); 844 } 845 } 846 lockProfile(Profile profile)847 public synchronized void lockProfile(Profile profile) { 848 Profile internalProfile = internalGetProfile(profile.getProfileId()); 849 if (internalProfile == null) 850 throw new IllegalArgumentException(NLS.bind(Messages.profile_not_registered, profile.getProfileId())); 851 852 if (!internalLockProfile(internalProfile)) 853 throw new IllegalStateException(Messages.SimpleProfileRegistry_Profile_in_use); 854 855 boolean isCurrent = false; 856 try { 857 if (profile.isChanged()) { 858 if (DebugHelper.DEBUG_PROFILE_REGISTRY) 859 DebugHelper.debug(PROFILE_REGISTRY, "Profile is marked as changed."); //$NON-NLS-1$ 860 throw new IllegalStateException(NLS.bind(Messages.profile_changed, profile.getProfileId())); 861 } 862 if (!checkTimestamps(profile, internalProfile)) { 863 if (DebugHelper.DEBUG_PROFILE_REGISTRY) 864 DebugHelper.debug(PROFILE_REGISTRY, "Unexpected timestamp difference in profile."); //$NON-NLS-1$ 865 throw new IllegalStateException(NLS.bind(Messages.profile_not_current, new String[] {profile.getProfileId(), Long.toString(internalProfile.getTimestamp()), Long.toString(profile.getTimestamp())})); 866 } 867 isCurrent = true; 868 } finally { 869 // this check is done here to ensure we unlock even if a runtime exception is thrown 870 if (!isCurrent) 871 internalUnlockProfile(internalProfile); 872 } 873 } 874 internalLockProfile(IProfile profile)875 private boolean internalLockProfile(IProfile profile) { 876 ProfileLock lock = profileLocks.get(profile.getProfileId()); 877 if (lock == null) { 878 lock = new ProfileLock(this, getProfileFolder(profile.getProfileId())); 879 profileLocks.put(profile.getProfileId(), lock); 880 } 881 return lock.lock(); 882 } 883 checkTimestamps(IProfile profile, IProfile internalProfile)884 private boolean checkTimestamps(IProfile profile, IProfile internalProfile) { 885 long[] timestamps = listProfileTimestamps(profile.getProfileId()); 886 if (timestamps.length == 0) { 887 if (DebugHelper.DEBUG_PROFILE_REGISTRY) 888 DebugHelper.debug(PROFILE_REGISTRY, "check timestamp: expected " + profile.getTimestamp() + " but no profiles were found"); //$NON-NLS-1$ //$NON-NLS-2$ 889 resetProfiles(); 890 return false; 891 } 892 893 long currentTimestamp = (timestamps.length == 0) ? -1 : timestamps[timestamps.length - 1]; 894 if (profile.getTimestamp() != currentTimestamp) { 895 if (DebugHelper.DEBUG_PROFILE_REGISTRY) 896 DebugHelper.debug(PROFILE_REGISTRY, "check timestamp: expected " + profile.getTimestamp() + " but was " + currentTimestamp); //$NON-NLS-1$ //$NON-NLS-2$ 897 if (internalProfile.getTimestamp() != currentTimestamp) 898 resetProfiles(); 899 return false; 900 } 901 902 return true; 903 } 904 905 @Override containsProfile(String id)906 public synchronized boolean containsProfile(String id) { 907 if (SELF.equals(id)) 908 id = self; 909 //null check done after self check, because self can be null 910 if (id == null) 911 return false; 912 913 // check profiles to avoid restoring the profile registry 914 if (profiles != null) 915 if (getProfile(id) != null) 916 return true; 917 918 File profileDirectory = getProfileFolder(id); 919 if (!profileDirectory.isDirectory()) 920 return false; 921 File[] profileFiles = profileDirectory.listFiles((FileFilter) pathname -> (pathname.getName().endsWith(PROFILE_GZ_EXT) || pathname.getName().endsWith(PROFILE_EXT)) && pathname.isFile()); 922 return profileFiles.length > 0; 923 } 924 resetProfiles()925 public synchronized void resetProfiles() { 926 profiles = null; 927 } 928 unlockProfile(IProfile profile)929 public synchronized void unlockProfile(IProfile profile) { 930 if (profile == null) 931 throw new IllegalArgumentException(NLS.bind(Messages.profile_not_registered, "")); //$NON-NLS-1$ 932 internalUnlockProfile(profile); 933 } 934 internalUnlockProfile(IProfile profile)935 private void internalUnlockProfile(IProfile profile) { 936 ProfileLock lock = profileLocks.get(profile.getProfileId()); 937 lock.unlock(); 938 } 939 validate(IProfile candidate)940 public Profile validate(IProfile candidate) { 941 if (candidate instanceof Profile) 942 return (Profile) candidate; 943 944 throw new IllegalArgumentException("Profile incompatible: expected " + Profile.class.getName() + " but was " + ((candidate != null) ? candidate.getClass().getName() : "null") + "."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ 945 } 946 getProfileDataDirectory(String id)947 public synchronized File getProfileDataDirectory(String id) { 948 if (SELF.equals(id)) 949 id = self; 950 File profileDirectory = getProfileFolder(id); 951 File profileDataArea = new File(profileDirectory, DATA_EXT); 952 if (!profileDataArea.isDirectory() && !profileDataArea.mkdir()) 953 throw new IllegalStateException("Could not create profile data area " + profileDataArea.getAbsolutePath() + "for: " + id); //$NON-NLS-1$ //$NON-NLS-2$ 954 return profileDataArea; 955 } 956 957 @Override start()958 public void start() { 959 //nothing to do 960 } 961 962 @Override stop()963 public void stop() { 964 try { 965 //ensure there are no more profile preference save jobs running 966 Job.getJobManager().join(ProfilePreferences.PROFILE_SAVE_JOB_FAMILY, null); 967 } catch (InterruptedException e) { 968 //ignore 969 } 970 } 971 972 // Class representing a particular instance of a profile's state properties. 973 // Can be used for caching. 974 class ProfileStateProperties { 975 private String id; 976 private File file; 977 private long timestamp; 978 private Properties properties; 979 ProfileStateProperties(String id, File file, Properties properties)980 ProfileStateProperties(String id, File file, Properties properties) { 981 this.id = id; 982 this.file = file; 983 this.properties = properties; 984 this.timestamp = file.lastModified(); 985 } 986 987 // return true if the cached timestamp is the same as the one on disk isCurrent()988 boolean isCurrent() { 989 if (!file.exists()) 990 return true; 991 return file.lastModified() == timestamp; 992 } 993 getId()994 String getId() { 995 return id; 996 } 997 getProperties()998 Properties getProperties() { 999 return this.properties; 1000 } 1001 } 1002 1003 /* 1004 * Return the folder on disk associated with the profile with the given identifier. 1005 */ getProfileFolder(String id)1006 private File getProfileFolder(String id) { 1007 return new File(store, escape(id) + PROFILE_EXT); 1008 } 1009 1010 /* 1011 * Read and return the state properties for the profile with the given id. 1012 * If one does not exist, then return an empty Properties file. 1013 * If there were problems reading the file then return throw an exception. 1014 */ readStateProperties(String id)1015 private Properties readStateProperties(String id) throws ProvisionException { 1016 if (SELF.equals(id)) 1017 id = self; 1018 1019 // if the last cached value is the one we are interested in and up-to-date 1020 // then don't bother reading from disk 1021 if (lastAccessedProperties != null && id.equals(lastAccessedProperties.getId()) && lastAccessedProperties.isCurrent()) 1022 return lastAccessedProperties.getProperties(); 1023 1024 File profileDirectory = getProfileFolder(id); 1025 if (!profileDirectory.isDirectory()) 1026 throw new ProvisionException(new Status(IStatus.ERROR, EngineActivator.ID, NLS.bind(Messages.SimpleProfileRegistry_Bad_profile_location, profileDirectory.getPath()))); 1027 1028 File file = new File(profileDirectory, PROFILE_PROPERTIES_FILE); 1029 Properties properties = new Properties(); 1030 if (!file.exists()) { 1031 lastAccessedProperties = new ProfileStateProperties(id, file, properties); 1032 return properties; 1033 } 1034 try (InputStream input = new BufferedInputStream(new FileInputStream(file))) { 1035 properties.load(input); 1036 } catch (IOException e) { 1037 throw new ProvisionException(new Status(IStatus.ERROR, EngineActivator.ID, Messages.SimpleProfileRegistry_States_Error_Reading_File, e)); 1038 } 1039 1040 //cache the value before we return 1041 lastAccessedProperties = new ProfileStateProperties(id, file, properties); 1042 return properties; 1043 } 1044 1045 /* 1046 * Write the given state properties to disk for the specified profile. 1047 */ writeStateProperties(String id, Properties properties)1048 private IStatus writeStateProperties(String id, Properties properties) { 1049 if (SELF.equals(id)) 1050 id = self; 1051 1052 File profileDirectory = getProfileFolder(id); 1053 File file = new File(profileDirectory, PROFILE_PROPERTIES_FILE); 1054 Properties prunedProperties = properties; 1055 try (OutputStream output = new BufferedOutputStream(new FileOutputStream(file));) { 1056 prunedProperties = pruneStateProperties(id, properties); 1057 prunedProperties.store(output, null); 1058 output.flush(); 1059 } catch (IOException e) { 1060 return new Status(IStatus.ERROR, EngineActivator.ID, Messages.SimpleProfileRegistry_States_Error_Writing_File, e); 1061 } 1062 // cache the value 1063 lastAccessedProperties = new ProfileStateProperties(id, file, prunedProperties); 1064 return Status.OK_STATUS; 1065 } 1066 1067 // Only write state properties for state timestamps that still exist 1068 // TODO: Do we want to expose this method as API? 1069 // TODO: Do we want to run this method on every write or just after specific elapsed times since the last Prune? pruneStateProperties(String id, Properties properties)1070 private Properties pruneStateProperties(String id, Properties properties) { 1071 Properties result = new Properties(); 1072 long[] timestamps = listProfileTimestamps(id); 1073 HashSet<String> timestampsSet = new HashSet<>(timestamps.length); 1074 for (long timestamp : timestamps) { 1075 timestampsSet.add(String.valueOf(timestamp)); 1076 } 1077 1078 Enumeration<Object> keys = properties.keys(); 1079 while (keys.hasMoreElements()) { 1080 String key = (String) keys.nextElement(); 1081 int index = key.indexOf('.'); 1082 if (index > -1) { 1083 String timestamp = key.substring(0, index); 1084 if (timestampsSet.contains(timestamp)) { 1085 result.put(key, properties.get(key)); 1086 } 1087 } 1088 } 1089 return result; 1090 } 1091 1092 /* 1093 * Ensure a profile with the given identifier has a state with the specified timestamp. Return 1094 * a status object indicating success or failure. 1095 */ validateState(String id, long timestamp)1096 private IStatus validateState(String id, long timestamp) { 1097 long[] states = listProfileTimestamps(id); 1098 for (long ts : states) 1099 if (ts == timestamp) 1100 return Status.OK_STATUS; 1101 return new Status(IStatus.ERROR, EngineActivator.ID, (NLS.bind(Messages.SimpleProfileRegistry_state_not_found, timestamp, id))); 1102 } 1103 1104 @Override setProfileStateProperties(String id, long timestamp, Map<String, String> propertiesToAdd)1105 public IStatus setProfileStateProperties(String id, long timestamp, Map<String, String> propertiesToAdd) { 1106 if (id == null || propertiesToAdd == null) 1107 throw new NullPointerException(); 1108 1109 Profile internalProfile = internalGetProfile(id); 1110 if (internalProfile == null) 1111 throw new IllegalArgumentException(id); 1112 return internalSetProfileStateProperties(internalProfile, timestamp, propertiesToAdd); 1113 } 1114 internalSetProfileStateProperties(IProfile profile, long timestamp, Map<String, String> propertiesToAdd)1115 private IStatus internalSetProfileStateProperties(IProfile profile, long timestamp, Map<String, String> propertiesToAdd) { 1116 IStatus result = validateState(profile.getProfileId(), timestamp); 1117 if (!result.isOK()) 1118 return result; 1119 1120 if (!internalLockProfile(profile)) 1121 throw new IllegalStateException(Messages.SimpleProfileRegistry_Profile_in_use); 1122 1123 try { 1124 Properties properties = readStateProperties(profile.getProfileId()); 1125 for (Map.Entry<String, String> entry : propertiesToAdd.entrySet()) { 1126 // property key format is timestamp.key 1127 properties.put(timestamp + "." + entry.getKey(), entry.getValue()); //$NON-NLS-1$ 1128 } 1129 writeStateProperties(profile.getProfileId(), properties); 1130 } catch (ProvisionException e) { 1131 return e.getStatus(); 1132 } finally { 1133 internalUnlockProfile(profile); 1134 } 1135 return Status.OK_STATUS; 1136 } 1137 1138 @Override setProfileStateProperty(String id, long timestamp, String key, String value)1139 public IStatus setProfileStateProperty(String id, long timestamp, String key, String value) { 1140 if (id == null) 1141 throw new NullPointerException(); 1142 Profile internalProfile = internalGetProfile(id); 1143 if (internalProfile == null) 1144 throw new IllegalArgumentException(id); 1145 return internalSetProfileStateProperty(internalProfile, timestamp, key, value); 1146 } 1147 internalSetProfileStateProperty(IProfile profile, long timestamp, String key, String value)1148 private IStatus internalSetProfileStateProperty(IProfile profile, long timestamp, String key, String value) { 1149 if (key == null || value == null) 1150 throw new NullPointerException(); 1151 Map<String, String> properties = new HashMap<>(); 1152 properties.put(key, value); 1153 1154 return internalSetProfileStateProperties(profile, timestamp, properties); 1155 } 1156 1157 @Override getProfileStateProperties(String id, long timestamp)1158 public Map<String, String> getProfileStateProperties(String id, long timestamp) { 1159 if (id == null) 1160 throw new NullPointerException(); 1161 Profile internalProfile = internalGetProfile(id); 1162 if (internalProfile == null) 1163 return Collections.emptyMap(); 1164 return internalGetProfileStateProperties(internalProfile, timestamp, true); 1165 } 1166 internalGetProfileStateProperties(IProfile profile, long timestamp, boolean lock)1167 private Map<String, String> internalGetProfileStateProperties(IProfile profile, long timestamp, boolean lock) { 1168 Map<String, String> result = new HashMap<>(); 1169 String timestampString = String.valueOf(timestamp); 1170 int keyOffset = timestampString.length() + 1; 1171 lock = lock || lastAccessedProperties == null; 1172 if (lock) 1173 if (!internalLockProfile(profile)) 1174 throw new IllegalStateException(Messages.SimpleProfileRegistry_Profile_in_use); 1175 try { 1176 Properties properties = readStateProperties(profile.getProfileId()); 1177 Iterator<Object> keys = properties.keySet().iterator(); 1178 while (keys.hasNext()) { 1179 String key = (String) keys.next(); 1180 if (key.indexOf(timestampString) == 0) 1181 result.put(key.substring(keyOffset), properties.getProperty(key)); 1182 } 1183 } catch (ProvisionException e) { 1184 LogHelper.log(e); 1185 } finally { 1186 if (lock) 1187 internalUnlockProfile(profile); 1188 } 1189 return result; 1190 } 1191 1192 @Override getProfileStateProperties(String id, String userKey)1193 public Map<String, String> getProfileStateProperties(String id, String userKey) { 1194 if (id == null || userKey == null) 1195 throw new NullPointerException(); 1196 1197 Profile internalProfile = internalGetProfile(id); 1198 if (internalProfile == null) 1199 return Collections.emptyMap(); 1200 return internalGetProfileStateProperties(internalProfile, userKey, true); 1201 } 1202 internalGetProfileStateProperties(IProfile profile, String userKey, boolean lock)1203 private Map<String, String> internalGetProfileStateProperties(IProfile profile, String userKey, boolean lock) { 1204 Map<String, String> result = new HashMap<>(); 1205 lock = lock || lastAccessedProperties == null; 1206 if (lock) 1207 if (!internalLockProfile(profile)) 1208 throw new IllegalStateException(Messages.SimpleProfileRegistry_Profile_in_use); 1209 try { 1210 Properties properties = readStateProperties(profile.getProfileId()); 1211 Iterator<Object> keys = properties.keySet().iterator(); 1212 while (keys.hasNext()) { 1213 // property key format is timestamp.key 1214 String key = (String) keys.next(); 1215 int index = key.indexOf('.'); 1216 if (index != -1 && index + 1 != key.length() && key.substring(index + 1).equals(userKey)) { 1217 result.put(key.substring(0, index), properties.getProperty(key)); 1218 } 1219 } 1220 } catch (ProvisionException e) { 1221 LogHelper.log(e); 1222 } finally { 1223 if (lock) 1224 internalUnlockProfile(profile); 1225 } 1226 return result; 1227 } 1228 1229 @Override removeProfileStateProperties(String id, long timestamp, Collection<String> keys)1230 public IStatus removeProfileStateProperties(String id, long timestamp, Collection<String> keys) { 1231 if (id == null) 1232 throw new NullPointerException(); 1233 // return if there is no work to do 1234 if (keys != null && keys.size() == 0) 1235 return Status.OK_STATUS; 1236 1237 Profile internalProfile = internalGetProfile(id); 1238 if (internalProfile == null) 1239 return Status.OK_STATUS; 1240 1241 if (!internalLockProfile(internalProfile)) 1242 throw new IllegalStateException(Messages.SimpleProfileRegistry_Profile_in_use); 1243 1244 try { 1245 Properties properties = readStateProperties(id); 1246 String timestampString = String.valueOf(timestamp); 1247 if (keys == null) { 1248 // remove all keys 1249 for (Iterator<Object> already = properties.keySet().iterator(); already.hasNext();) { 1250 String key = (String) already.next(); 1251 // property key is timestamp.key 1252 if (key.startsWith(timestampString)) 1253 already.remove(); 1254 } 1255 } else { 1256 for (String key : keys) { 1257 // property key format is timestamp.key 1258 if (key != null) 1259 properties.remove(timestampString + "." + key); //$NON-NLS-1$ 1260 } 1261 } 1262 writeStateProperties(id, properties); 1263 } catch (ProvisionException e) { 1264 return e.getStatus(); 1265 } finally { 1266 internalUnlockProfile(internalProfile); 1267 } 1268 return Status.OK_STATUS; 1269 } 1270 } 1271