| /******************************************************************************* |
| * Copyright (c) 2004, 2017 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| * Ericsson AB (Pascal Rapicault) - reading preferences from base in shared install |
| *******************************************************************************/ |
| package org.eclipse.equinox.internal.p2.engine; |
| |
| import java.io.File; |
| import java.util.*; |
| import org.eclipse.core.internal.preferences.EclipsePreferences; |
| import org.eclipse.core.runtime.*; |
| import org.eclipse.core.runtime.jobs.Job; |
| import org.eclipse.core.runtime.preferences.IEclipsePreferences; |
| import org.eclipse.equinox.internal.p2.core.helpers.LogHelper; |
| import org.eclipse.equinox.internal.p2.core.helpers.Tracing; |
| import org.eclipse.equinox.p2.core.IAgentLocation; |
| import org.eclipse.equinox.p2.core.IProvisioningAgent; |
| import org.eclipse.equinox.p2.engine.IProfileRegistry; |
| import org.osgi.framework.*; |
| import org.osgi.service.prefs.BackingStoreException; |
| |
| /** |
| * A preference implementation that stores preferences in the engine's profile |
| * data area. There is one preference file per profile, with an additional file |
| * that is used when there is no currently running profile. |
| */ |
| public class ProfilePreferences extends EclipsePreferences { |
| private class SaveJob extends Job { |
| IProvisioningAgent agent; |
| |
| SaveJob(IProvisioningAgent agent) { |
| super(Messages.ProfilePreferences_saving); |
| setSystem(true); |
| this.agent = agent; |
| } |
| |
| @Override |
| public boolean belongsTo(Object family) { |
| return family == PROFILE_SAVE_JOB_FAMILY; |
| } |
| |
| @Override |
| protected IStatus run(IProgressMonitor monitor) { |
| try { |
| doSave(agent); |
| } catch (IllegalStateException e) { |
| if (Tracing.DEBUG_PROFILE_PREFERENCES) { |
| Tracing.debug("Attempt to save preferences after agent has been stopped"); //$NON-NLS-1$ |
| e.printStackTrace(); |
| } |
| //ignore - this means the provisioning agent has already been stopped, and since |
| //this job is joined during agent stop, it means this job has been scheduled after the |
| //agent stopped and therefore can't have any interesting changes to save |
| } catch (BackingStoreException e) { |
| LogHelper.log(new Status(IStatus.WARNING, EngineActivator.ID, "Exception saving profile preferences", e)); //$NON-NLS-1$ |
| } catch (RuntimeException e) { |
| LogHelper.log(new Status(IStatus.WARNING, EngineActivator.ID, "Exception saving profile preferences", e)); //$NON-NLS-1$ |
| } |
| return Status.OK_STATUS; |
| } |
| } |
| |
| // cache which nodes have been loaded from disk |
| private static Set<String> loadedNodes = Collections.synchronizedSet(new HashSet<String>()); |
| |
| public static final Object PROFILE_SAVE_JOB_FAMILY = new Object(); |
| |
| private static final long SAVE_SCHEDULE_DELAY = 500; |
| |
| //private IPath location; |
| protected IEclipsePreferences loadLevel; |
| protected Object profileLock; |
| protected String qualifier; |
| |
| private SaveJob saveJob; |
| protected int segmentCount; |
| |
| public ProfilePreferences() { |
| this(null, null); |
| } |
| |
| public ProfilePreferences(EclipsePreferences nodeParent, String nodeName) { |
| super(nodeParent, nodeName); |
| |
| //path is /profile/{agent location}/{profile id}/qualifier |
| |
| // cache the segment count |
| String path = absolutePath(); |
| segmentCount = getSegmentCount(path); |
| |
| if (segmentCount <= 2) |
| return; |
| |
| if (segmentCount == 3) |
| profileLock = new Object(); |
| |
| if (segmentCount < 4) |
| return; |
| // cache the qualifier |
| qualifier = getQualifierSegment(); |
| } |
| |
| private boolean containsProfile(IProfileRegistry profileRegistry, String profileId) { |
| if (profileId == null || profileRegistry == null) |
| return false; |
| return profileRegistry.containsProfile(profileId); |
| } |
| |
| /* |
| * Create an Engine phase to save profile preferences |
| */ |
| protected void doSave(IProvisioningAgent agent) throws BackingStoreException { |
| synchronized (((ProfilePreferences) parent).profileLock) { |
| String profileId = getProfileIdSegment(); |
| IProfileRegistry registry = agent.getService(IProfileRegistry.class); |
| //can't save anything without a profile registry |
| if (registry == null) |
| return; |
| if (!containsProfile(registry, profileId)) { |
| //use the default location for the self profile, otherwise just do nothing and return |
| if (IProfileRegistry.SELF.equals(profileId)) { |
| IPath location = getDefaultLocation(agent); |
| if (location != null) { |
| super.save(location); |
| return; |
| } |
| } |
| if (Tracing.DEBUG_PROFILE_PREFERENCES) |
| Tracing.debug("Not saving preferences since there is no file for node: " + absolutePath()); //$NON-NLS-1$ |
| return; |
| } |
| super.save(getProfileLocation(registry, profileId)); |
| } |
| } |
| |
| /** |
| * Returns a reference to the agent service corresponding to the given encoded |
| * agent location. Never returns null; throws an exception if the agent could not be found. |
| */ |
| protected IProvisioningAgent getAgent(String segment) throws BackingStoreException { |
| String locationString = SlashEncode.decode(segment); |
| Exception failure = null; |
| IProvisioningAgent result = null; |
| try { |
| String filter = "(locationURI=" + encodeForFilter(locationString) + ')'; //$NON-NLS-1$ |
| final BundleContext context = EngineActivator.getContext(); |
| if (context != null) { |
| Collection<ServiceReference<IProvisioningAgent>> refs = context.getServiceReferences(IProvisioningAgent.class, filter); |
| if (!refs.isEmpty()) { |
| ServiceReference<IProvisioningAgent> ref = refs.iterator().next(); |
| result = EngineActivator.getContext().getService(ref); |
| EngineActivator.getContext().ungetService(ref); |
| return result; |
| } |
| } |
| } catch (InvalidSyntaxException e) { |
| failure = e; |
| } |
| throw new BackingStoreException("Unable to determine provisioning agent from location: " + segment, failure); //$NON-NLS-1$ |
| } |
| |
| /** |
| * Encodes a string so that it is suitable for use as a value for a filter property. |
| * Any reserved filter characters are escaped. |
| */ |
| private String encodeForFilter(String string) { |
| StringBuilder result = new StringBuilder(string.length()); |
| char[] input = string.toCharArray(); |
| for (char element : input) { |
| switch (element) { |
| case '(' : |
| case ')' : |
| case '*' : |
| case '\\' : |
| result.append('\\'); |
| //fall through |
| default : |
| result.append(element); |
| } |
| } |
| return result.toString(); |
| } |
| |
| /** |
| * Returns the preference file to use when there is no active profile. |
| */ |
| private IPath getDefaultLocation(IProvisioningAgent agent) { |
| //use engine agent location for preferences if there is no self profile |
| IAgentLocation location = agent.getService(IAgentLocation.class); |
| if (location == null) { |
| LogHelper.log(new Status(IStatus.WARNING, EngineActivator.ID, "Agent location service not available", new RuntimeException())); //$NON-NLS-1$ |
| return null; |
| } |
| IPath dataArea = new Path(URIUtil.toFile(location.getDataArea(EngineActivator.ID)).getAbsolutePath()); |
| return computeLocation(dataArea, qualifier); |
| } |
| |
| @Override |
| protected IEclipsePreferences getLoadLevel() { |
| if (loadLevel == null) { |
| if (qualifier == null) |
| return null; |
| // Make it relative to this node rather than navigating to it from the root. |
| // Walk backwards up the tree starting at this node. |
| // This is important to avoid a chicken/egg thing on startup. |
| IEclipsePreferences node = this; |
| for (int i = 4; i < segmentCount; i++) |
| node = (EclipsePreferences) node.parent(); |
| loadLevel = node; |
| } |
| return loadLevel; |
| } |
| |
| /** |
| * Returns the location of the preference file for the given profile. |
| */ |
| private IPath getProfileLocation(IProfileRegistry registry, String profileId) { |
| SimpleProfileRegistry profileRegistry = (SimpleProfileRegistry) registry; |
| File profileDataDirectory = profileRegistry.getProfileDataDirectory(profileId); |
| return computeLocation(new Path(profileDataDirectory.getAbsolutePath()), qualifier); |
| } |
| |
| @Override |
| protected EclipsePreferences internalCreate(EclipsePreferences nodeParent, String nodeName, Object context) { |
| if (nodeName.equals("shared") && segmentCount == 1) { //$NON-NLS-1$ |
| return new SharedProfilePreferences(nodeParent, nodeName); |
| } |
| return new ProfilePreferences(nodeParent, nodeName); |
| } |
| |
| @Override |
| protected boolean isAlreadyLoaded(IEclipsePreferences node) { |
| return loadedNodes.contains(node.absolutePath()); |
| } |
| |
| protected boolean isAlreadyLoaded(String path) { |
| return loadedNodes.contains(path); |
| } |
| |
| /* |
| * Create an Engine phase to load profile preferences |
| */ |
| @Override |
| protected void load() throws BackingStoreException { |
| synchronized (((ProfilePreferences) parent).profileLock) { |
| IProvisioningAgent agent = getAgent(getAgentLocationSegment()); |
| if (agent == null) |
| return; |
| IProfileRegistry registry = agent.getService(IProfileRegistry.class); |
| String profileId = getProfileIdSegment(); |
| if (!containsProfile(registry, profileId)) { |
| //use the default location for the self profile, otherwise just do nothing and return |
| if (IProfileRegistry.SELF.equals(profileId)) { |
| IPath location = getDefaultLocation(agent); |
| if (location != null) { |
| load(location); |
| return; |
| } |
| } |
| if (Tracing.DEBUG_PROFILE_PREFERENCES) |
| Tracing.debug("Not loading preferences since there is no file for node: " + absolutePath()); //$NON-NLS-1$ |
| return; |
| } |
| load(getProfileLocation(registry, profileId)); |
| } |
| } |
| |
| @Override |
| protected void loaded() { |
| loadedNodes.add(name()); |
| } |
| |
| @Override |
| public void removeNode() throws BackingStoreException { |
| super.removeNode(); |
| loadedNodes.remove(this.absolutePath()); |
| } |
| |
| /** |
| * Schedules the save job. This method is synchronized to protect lazily initialization |
| * of the save job instance. |
| */ |
| @Override |
| protected synchronized void save() throws BackingStoreException { |
| try { |
| IProvisioningAgent agent = getAgent(getAgentLocationSegment()); |
| if (saveJob == null || saveJob.agent != agent) |
| saveJob = new SaveJob(agent); |
| } catch (BackingStoreException e) { |
| if (Tracing.DEBUG_PROFILE_PREFERENCES) |
| e.printStackTrace(); |
| //get agent has already gone away so we can't save preferences |
| //TODO see bug 300450 |
| } |
| //only schedule a save if the engine bundle is still running |
| BundleContext context = EngineActivator.getContext(); |
| if (context == null || saveJob == null) |
| return; |
| try { |
| if (context.getBundle().getState() == Bundle.ACTIVE) |
| saveJob.schedule(SAVE_SCHEDULE_DELAY); |
| } catch (IllegalStateException e) { |
| //bundle has been stopped concurrently, so don't save |
| } |
| } |
| |
| protected String getQualifierSegment() { |
| return getSegment(absolutePath(), 3); |
| } |
| |
| protected String getProfileIdSegment() { |
| return getSegment(absolutePath(), 2); |
| } |
| |
| protected String getAgentLocationSegment() { |
| return getSegment(absolutePath(), 1); |
| } |
| |
| } |