/******************************************************************************* * Copyright (c) 2004, 2010 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 *******************************************************************************/ 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.eclipse.equinox.security.storage.EncodingUtils; 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; } public boolean belongsTo(Object family) { return family == PROFILE_SAVE_JOB_FAMILY; } 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; private IEclipsePreferences loadLevel; private Object profileLock; private String qualifier; private SaveJob saveJob; private 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 = getSegment(path, 3); } private boolean containsProfile(IProfileRegistry profileRegistry, String profileId) { if (profileId == null || profileRegistry == null) return false; return profileRegistry.containsProfile(profileId); } /* * (non-Javadoc) * Create an Engine phase to save profile preferences */ protected void doSave(IProvisioningAgent agent) throws BackingStoreException { synchronized (((ProfilePreferences) parent).profileLock) { String profileId = getSegment(absolutePath(), 2); IProfileRegistry registry = (IProfileRegistry) agent.getService(IProfileRegistry.SERVICE_NAME); //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. */ private ServiceReference<IProvisioningAgent> getAgent(String segment) throws BackingStoreException { String locationString = EncodingUtils.decodeSlashes(segment); Exception failure = 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()) return refs.iterator().next(); } } 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) { StringBuffer result = new StringBuffer(string.length()); char[] input = string.toCharArray(); for (int i = 0; i < input.length; i++) { switch (input[i]) { case '(' : case ')' : case '*' : case '\\' : result.append('\\'); //fall through default : result.append(input[i]); } } 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 = (IAgentLocation) agent.getService(IAgentLocation.SERVICE_NAME); 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); } 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); } protected EclipsePreferences internalCreate(EclipsePreferences nodeParent, String nodeName, Object context) { return new ProfilePreferences(nodeParent, nodeName); } protected boolean isAlreadyLoaded(IEclipsePreferences node) { return loadedNodes.contains(node.absolutePath()); } protected boolean isAlreadyLoaded(String path) { return loadedNodes.contains(path); } /* * (non-Javadoc) * Create an Engine phase to load profile preferences */ protected void load() throws BackingStoreException { synchronized (((ProfilePreferences) parent).profileLock) { ServiceReference<IProvisioningAgent> agentRef = getAgent(getSegment(absolutePath(), 1)); IProvisioningAgent agent = EngineActivator.getContext().getService(agentRef); IProfileRegistry registry = (IProfileRegistry) agent.getService(IProfileRegistry.SERVICE_NAME); try { String profileId = getSegment(absolutePath(), 2); 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)); } finally { EngineActivator.getContext().ungetService(agentRef); } } } protected void loaded() { loadedNodes.add(name()); } 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. */ protected synchronized void save() throws BackingStoreException { try { ServiceReference<IProvisioningAgent> agentRef = getAgent(getSegment(absolutePath(), 1)); IProvisioningAgent agent = EngineActivator.getContext().getService(agentRef); if (saveJob == null || saveJob.agent != agent) saveJob = new SaveJob(agent); EngineActivator.getContext().ungetService(agentRef); } 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 } } }