| /******************************************************************************* |
| * Copyright (c) 2007, 2015 IBM Corporation and others. All rights reserved. This |
| * program and the accompanying materials are made available under the terms of |
| * the Eclipse Public License v1.0 which accompanies this distribution, and is |
| * available at http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| * Ericsson AB - ongoing development |
| * Red Hat, Inc. - fragments support added, Bug 460967 |
| ******************************************************************************/ |
| package org.eclipse.equinox.internal.p2.engine; |
| |
| import java.io.*; |
| import java.lang.ref.SoftReference; |
| import java.net.URI; |
| import java.util.*; |
| import java.util.Map.Entry; |
| import java.util.zip.GZIPInputStream; |
| import java.util.zip.GZIPOutputStream; |
| import javax.xml.parsers.ParserConfigurationException; |
| import org.eclipse.core.runtime.*; |
| import org.eclipse.core.runtime.jobs.Job; |
| import org.eclipse.equinox.internal.p2.core.helpers.*; |
| import org.eclipse.equinox.internal.p2.metadata.TranslationSupport; |
| import org.eclipse.equinox.internal.provisional.p2.core.eventbus.IProvisioningEventBus; |
| import org.eclipse.equinox.p2.core.*; |
| import org.eclipse.equinox.p2.core.spi.IAgentService; |
| import org.eclipse.equinox.p2.engine.*; |
| import org.eclipse.equinox.p2.metadata.*; |
| import org.eclipse.equinox.p2.query.IQueryResult; |
| import org.eclipse.equinox.p2.query.QueryUtil; |
| import org.eclipse.osgi.service.datalocation.Location; |
| import org.eclipse.osgi.util.NLS; |
| import org.osgi.framework.BundleContext; |
| import org.osgi.framework.ServiceReference; |
| import org.xml.sax.InputSource; |
| import org.xml.sax.SAXException; |
| |
| public class SimpleProfileRegistry implements IProfileRegistry, IAgentService { |
| |
| private static final String SIMPLE_PROFILE_REGISTRY_INTERNAL = "_simpleProfileRegistry_internal_"; //$NON-NLS-1$ |
| private static final String PROFILE_REGISTRY = "profile registry"; //$NON-NLS-1$ |
| private static final String PROFILE_PROPERTIES_FILE = "state.properties"; //$NON-NLS-1$ |
| |
| private static final String PROFILE_EXT = ".profile"; //$NON-NLS-1$ |
| private static final String PROFILE_GZ_EXT = ".profile.gz"; //$NON-NLS-1$ |
| public static final String DEFAULT_STORAGE_DIR = "profileRegistry"; //$NON-NLS-1$ |
| private static final String DATA_EXT = ".data"; //$NON-NLS-1$ |
| |
| //Internal constant used to keep track of the newly created timestamp |
| private static final String SERVICE_SHARED_INSTALL_NEW_TIMESTAMP = IProfileRegistry.class.getName() + '_' + "NEW_SELF_TIMESTAMP"; //$NON-NLS-1$ |
| |
| protected final IProvisioningAgent agent; |
| |
| /** |
| * Reference to Map of String(Profile id)->Profile. |
| */ |
| private SoftReference<Map<String, Profile>> profiles; |
| private Map<String, ProfileLock> profileLocks = new HashMap<String, ProfileLock>(); |
| |
| private String self; |
| |
| //Whether the registry should update the self profile when the registry is restored |
| private boolean updateSelfProfile; |
| |
| private File store; |
| |
| ISurrogateProfileHandler surrogateProfileHandler; |
| |
| private IProvisioningEventBus eventBus; |
| // cache of last accessed profile state properties |
| private ProfileStateProperties lastAccessedProperties; |
| |
| public SimpleProfileRegistry(IProvisioningAgent agent, File registryDirectory) { |
| this(agent, registryDirectory, new SurrogateProfileHandler(agent), true); |
| } |
| |
| public SimpleProfileRegistry(IProvisioningAgent agent, File registryDirectory, ISurrogateProfileHandler handler, boolean updateSelfProfile) { |
| this.agent = agent; |
| store = registryDirectory; |
| surrogateProfileHandler = handler; |
| Assert.isNotNull(store, "Profile registry requires a directory"); //$NON-NLS-1$ |
| findSelf(); |
| this.updateSelfProfile = updateSelfProfile; |
| } |
| |
| /** |
| * Determine the id of the "self" profile. This is only applicable for the registry |
| * of the currently running system. |
| */ |
| private void findSelf() { |
| //the location for the currently running system is registered as a service |
| final BundleContext context = EngineActivator.getContext(); |
| if (context == null) |
| return; |
| ServiceReference<IAgentLocation> ref = context.getServiceReference(IAgentLocation.class); |
| if (ref == null) |
| return; |
| IAgentLocation location = context.getService(ref); |
| if (location == null) |
| return; |
| if (store.equals(getDefaultRegistryDirectory(location))) { |
| //we are the registry for the currently running system |
| self = context.getProperty("eclipse.p2.profile"); //$NON-NLS-1$ |
| } else if (agent.getService(IProvisioningAgent.SHARED_CURRENT_AGENT) != null) { |
| // In shared mode, _SELF_ is the value of the current running profile for both agents current and shared |
| if (((IProvisioningAgent) agent.getService(IProvisioningAgent.SHARED_CURRENT_AGENT)).getService(IProvisioningAgent.SHARED_BASE_AGENT) == agent) { |
| self = context.getProperty("eclipse.p2.profile"); //$NON-NLS-1$ |
| } |
| } |
| if (self == null) |
| self = (String) agent.getService("FORCED_SELF"); |
| context.ungetService(ref); |
| } |
| |
| public static File getDefaultRegistryDirectory(IAgentLocation agent) { |
| File registryDirectory = null; |
| if (agent == null) |
| throw new IllegalStateException("Profile Registry inialization failed: Agent Location is not available"); //$NON-NLS-1$ |
| final URI engineDataArea = agent.getDataArea(EngineActivator.ID); |
| URI registryURL = URIUtil.append(engineDataArea, DEFAULT_STORAGE_DIR); |
| registryDirectory = new File(registryURL); |
| registryDirectory.mkdirs(); |
| return registryDirectory; |
| } |
| |
| /** |
| * If the current profile for self is marked as a roaming profile, we need |
| * to update its install and bundle pool locations. |
| */ |
| private void updateSelfProfile(Map<String, Profile> profileMap) { |
| if (profileMap == null) |
| return; |
| Profile selfProfile = profileMap.get(self); |
| if (selfProfile == null) |
| return; |
| |
| //register default locale provider where metadata translations are found |
| //TODO ideally this should not be hard-coded to the current profile |
| TranslationSupport.getInstance().setTranslationSource(selfProfile); |
| |
| if (DebugHelper.DEBUG_PROFILE_REGISTRY) |
| DebugHelper.debug(PROFILE_REGISTRY, "SimpleProfileRegistry.updateSelfProfile"); //$NON-NLS-1$ |
| boolean changed = false; |
| //only update if self is a roaming profile |
| if (Boolean.parseBoolean(selfProfile.getProperty(IProfile.PROP_ROAMING))) |
| changed = updateRoamingProfile(selfProfile); |
| |
| if (changed) |
| saveProfile(selfProfile); |
| } |
| |
| private boolean updateRoamingProfile(Profile selfProfile) { |
| if (DebugHelper.DEBUG_PROFILE_REGISTRY) |
| DebugHelper.debug(PROFILE_REGISTRY, "SimpleProfileRegistry.updateRoamingProfile"); //$NON-NLS-1$ |
| Location installLocation = ServiceHelper.getService(EngineActivator.getContext(), Location.class, Location.INSTALL_FILTER); |
| File location = new File(installLocation.getURL().getPath()); |
| boolean changed = false; |
| if (!location.equals(new File(selfProfile.getProperty(IProfile.PROP_INSTALL_FOLDER)))) { |
| selfProfile.setProperty(IProfile.PROP_INSTALL_FOLDER, location.getAbsolutePath()); |
| changed = true; |
| } |
| String propCache = selfProfile.getProperty(IProfile.PROP_CACHE); |
| if (propCache != null && !location.equals(new File(propCache))) { |
| selfProfile.setProperty(IProfile.PROP_CACHE, location.getAbsolutePath()); |
| changed = true; |
| } |
| if (DebugHelper.DEBUG_PROFILE_REGISTRY) |
| DebugHelper.debug(PROFILE_REGISTRY, "SimpleProfileRegistry.updateRoamingProfile(changed=" + changed + ')'); //$NON-NLS-1$ |
| return changed; |
| } |
| |
| public synchronized String toString() { |
| return "Profile registry for location: " + store.getAbsolutePath() + "\n" + getProfileMap().toString(); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| |
| public synchronized IProfile getProfile(String id) { |
| Profile profile = internalGetProfile(id); |
| if (profile == null) |
| return null; |
| return profile.snapshot(); |
| } |
| |
| public synchronized IProfile getProfile(String id, long timestamp) { |
| if (SELF.equals(id)) |
| id = self; |
| |
| if (profiles != null) { |
| IProfile profile = getProfile(id); |
| if (profile != null && profile.getTimestamp() == timestamp) |
| return profile; |
| } |
| |
| File profileDirectory = getProfileFolder(id); |
| if (!profileDirectory.isDirectory()) |
| return null; |
| |
| File profileFile = new File(profileDirectory, Long.toString(timestamp) + PROFILE_GZ_EXT); |
| if (!profileFile.exists()) { |
| profileFile = new File(profileDirectory, Long.toString(timestamp) + PROFILE_EXT); |
| if (!profileFile.exists()) |
| return null; |
| } |
| |
| Parser parser = new Parser(EngineActivator.getContext(), EngineActivator.ID); |
| try { |
| parser.parse(profileFile); |
| } catch (IOException e) { |
| LogHelper.log(new Status(IStatus.ERROR, EngineActivator.ID, NLS.bind(Messages.error_parsing_profile, profileFile), e)); |
| } |
| return parser.getProfileMap().get(id); |
| } |
| |
| public synchronized long[] listProfileTimestamps(String id) { |
| if (SELF.equals(id)) |
| id = self; |
| //guard against null self profile |
| if (id == null) |
| return new long[0]; |
| |
| File profileDirectory = getProfileFolder(id); |
| if (!profileDirectory.isDirectory()) |
| return new long[0]; |
| |
| File[] profileFiles = profileDirectory.listFiles(new FileFilter() { |
| public boolean accept(File pathname) { |
| return (pathname.getName().endsWith(PROFILE_EXT) || pathname.getName().endsWith(PROFILE_GZ_EXT)) && pathname.isFile() && !pathname.getName().startsWith("._"); //$NON-NLS-1$ |
| } |
| }); |
| |
| long[] timestamps = new long[profileFiles.length]; |
| for (int i = 0; i < profileFiles.length; i++) { |
| String filename = profileFiles[i].getName(); |
| int extensionIndex = filename.lastIndexOf(PROFILE_EXT); |
| try { |
| timestamps[i] = Long.parseLong(filename.substring(0, extensionIndex)); |
| } catch (NumberFormatException e) { |
| 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$ |
| } |
| } |
| Arrays.sort(timestamps); |
| return timestamps; |
| } |
| |
| private Profile internalGetProfile(String id) { |
| if (SELF.equals(id)) |
| id = self; |
| Profile profile = getProfileMap().get(id); |
| if (self != null && self.equals(id)) { |
| boolean resetProfile = false; |
| if (profile != null && ignoreExistingProfile(profile)) { |
| internalSetProfileStateProperty(profile, profile.getTimestamp(), IProfile.STATE_PROP_SHARED_INSTALL, IProfile.STATE_SHARED_INSTALL_VALUE_BEFOREFLUSH); |
| profile = null; |
| resetProfile = true; |
| } |
| if (profile == null) { |
| profile = createSurrogateProfile(id); |
| if (profile == null) |
| return null; |
| |
| if (resetProfile) { |
| //Now that we created a new profile. Tag it, override the property and register the timestamp in the agent registry for pickup by other |
| internalSetProfileStateProperty(profile, profile.getTimestamp(), IProfile.STATE_PROP_SHARED_INSTALL, IProfile.STATE_SHARED_INSTALL_VALUE_NEW); |
| internalSetProfileStateProperty(profile, profile.getTimestamp(), SIMPLE_PROFILE_REGISTRY_INTERNAL + getBaseTimestamp(profile.getProfileId()), getBaseTimestamp(id)); |
| //fragments support - remeber the property |
| internalSetProfileStateProperty(profile, profile.getTimestamp(), SIMPLE_PROFILE_REGISTRY_INTERNAL + getExtTimeStamp(), getExtTimeStamp()); |
| agent.registerService(SERVICE_SHARED_INSTALL_NEW_TIMESTAMP, Long.toString(profile.getTimestamp())); |
| } else { |
| //This is the first time we create the shared profile. Tag it as such and also remember the timestamp of the base |
| internalSetProfileStateProperty(profile, profile.getTimestamp(), IProfile.STATE_PROP_SHARED_INSTALL, IProfile.STATE_SHARED_INSTALL_VALUE_INITIAL); |
| String baseTimestamp = getBaseTimestamp(id); |
| if (baseTimestamp != null) |
| internalSetProfileStateProperty(profile, profile.getTimestamp(), SIMPLE_PROFILE_REGISTRY_INTERNAL + baseTimestamp, baseTimestamp); |
| String extTimestamp = getExtTimeStamp(); |
| internalSetProfileStateProperty(profile, profile.getTimestamp(), SIMPLE_PROFILE_REGISTRY_INTERNAL + extTimestamp, extTimestamp); |
| } |
| } |
| } |
| return profile; |
| } |
| |
| // get timestamp of fragments (extensions) |
| private String getExtTimeStamp() { |
| long result = -1; |
| if (!EngineActivator.EXTENDED) { |
| return Long.toString(result); |
| } |
| File[] extensions = EngineActivator.getExtensionsDirectories(); |
| for (File extension : extensions) { |
| if (extension.lastModified() > result) { |
| result = extension.lastModified(); |
| } |
| } |
| return Long.toString(result); |
| } |
| |
| private boolean ignoreExistingProfile(IProfile profile) { |
| if (agent.getService(SERVICE_SHARED_INSTALL_NEW_TIMESTAMP) != null) |
| return false; |
| |
| String baseTimestamp = getBaseTimestamp(profile.getProfileId()); |
| String extTimestamp = getExtTimeStamp(); |
| if (baseTimestamp == null) { |
| return false; |
| } |
| |
| boolean extensionOK = true; |
| if (surrogateProfileHandler != null && surrogateProfileHandler.isSurrogate(profile)) { |
| extensionOK = (internalGetProfileStateProperties(profile, SIMPLE_PROFILE_REGISTRY_INTERNAL + extTimestamp, false).size() != 0); |
| } |
| |
| if ((internalGetProfileStateProperties(profile, SIMPLE_PROFILE_REGISTRY_INTERNAL + baseTimestamp, false).size() != 0) && extensionOK) |
| return false; |
| |
| return true; |
| } |
| |
| private String getBaseTimestamp(String id) { |
| IProvisioningAgent baseAgent = (IProvisioningAgent) agent.getService(IProvisioningAgent.SHARED_BASE_AGENT); |
| if (baseAgent == null) |
| return null; |
| IProfileRegistry registry = (IProfileRegistry) baseAgent.getService(IProfileRegistry.SERVICE_NAME); |
| if (registry == null) |
| return null; |
| long[] revisions = registry.listProfileTimestamps(id); |
| if (revisions.length >= 1) { |
| return Long.toString(revisions[revisions.length - 1]); |
| } |
| return null; |
| } |
| |
| private Profile createSurrogateProfile(String id) { |
| if (surrogateProfileHandler == null) |
| return null; |
| |
| Profile profile = (Profile) surrogateProfileHandler.createProfile(id); |
| if (profile == null) |
| return null; |
| |
| saveProfile(profile); |
| resetProfiles(); |
| return getProfileMap().get(id); |
| } |
| |
| public synchronized IProfile[] getProfiles() { |
| Map<String, Profile> profileMap = getProfileMap(); |
| Profile[] result = new Profile[profileMap.size()]; |
| int i = 0; |
| for (Profile profile : profileMap.values()) { |
| result[i++] = profile.snapshot(); |
| } |
| return result; |
| } |
| |
| /** |
| * Returns an initialized map of String(Profile id)->Profile. |
| */ |
| protected Map<String, Profile> getProfileMap() { |
| if (profiles != null) { |
| Map<String, Profile> result = profiles.get(); |
| if (result != null) |
| return result; |
| } |
| Map<String, Profile> result = restore(); |
| if (result == null) |
| result = new LinkedHashMap<String, Profile>(8); |
| profiles = new SoftReference<Map<String, Profile>>(result); |
| if (updateSelfProfile) { |
| //update self profile on first load |
| updateSelfProfile(result); |
| } |
| return result; |
| } |
| |
| public synchronized void updateProfile(Profile profile) { |
| String id = profile.getProfileId(); |
| Profile current = getProfileMap().get(id); |
| if (current == null) |
| throw new IllegalArgumentException(NLS.bind(Messages.profile_does_not_exist, id)); |
| |
| ProfileLock lock = profileLocks.get(id); |
| lock.checkLocked(); |
| |
| current.clearLocalProperties(); |
| current.clearInstallableUnits(); |
| |
| current.addProperties(profile.getLocalProperties()); |
| IQueryResult<IInstallableUnit> queryResult = profile.query(QueryUtil.createIUAnyQuery(), null); |
| for (Iterator<IInstallableUnit> queryResultIt = queryResult.iterator(); queryResultIt.hasNext();) { |
| IInstallableUnit iu = queryResultIt.next(); |
| current.addInstallableUnit(iu); |
| Map<String, String> iuProperties = profile.getInstallableUnitProperties(iu); |
| if (iuProperties != null) |
| current.addInstallableUnitProperties(iu, iuProperties); |
| } |
| saveProfile(current); |
| profile.clearOrphanedInstallableUnitProperties(); |
| profile.setTimestamp(current.getTimestamp()); |
| broadcastChangeEvent(id, IProfileEvent.CHANGED); |
| } |
| |
| public IProfile addProfile(String id) throws ProvisionException { |
| return addProfile(id, null, null); |
| } |
| |
| public IProfile addProfile(String id, Map<String, String> profileProperties) throws ProvisionException { |
| return addProfile(id, profileProperties, null); |
| } |
| |
| public synchronized IProfile addProfile(String id, Map<String, String> profileProperties, String parentId) throws ProvisionException { |
| if (SELF.equals(id)) |
| id = self; |
| Map<String, Profile> profileMap = getProfileMap(); |
| if (profileMap.get(id) != null) |
| throw new ProvisionException(NLS.bind(Messages.Profile_Duplicate_Root_Profile_Id, id)); |
| |
| Profile parent = null; |
| if (parentId != null) { |
| if (SELF.equals(parentId)) |
| parentId = self; |
| parent = profileMap.get(parentId); |
| if (parent == null) |
| throw new ProvisionException(NLS.bind(Messages.Profile_Parent_Not_Found, parentId)); |
| } |
| |
| Profile profile = new Profile(agent, id, parent, profileProperties); |
| if (surrogateProfileHandler != null && surrogateProfileHandler.isSurrogate(profile)) |
| profile.setSurrogateProfileHandler(surrogateProfileHandler); |
| profileMap.put(id, profile); |
| saveProfile(profile); |
| broadcastChangeEvent(id, IProfileEvent.ADDED); |
| return profile.snapshot(); |
| } |
| |
| public synchronized void removeProfile(String profileId) { |
| if (SELF.equals(profileId)) |
| profileId = self; |
| //note we need to maintain a reference to the profile map until it is persisted to prevent gc |
| Map<String, Profile> profileMap = getProfileMap(); |
| Profile profile = profileMap.get(profileId); |
| if (profile == null) |
| return; |
| |
| List<String> subProfileIds = profile.getSubProfileIds(); |
| for (int i = 0; i < subProfileIds.size(); i++) { |
| removeProfile(subProfileIds.get(i)); |
| } |
| internalLockProfile(profile); |
| // The above call recursively locked the parent(s). So save it away to rewind the locking process. |
| IProfile savedParent = profile.getParentProfile(); |
| try { |
| profile.setParent(null); |
| } finally { |
| internalUnlockProfile(profile); |
| // The above call will not recurse since parent is now null. So do it explicitly. |
| if (savedParent != null) { |
| internalUnlockProfile(savedParent); |
| } |
| } |
| profileMap.remove(profileId); |
| profileLocks.remove(profileId); |
| // deleting the profile removes the folder and subsequently all |
| // the profile state properties as well since they are stored in a file in the folder. |
| deleteProfile(profileId); |
| broadcastChangeEvent(profileId, IProfileEvent.REMOVED); |
| } |
| |
| public synchronized void removeProfile(String id, long timestamp) throws ProvisionException { |
| if (SELF.equals(id)) |
| id = self; |
| |
| if (profiles != null) { |
| IProfile profile = getProfile(id); |
| if (profile != null && profile.getTimestamp() == timestamp) |
| throw new ProvisionException(Messages.SimpleProfileRegistry_CannotRemoveCurrentSnapshot); |
| } |
| |
| File profileDirectory = getProfileFolder(id); |
| if (!profileDirectory.isDirectory()) |
| return; |
| |
| File profileFile = new File(profileDirectory, Long.toString(timestamp) + PROFILE_GZ_EXT); |
| if (!profileFile.exists()) { |
| profileFile = new File(profileDirectory, Long.toString(timestamp) + PROFILE_EXT); |
| if (!profileFile.exists()) |
| return; |
| } |
| FileUtils.deleteAll(profileFile); |
| // Ignore the return value here. If there was a problem removing the profile state |
| // properties we don't want to fail the whole operation since the profile state itself |
| // was removed successfully |
| removeProfileStateProperties(id, timestamp, null); |
| } |
| |
| private void broadcastChangeEvent(String profileId, int reason) { |
| if (eventBus != null) |
| eventBus.publishEvent(new ProfileEvent(profileId, reason)); |
| } |
| |
| /** |
| * Restores the profile registry from disk, and returns the loaded profile map. |
| * Returns <code>null</code> if unable to read the registry. |
| */ |
| private Map<String, Profile> restore() { |
| if (store == null || !store.isDirectory()) |
| throw new IllegalStateException(NLS.bind(Messages.reg_dir_not_available, store)); |
| |
| Parser parser = new Parser(EngineActivator.getContext(), EngineActivator.ID); |
| File[] profileDirectories = store.listFiles(new FileFilter() { |
| public boolean accept(File pathname) { |
| return pathname.getName().endsWith(PROFILE_EXT) && pathname.isDirectory(); |
| } |
| }); |
| // protect against NPE |
| if (profileDirectories == null) { |
| parser.getProfileMap(); |
| } |
| for (int i = 0; i < profileDirectories.length; i++) { |
| String directoryName = profileDirectories[i].getName(); |
| String profileId = unescape(directoryName.substring(0, directoryName.lastIndexOf(PROFILE_EXT))); |
| ProfileLock lock = profileLocks.get(profileId); |
| if (lock == null) { |
| lock = new ProfileLock(this, profileDirectories[i]); |
| profileLocks.put(profileId, lock); |
| } |
| |
| boolean locked = false; |
| if (lock.processHoldsLock() || (locked = lock.lock())) { |
| try { |
| File profileFile = findLatestProfileFile(profileDirectories[i]); |
| if (profileFile != null) { |
| try { |
| parser.parse(profileFile); |
| } catch (IOException e) { |
| LogHelper.log(new Status(IStatus.ERROR, EngineActivator.ID, NLS.bind(Messages.error_parsing_profile, profileFile), e)); |
| } |
| } |
| } finally { |
| if (locked) |
| lock.unlock(); |
| } |
| } else { |
| // could not lock the profile, so add a place holder |
| parser.addProfilePlaceHolder(profileId); |
| } |
| } |
| return parser.getProfileMap(); |
| } |
| |
| private File findLatestProfileFile(File profileDirectory) { |
| File latest = null; |
| long latestTimestamp = 0; |
| File[] profileFiles = profileDirectory.listFiles(new FileFilter() { |
| public boolean accept(File pathname) { |
| return (pathname.getName().endsWith(PROFILE_GZ_EXT) || pathname.getName().endsWith(PROFILE_EXT)) && !pathname.isDirectory(); |
| } |
| }); |
| // protect against NPE |
| if (profileFiles == null) |
| return null; |
| for (int i = 0; i < profileFiles.length; i++) { |
| File profileFile = profileFiles[i]; |
| String fileName = profileFile.getName(); |
| try { |
| long timestamp = Long.parseLong(fileName.substring(0, fileName.indexOf(PROFILE_EXT))); |
| if (timestamp > latestTimestamp) { |
| latestTimestamp = timestamp; |
| latest = profileFile; |
| } |
| } catch (NumberFormatException e) { |
| // ignore |
| } |
| } |
| return latest; |
| } |
| |
| private void saveProfile(Profile profile) { |
| File profileDirectory = getProfileFolder(profile.getProfileId()); |
| profileDirectory.mkdir(); |
| |
| long previousTimestamp = profile.getTimestamp(); |
| long currentTimestamp = System.currentTimeMillis(); |
| if (currentTimestamp <= previousTimestamp) |
| currentTimestamp = previousTimestamp + 1; |
| boolean shouldGzipFile = shouldGzipFile(profile); |
| File profileFile = new File(profileDirectory, Long.toString(currentTimestamp) + (shouldGzipFile ? PROFILE_GZ_EXT : PROFILE_EXT)); |
| |
| // Log a stack trace to see who is writing the profile. |
| if (DebugHelper.DEBUG_PROFILE_REGISTRY) |
| DebugHelper.debug(PROFILE_REGISTRY, "Saving profile to: " + profileFile.getAbsolutePath()); //$NON-NLS-1$ |
| |
| profile.setTimestamp(currentTimestamp); |
| profile.setChanged(false); |
| OutputStream os = null; |
| try { |
| if (shouldGzipFile) |
| os = new BufferedOutputStream(new GZIPOutputStream(new FileOutputStream(profileFile))); |
| else |
| os = new BufferedOutputStream(new FileOutputStream(profileFile)); |
| Writer writer = new Writer(os); |
| writer.writeProfile(profile); |
| } catch (IOException e) { |
| profile.setTimestamp(previousTimestamp); |
| profileFile.delete(); |
| LogHelper.log(new Status(IStatus.ERROR, EngineActivator.ID, NLS.bind(Messages.error_persisting_profile, profile.getProfileId()), e)); |
| } finally { |
| try { |
| if (os != null) |
| os.close(); |
| } catch (IOException e) { |
| // ignore |
| } |
| } |
| } |
| |
| public void setEventBus(IProvisioningEventBus bus) { |
| this.eventBus = bus; |
| } |
| |
| /** |
| * Returns whether the profile file for the given profile should be written in gzip format. |
| */ |
| private boolean shouldGzipFile(Profile profile) { |
| //check system property controlling compression |
| String format = EngineActivator.getContext().getProperty(EngineActivator.PROP_PROFILE_FORMAT); |
| if (format != null && format.equals(EngineActivator.PROFILE_FORMAT_UNCOMPRESSED)) |
| return false; |
| |
| //check whether the profile contains the p2 engine from 3.5.0 or earlier |
| return profile.available(QueryUtil.createIUQuery("org.eclipse.equinox.p2.engine", new VersionRange("[0.0.0, 1.0.101)")), null).isEmpty(); //$NON-NLS-1$//$NON-NLS-2$ |
| } |
| |
| private void deleteProfile(String profileId) { |
| File profileDirectory = getProfileFolder(profileId); |
| FileUtils.deleteAll(profileDirectory); |
| } |
| |
| /** |
| * Converts a profile id into a string that can be used as a file name in any file system. |
| */ |
| public static String escape(String toEscape) { |
| StringBuffer buffer = new StringBuffer(); |
| int length = toEscape.length(); |
| for (int i = 0; i < length; ++i) { |
| char ch = toEscape.charAt(i); |
| switch (ch) { |
| case '\\' : |
| case '/' : |
| case ':' : |
| case '*' : |
| case '?' : |
| case '"' : |
| case '<' : |
| case '>' : |
| case '|' : |
| case '%' : |
| buffer.append("%" + (int) ch + ";"); //$NON-NLS-1$ //$NON-NLS-2$ |
| break; |
| default : |
| buffer.append(ch); |
| } |
| } |
| return buffer.toString(); |
| } |
| |
| public static String unescape(String text) { |
| if (text.indexOf('%') == -1) |
| return text; |
| |
| StringBuffer buffer = new StringBuffer(); |
| int length = text.length(); |
| for (int i = 0; i < length; ++i) { |
| char ch = text.charAt(i); |
| if (ch == '%') { |
| int colon = text.indexOf(';', i); |
| if (colon == -1) |
| throw new IllegalStateException("error unescaping the sequence at character (" + i + ") for " + text + ". Expected %{int};."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| ch = (char) Integer.parseInt(text.substring(i + 1, colon)); |
| i = colon; |
| } |
| buffer.append(ch); |
| } |
| return buffer.toString(); |
| } |
| |
| static class Writer extends ProfileWriter { |
| |
| public Writer(OutputStream output) throws IOException { |
| super(output, new ProcessingInstruction[] {ProcessingInstruction.makeTargetVersionInstruction(PROFILE_TARGET, ProfileXMLConstants.CURRENT_VERSION)}); |
| } |
| } |
| |
| /* |
| * Parser for the contents of a SimpleProfileRegistry, |
| * as written by the Writer class. |
| */ |
| class Parser extends ProfileParser { |
| private final Map<String, ProfileHandler> profileHandlers = new HashMap<String, ProfileHandler>(); |
| |
| public Map<String, ProfileHandler> getProfileHandlers() { |
| return Collections.unmodifiableMap(profileHandlers); |
| } |
| |
| public Parser(BundleContext context, String bundleId) { |
| super(context, bundleId); |
| } |
| |
| public void addProfilePlaceHolder(String profileId) { |
| profileHandlers.put(profileId, new ProfileHandler(profileId)); |
| } |
| |
| public void parse(File file) throws IOException { |
| InputStream is; |
| if (file.getName().endsWith(PROFILE_GZ_EXT)) { |
| is = new BufferedInputStream(new GZIPInputStream(new FileInputStream(file))); |
| } else { // backward compatibility. SimpleProfileRegistry doesn't write non-gzipped profiles any more. |
| is = new BufferedInputStream(new FileInputStream(file)); |
| } |
| parse(is); |
| } |
| |
| public synchronized void parse(InputStream stream) throws IOException { |
| this.status = null; |
| try { |
| // TODO: currently not caching the parser since we make no assumptions |
| // or restrictions on concurrent parsing |
| getParser(); |
| ProfileHandler profileHandler = new ProfileHandler(); |
| xmlReader.setContentHandler(new ProfileDocHandler(PROFILE_ELEMENT, profileHandler)); |
| xmlReader.parse(new InputSource(stream)); |
| profileHandlers.put(profileHandler.getProfileId(), profileHandler); |
| } catch (SAXException e) { |
| throw new IOException(e.getMessage()); |
| } catch (ParserConfigurationException e) { |
| throw new IOException(e.getMessage()); |
| } finally { |
| stream.close(); |
| } |
| } |
| |
| protected Object getRootObject() { |
| return this; |
| } |
| |
| public Map<String, Profile> getProfileMap() { |
| Map<String, Profile> profileMap = new HashMap<String, Profile>(); |
| for (String profileId : profileHandlers.keySet()) { |
| addProfile(profileId, profileMap); |
| } |
| return profileMap; |
| } |
| |
| private void addProfile(String profileId, Map<String, Profile> profileMap) { |
| if (profileMap.containsKey(profileId)) |
| return; |
| |
| ProfileHandler profileHandler = profileHandlers.get(profileId); |
| Profile parentProfile = null; |
| |
| String parentId = profileHandler.getParentId(); |
| if (parentId != null) { |
| addProfile(parentId, profileMap); |
| parentProfile = profileMap.get(parentId); |
| } |
| |
| Profile profile = new Profile(agent, profileId, parentProfile, profileHandler.getProperties()); |
| if (surrogateProfileHandler != null && surrogateProfileHandler.isSurrogate(profile)) |
| profile.setSurrogateProfileHandler(surrogateProfileHandler); |
| |
| profile.setTimestamp(profileHandler.getTimestamp()); |
| |
| IInstallableUnit[] ius = profileHandler.getInstallableUnits(); |
| if (ius != null) { |
| for (int i = 0; i < ius.length; i++) { |
| IInstallableUnit iu = ius[i]; |
| profile.addInstallableUnit(iu); |
| Map<String, String> iuProperties = profileHandler.getIUProperties(iu); |
| if (iuProperties != null) { |
| for (Entry<String, String> entry : iuProperties.entrySet()) { |
| profile.setInstallableUnitProperty(iu, entry.getKey(), entry.getValue()); |
| } |
| } |
| } |
| } |
| profile.setChanged(false); |
| profileMap.put(profileId, profile); |
| } |
| |
| private final class ProfileDocHandler extends DocHandler { |
| |
| public ProfileDocHandler(String rootName, RootHandler rootHandler) { |
| super(rootName, rootHandler); |
| } |
| |
| public void processingInstruction(String target, String data) throws SAXException { |
| if (ProfileXMLConstants.PROFILE_TARGET.equals(target)) { |
| Version repositoryVersion = extractPIVersion(target, data); |
| if (!ProfileXMLConstants.XML_TOLERANCE.isIncluded(repositoryVersion)) { |
| throw new SAXException(NLS.bind(Messages.SimpleProfileRegistry_Parser_Has_Incompatible_Version, repositoryVersion, ProfileXMLConstants.XML_TOLERANCE)); |
| } |
| } |
| } |
| } |
| |
| protected String getErrorMessage() { |
| return Messages.SimpleProfileRegistry_Parser_Error_Parsing_Registry; |
| } |
| |
| public String toString() { |
| // TODO: |
| return null; |
| } |
| |
| } |
| |
| public synchronized boolean isCurrent(IProfile profile) { |
| Profile internalProfile = getProfileMap().get(profile.getProfileId()); |
| if (internalProfile == null) |
| throw new IllegalArgumentException(NLS.bind(Messages.profile_not_registered, profile.getProfileId())); |
| |
| if (!internalLockProfile(internalProfile)) |
| throw new IllegalStateException(Messages.SimpleProfileRegistry_Profile_in_use); |
| |
| try { |
| return (!((Profile) profile).isChanged() && checkTimestamps(profile, internalProfile)); |
| } finally { |
| internalUnlockProfile(internalProfile); |
| } |
| } |
| |
| public synchronized void lockProfile(Profile profile) { |
| Profile internalProfile = internalGetProfile(profile.getProfileId()); |
| if (internalProfile == null) |
| throw new IllegalArgumentException(NLS.bind(Messages.profile_not_registered, profile.getProfileId())); |
| |
| if (!internalLockProfile(internalProfile)) |
| throw new IllegalStateException(Messages.SimpleProfileRegistry_Profile_in_use); |
| |
| boolean isCurrent = false; |
| try { |
| if (profile.isChanged()) { |
| if (DebugHelper.DEBUG_PROFILE_REGISTRY) |
| DebugHelper.debug(PROFILE_REGISTRY, "Profile is marked as changed."); //$NON-NLS-1$ |
| throw new IllegalStateException(NLS.bind(Messages.profile_changed, profile.getProfileId())); |
| } |
| if (!checkTimestamps(profile, internalProfile)) { |
| if (DebugHelper.DEBUG_PROFILE_REGISTRY) |
| DebugHelper.debug(PROFILE_REGISTRY, "Unexpected timestamp difference in profile."); //$NON-NLS-1$ |
| throw new IllegalStateException(NLS.bind(Messages.profile_not_current, new String[] {profile.getProfileId(), Long.toString(internalProfile.getTimestamp()), Long.toString(profile.getTimestamp())})); |
| } |
| isCurrent = true; |
| } finally { |
| // this check is done here to ensure we unlock even if a runtime exception is thrown |
| if (!isCurrent) |
| internalUnlockProfile(internalProfile); |
| } |
| } |
| |
| private boolean internalLockProfile(IProfile profile) { |
| ProfileLock lock = profileLocks.get(profile.getProfileId()); |
| if (lock == null) { |
| lock = new ProfileLock(this, getProfileFolder(profile.getProfileId())); |
| profileLocks.put(profile.getProfileId(), lock); |
| } |
| return lock.lock(); |
| } |
| |
| private boolean checkTimestamps(IProfile profile, IProfile internalProfile) { |
| long[] timestamps = listProfileTimestamps(profile.getProfileId()); |
| if (timestamps.length == 0) { |
| if (DebugHelper.DEBUG_PROFILE_REGISTRY) |
| DebugHelper.debug(PROFILE_REGISTRY, "check timestamp: expected " + profile.getTimestamp() + " but no profiles were found"); //$NON-NLS-1$ //$NON-NLS-2$ |
| resetProfiles(); |
| return false; |
| } |
| |
| long currentTimestamp = (timestamps.length == 0) ? -1 : timestamps[timestamps.length - 1]; |
| if (profile.getTimestamp() != currentTimestamp) { |
| if (DebugHelper.DEBUG_PROFILE_REGISTRY) |
| DebugHelper.debug(PROFILE_REGISTRY, "check timestamp: expected " + profile.getTimestamp() + " but was " + currentTimestamp); //$NON-NLS-1$ //$NON-NLS-2$ |
| if (internalProfile.getTimestamp() != currentTimestamp) |
| resetProfiles(); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.equinox.internal.provisional.p2.engine.IProfileRegistry#containsProfile(java.lang.String) |
| */ |
| public synchronized boolean containsProfile(String id) { |
| if (SELF.equals(id)) |
| id = self; |
| //null check done after self check, because self can be null |
| if (id == null) |
| return false; |
| |
| // check profiles to avoid restoring the profile registry |
| if (profiles != null) |
| if (getProfile(id) != null) |
| return true; |
| |
| File profileDirectory = getProfileFolder(id); |
| if (!profileDirectory.isDirectory()) |
| return false; |
| File[] profileFiles = profileDirectory.listFiles(new FileFilter() { |
| public boolean accept(File pathname) { |
| return (pathname.getName().endsWith(PROFILE_GZ_EXT) || pathname.getName().endsWith(PROFILE_EXT)) && pathname.isFile(); |
| } |
| }); |
| return profileFiles.length > 0; |
| } |
| |
| public synchronized void resetProfiles() { |
| profiles = null; |
| } |
| |
| public synchronized void unlockProfile(IProfile profile) { |
| if (profile == null) |
| throw new IllegalArgumentException(NLS.bind(Messages.profile_not_registered, "")); //$NON-NLS-1$ |
| internalUnlockProfile(profile); |
| } |
| |
| private void internalUnlockProfile(IProfile profile) { |
| ProfileLock lock = profileLocks.get(profile.getProfileId()); |
| lock.unlock(); |
| } |
| |
| public Profile validate(IProfile candidate) { |
| if (candidate instanceof Profile) |
| return (Profile) candidate; |
| |
| 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$ |
| } |
| |
| public synchronized File getProfileDataDirectory(String id) { |
| if (SELF.equals(id)) |
| id = self; |
| File profileDirectory = getProfileFolder(id); |
| File profileDataArea = new File(profileDirectory, DATA_EXT); |
| if (!profileDataArea.isDirectory() && !profileDataArea.mkdir()) |
| throw new IllegalStateException("Could not create profile data area " + profileDataArea.getAbsolutePath() + "for: " + id); //$NON-NLS-1$ //$NON-NLS-2$ |
| return profileDataArea; |
| } |
| |
| /*(non-Javadoc) |
| * @see org.eclipse.equinox.p2.core.spi.IAgentService#start() |
| */ |
| public void start() { |
| //nothing to do |
| } |
| |
| /*(non-Javadoc) |
| * @see org.eclipse.equinox.p2.core.spi.IAgentService#stop() |
| */ |
| public void stop() { |
| try { |
| //ensure there are no more profile preference save jobs running |
| Job.getJobManager().join(ProfilePreferences.PROFILE_SAVE_JOB_FAMILY, null); |
| } catch (InterruptedException e) { |
| //ignore |
| } |
| } |
| |
| // Class representing a particular instance of a profile's state properties. |
| // Can be used for caching. |
| class ProfileStateProperties { |
| private String id; |
| private File file; |
| private long timestamp; |
| private Properties properties; |
| |
| ProfileStateProperties(String id, File file, Properties properties) { |
| this.id = id; |
| this.file = file; |
| this.properties = properties; |
| this.timestamp = file.lastModified(); |
| } |
| |
| // return true if the cached timestamp is the same as the one on disk |
| boolean isCurrent() { |
| if (!file.exists()) |
| return true; |
| return file.lastModified() == timestamp; |
| } |
| |
| String getId() { |
| return id; |
| } |
| |
| Properties getProperties() { |
| return this.properties; |
| } |
| } |
| |
| /* |
| * Return the folder on disk associated with the profile with the given identifier. |
| */ |
| private File getProfileFolder(String id) { |
| return new File(store, escape(id) + PROFILE_EXT); |
| } |
| |
| /* |
| * Read and return the state properties for the profile with the given id. |
| * If one does not exist, then return an empty Properties file. |
| * If there were problems reading the file then return throw an exception. |
| */ |
| private Properties readStateProperties(String id) throws ProvisionException { |
| if (SELF.equals(id)) |
| id = self; |
| |
| // if the last cached value is the one we are interested in and up-to-date |
| // then don't bother reading from disk |
| if (lastAccessedProperties != null && id.equals(lastAccessedProperties.getId()) && lastAccessedProperties.isCurrent()) |
| return lastAccessedProperties.getProperties(); |
| |
| File profileDirectory = getProfileFolder(id); |
| if (!profileDirectory.isDirectory()) |
| throw new ProvisionException(new Status(IStatus.ERROR, EngineActivator.ID, NLS.bind(Messages.SimpleProfileRegistry_Bad_profile_location, profileDirectory.getPath()))); |
| |
| File file = new File(profileDirectory, PROFILE_PROPERTIES_FILE); |
| Properties properties = new Properties(); |
| if (!file.exists()) { |
| lastAccessedProperties = new ProfileStateProperties(id, file, properties); |
| return properties; |
| } |
| InputStream input = null; |
| try { |
| input = new BufferedInputStream(new FileInputStream(file)); |
| properties.load(input); |
| } catch (IOException e) { |
| throw new ProvisionException(new Status(IStatus.ERROR, EngineActivator.ID, Messages.SimpleProfileRegistry_States_Error_Reading_File, e)); |
| } finally { |
| if (input != null) |
| try { |
| input.close(); |
| } catch (IOException e) { |
| // Ignore |
| } |
| } |
| |
| //cache the value before we return |
| lastAccessedProperties = new ProfileStateProperties(id, file, properties); |
| return properties; |
| } |
| |
| /* |
| * Write the given state properties to disk for the specified profile. |
| */ |
| private IStatus writeStateProperties(String id, Properties properties) { |
| if (SELF.equals(id)) |
| id = self; |
| |
| File profileDirectory = getProfileFolder(id); |
| File file = new File(profileDirectory, PROFILE_PROPERTIES_FILE); |
| OutputStream output = null; |
| Properties prunedProperties = properties; |
| try { |
| output = new BufferedOutputStream(new FileOutputStream(file)); |
| prunedProperties = pruneStateProperties(id, properties); |
| prunedProperties.store(output, null); |
| output.flush(); |
| } catch (IOException e) { |
| return new Status(IStatus.ERROR, EngineActivator.ID, Messages.SimpleProfileRegistry_States_Error_Writing_File, e); |
| } finally { |
| try { |
| if (output != null) |
| output.close(); |
| } catch (IOException e) { |
| // ignore |
| } |
| } |
| // cache the value |
| lastAccessedProperties = new ProfileStateProperties(id, file, prunedProperties); |
| return Status.OK_STATUS; |
| } |
| |
| // Only write state properties for state timestamps that still exist |
| // TODO: Do we want to expose this method as API? |
| // TODO: Do we want to run this method on every write or just after specific elapsed times since the last Prune? |
| private Properties pruneStateProperties(String id, Properties properties) { |
| Properties result = new Properties(); |
| long[] timestamps = listProfileTimestamps(id); |
| HashSet<String> timestampsSet = new HashSet<String>(timestamps.length); |
| for (int i = 0; i < timestamps.length; i++) { |
| timestampsSet.add(String.valueOf(timestamps[i])); |
| } |
| |
| Enumeration<Object> keys = properties.keys(); |
| while (keys.hasMoreElements()) { |
| String key = (String) keys.nextElement(); |
| int index = key.indexOf('.'); |
| if (index > -1) { |
| String timestamp = key.substring(0, index); |
| if (timestampsSet.contains(timestamp)) { |
| result.put(key, properties.get(key)); |
| } |
| } |
| } |
| return result; |
| } |
| |
| /* |
| * Ensure a profile with the given identifier has a state with the specified timestamp. Return |
| * a status object indicating success or failure. |
| */ |
| private IStatus validateState(String id, long timestamp) { |
| long[] states = listProfileTimestamps(id); |
| for (long ts : states) |
| if (ts == timestamp) |
| return Status.OK_STATUS; |
| return new Status(IStatus.ERROR, EngineActivator.ID, (NLS.bind(Messages.SimpleProfileRegistry_state_not_found, timestamp, id))); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.equinox.p2.engine.IProfileRegistry#setProfileStateProperties(java.lang.String, long, java.util.Map) |
| */ |
| public IStatus setProfileStateProperties(String id, long timestamp, Map<String, String> propertiesToAdd) { |
| if (id == null || propertiesToAdd == null) |
| throw new NullPointerException(); |
| |
| Profile internalProfile = internalGetProfile(id); |
| return internalSetProfileStateProperties(internalProfile, timestamp, propertiesToAdd); |
| } |
| |
| private IStatus internalSetProfileStateProperties(IProfile profile, long timestamp, Map<String, String> propertiesToAdd) { |
| IStatus result = validateState(profile.getProfileId(), timestamp); |
| if (!result.isOK()) |
| return result; |
| |
| if (!internalLockProfile(profile)) |
| throw new IllegalStateException(Messages.SimpleProfileRegistry_Profile_in_use); |
| |
| try { |
| Properties properties = readStateProperties(profile.getProfileId()); |
| for (Map.Entry<String, String> entry : propertiesToAdd.entrySet()) { |
| // property key format is timestamp.key |
| properties.put(timestamp + "." + entry.getKey(), entry.getValue()); //$NON-NLS-1$ |
| } |
| writeStateProperties(profile.getProfileId(), properties); |
| } catch (ProvisionException e) { |
| return e.getStatus(); |
| } finally { |
| internalUnlockProfile(profile); |
| } |
| return Status.OK_STATUS; |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.equinox.p2.engine.IProfileRegistry#setProfileStateProperty(java.lang.String, long, java.lang.String, java.lang.String) |
| */ |
| public IStatus setProfileStateProperty(String id, long timestamp, String key, String value) { |
| if (id == null) |
| throw new NullPointerException(); |
| return internalSetProfileStateProperty(internalGetProfile(id), timestamp, key, value); |
| } |
| |
| private IStatus internalSetProfileStateProperty(IProfile profile, long timestamp, String key, String value) { |
| if (key == null || value == null) |
| throw new NullPointerException(); |
| Map<String, String> properties = new HashMap<String, String>(); |
| properties.put(key, value); |
| |
| return internalSetProfileStateProperties(profile, timestamp, properties); |
| } |
| |
| public Map<String, String> getProfileStateProperties(String id, long timestamp) { |
| if (id == null) |
| throw new NullPointerException(); |
| return internalGetProfileStateProperties(internalGetProfile(id), timestamp, true); |
| } |
| |
| private Map<String, String> internalGetProfileStateProperties(IProfile profile, long timestamp, boolean lock) { |
| Map<String, String> result = new HashMap<String, String>(); |
| String timestampString = String.valueOf(timestamp); |
| int keyOffset = timestampString.length() + 1; |
| lock = lock || lastAccessedProperties == null; |
| if (lock) |
| if (!internalLockProfile(profile)) |
| throw new IllegalStateException(Messages.SimpleProfileRegistry_Profile_in_use); |
| try { |
| Properties properties = readStateProperties(profile.getProfileId()); |
| Iterator<Object> keys = properties.keySet().iterator(); |
| while (keys.hasNext()) { |
| String key = (String) keys.next(); |
| if (key.indexOf(timestampString) == 0) |
| result.put(key.substring(keyOffset), properties.getProperty(key)); |
| } |
| } catch (ProvisionException e) { |
| LogHelper.log(e); |
| } finally { |
| if (lock) |
| internalUnlockProfile(profile); |
| } |
| return result; |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.equinox.p2.engine.IProfileRegistry#getProfileStateProperties(java.lang.String, java.lang.String) |
| */ |
| public Map<String, String> getProfileStateProperties(String id, String userKey) { |
| if (id == null || userKey == null) |
| throw new NullPointerException(); |
| |
| Profile internalProfile = internalGetProfile(id); |
| return internalGetProfileStateProperties(internalProfile, userKey, true); |
| } |
| |
| private Map<String, String> internalGetProfileStateProperties(IProfile profile, String userKey, boolean lock) { |
| Map<String, String> result = new HashMap<String, String>(); |
| lock = lock || lastAccessedProperties == null; |
| if (lock) |
| if (!internalLockProfile(profile)) |
| throw new IllegalStateException(Messages.SimpleProfileRegistry_Profile_in_use); |
| try { |
| Properties properties = readStateProperties(profile.getProfileId()); |
| Iterator<Object> keys = properties.keySet().iterator(); |
| while (keys.hasNext()) { |
| // property key format is timestamp.key |
| String key = (String) keys.next(); |
| int index = key.indexOf('.'); |
| if (index != -1 && index + 1 != key.length() && key.substring(index + 1).equals(userKey)) { |
| result.put(key.substring(0, index), properties.getProperty(key)); |
| } |
| } |
| } catch (ProvisionException e) { |
| LogHelper.log(e); |
| } finally { |
| if (lock) |
| internalUnlockProfile(profile); |
| } |
| return result; |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.equinox.p2.engine.IProfileRegistry#removeProfileStateProperties(java.lang.String, long, java.util.Collection) |
| */ |
| public IStatus removeProfileStateProperties(String id, long timestamp, Collection<String> keys) { |
| if (id == null) |
| throw new NullPointerException(); |
| // return if there is no work to do |
| if (keys != null && keys.size() == 0) |
| return Status.OK_STATUS; |
| |
| Profile internalProfile = internalGetProfile(id); |
| if (!internalLockProfile(internalProfile)) |
| throw new IllegalStateException(Messages.SimpleProfileRegistry_Profile_in_use); |
| |
| try { |
| Properties properties = readStateProperties(id); |
| String timestampString = String.valueOf(timestamp); |
| if (keys == null) { |
| // remove all keys |
| for (Iterator<Object> already = properties.keySet().iterator(); already.hasNext();) { |
| String key = (String) already.next(); |
| // property key is timestamp.key |
| if (key.indexOf(timestampString) == 0) |
| already.remove(); |
| } |
| } else { |
| for (String key : keys) { |
| // property key format is timestamp.key |
| if (key != null) |
| properties.remove(timestampString + "." + key); //$NON-NLS-1$ |
| } |
| } |
| writeStateProperties(id, properties); |
| } catch (ProvisionException e) { |
| return e.getStatus(); |
| } finally { |
| internalUnlockProfile(internalProfile); |
| } |
| return Status.OK_STATUS; |
| } |
| } |