Bug 221031 Directory watcher to support repositories
diff --git a/bundles/org.eclipse.equinox.p2.directorywatcher/src/org/eclipse/equinox/internal/provisional/p2/directorywatcher/RepositoryListener.java b/bundles/org.eclipse.equinox.p2.directorywatcher/src/org/eclipse/equinox/internal/provisional/p2/directorywatcher/RepositoryListener.java
index 842fd5d..7087ac0 100644
--- a/bundles/org.eclipse.equinox.p2.directorywatcher/src/org/eclipse/equinox/internal/provisional/p2/directorywatcher/RepositoryListener.java
+++ b/bundles/org.eclipse.equinox.p2.directorywatcher/src/org/eclipse/equinox/internal/provisional/p2/directorywatcher/RepositoryListener.java
@@ -21,10 +21,8 @@
 import org.eclipse.equinox.internal.provisional.p2.metadata.IArtifactKey;
 import org.eclipse.equinox.internal.provisional.p2.metadata.IInstallableUnit;
 import org.eclipse.equinox.internal.provisional.p2.metadata.generator.*;
-import org.eclipse.equinox.internal.provisional.p2.metadata.query.InstallableUnitQuery;
 import org.eclipse.equinox.internal.provisional.p2.metadata.repository.IMetadataRepository;
 import org.eclipse.equinox.internal.provisional.p2.metadata.repository.IMetadataRepositoryManager;
-import org.eclipse.equinox.internal.provisional.p2.query.Collector;
 import org.eclipse.equinox.internal.provisional.p2.query.Query;
 import org.eclipse.osgi.service.resolver.*;
 import org.osgi.framework.BundleContext;
@@ -32,13 +30,14 @@
 
 public class RepositoryListener extends DirectoryChangeListener {
 
+	private static final String ARTIFACT_FOLDER = "artifact.folder"; //$NON-NLS-1$
+	private static final String ARTIFACT_REFERENCE = "artifact.reference"; //$NON-NLS-1$
+	private static final String FILE_LAST_MODIFIED = "file.lastModified"; //$NON-NLS-1$
+	private static final String FILE_NAME = "file.name"; //$NON-NLS-1$
 	private final IMetadataRepository metadataRepository;
 	private final IArtifactRepository artifactRepository;
 	private final BundleDescriptionFactory bundleDescriptionFactory;
 	private final Map currentFiles = new HashMap();
-	private final String repositoryName;
-	private final boolean hidden;
-	private long lastModifed;
 
 	/**
 	 * Create a repository listener that watches the specified folder and generates repositories
@@ -51,11 +50,9 @@
 	 */
 	public RepositoryListener(BundleContext context, String repositoryName, File repositoryFolder, boolean hidden) {
 
-		this.repositoryName = repositoryName;
-		this.hidden = hidden;
 		File stateDir;
 		if (repositoryFolder == null) {
-			String stateDirName = "listener_" + repositoryName;
+			String stateDirName = "listener_" + repositoryName.hashCode();
 			stateDir = context.getDataFile(stateDirName);
 			stateDir.mkdirs();
 		} else {
@@ -69,13 +66,19 @@
 			throw new IllegalStateException(e.getMessage());
 		}
 
-		metadataRepository = initializeMetadataRepository(context, stateDirURL);
-		artifactRepository = initializeArtifactRepository(context, stateDirURL);
+		metadataRepository = initializeMetadataRepository(context, repositoryName, stateDirURL, hidden);
+		artifactRepository = initializeArtifactRepository(context, repositoryName, stateDirURL, hidden);
 		bundleDescriptionFactory = initializeBundleDescriptionFactory(context);
 	}
 
-	public RepositoryListener(BundleContext context, String string) {
-		this(context, string, null, false);
+	public RepositoryListener(BundleContext context, String repositoryName) {
+		this(context, repositoryName, null, false);
+	}
+
+	public RepositoryListener(BundleContext context, IMetadataRepository metadataRepository, IArtifactRepository artifactRepository) {
+		this.artifactRepository = artifactRepository;
+		this.metadataRepository = metadataRepository;
+		bundleDescriptionFactory = initializeBundleDescriptionFactory(context);
 	}
 
 	private BundleDescriptionFactory initializeBundleDescriptionFactory(BundleContext context) {
@@ -95,7 +98,7 @@
 		}
 	}
 
-	private IArtifactRepository initializeArtifactRepository(BundleContext context, URL stateDirURL) {
+	private IArtifactRepository initializeArtifactRepository(BundleContext context, String repositoryName, URL stateDirURL, boolean hidden) {
 		ServiceReference reference = context.getServiceReference(IArtifactRepositoryManager.class.getName());
 		IArtifactRepositoryManager manager = null;
 		if (reference != null)
@@ -129,7 +132,7 @@
 		}
 	}
 
-	private IMetadataRepository initializeMetadataRepository(BundleContext context, URL stateDirURL) {
+	private IMetadataRepository initializeMetadataRepository(BundleContext context, String repositoryName, URL stateDirURL, boolean hidden) {
 		ServiceReference reference = context.getServiceReference(IMetadataRepositoryManager.class.getName());
 		IMetadataRepositoryManager manager = null;
 		if (reference != null)
@@ -150,6 +153,7 @@
 			} else {
 				repository = manager.createRepository(stateDirURL, repositoryName, IMetadataRepositoryManager.TYPE_SIMPLE_REPOSITORY);
 			}
+			manager.addRepository(stateDirURL);
 			return repository;
 		} catch (ProvisionException e) {
 			LogHelper.log(e);
@@ -163,8 +167,7 @@
 	 * @see org.eclipse.equinox.internal.provisional.p2.directorywatcher.IDirectoryChangeListener#added(java.io.File)
 	 */
 	public boolean added(File file) {
-		if (isInteresting(file))
-			currentFiles.put(file, new Long(file.lastModified()));
+		currentFiles.put(file, new Long(file.lastModified()));
 		return true;
 	}
 
@@ -172,8 +175,7 @@
 	 * @see org.eclipse.equinox.internal.provisional.p2.directorywatcher.IDirectoryChangeListener#changed(java.io.File)
 	 */
 	public boolean changed(File file) {
-		if (isInteresting(file))
-			currentFiles.put(file, new Long(file.lastModified()));
+		currentFiles.put(file, new Long(file.lastModified()));
 		return true;
 	}
 
@@ -187,19 +189,23 @@
 		return true;
 	}
 
-	/*
-	 * Return a boolean value indicating whether or not we are interested in
-	 * processing the given file. Currently we handle JAR files and directories.
-	 */
-	private boolean isInteresting(File file) {
-		return file.isDirectory() || file.getAbsolutePath().endsWith(".jar");
+	private boolean isBundle(File file) {
+		if (file.isDirectory() || file.getName().endsWith(".jar")) {
+			BundleDescription bundleDescription = bundleDescriptionFactory.getBundleDescription(file);
+			return bundleDescription != null;
+		}
+		return false;
+	}
+
+	private boolean isFeature(File file) {
+		return file.isDirectory() && new File(file, "feature.xml").exists();
 	}
 
 	/* (non-Javadoc)
 	 * @see org.eclipse.equinox.internal.provisional.p2.directorywatcher.DirectoryChangeListener#isInterested(java.io.File)
 	 */
 	public boolean isInterested(File file) {
-		return true;
+		return isFeature(file) || isBundle(file);
 	}
 
 	/* (non-Javadoc)
@@ -220,93 +226,89 @@
 	 * @see org.eclipse.equinox.internal.provisional.p2.directorywatcher.IDirectoryChangeListener#stopPoll()
 	 */
 	public void stopPoll() {
-		synchronizeMetadataRepository();
-		synchronizeArtifactRepository();
+		if (metadataRepository != null)
+			synchronizeMetadataRepository();
+
+		if (artifactRepository != null)
+			synchronizeArtifactRepository();
 	}
 
 	private void synchronizeMetadataRepository() {
-		boolean modified = false;
 		final Map snapshot = new HashMap(currentFiles);
 		Query removeQuery = new Query() {
 			public boolean isMatch(Object candidate) {
 				if (!(candidate instanceof IInstallableUnit))
 					return false;
 				IInstallableUnit iu = (IInstallableUnit) candidate;
-				File iuFile = new File(iu.getProperty("file.name")); //$NON-NLS-1$
-				Long iuLastModified = new Long(iu.getProperty("file.lastModified")); //$NON-NLS-1$
+				File iuFile = new File(iu.getProperty(FILE_NAME));
+				Long iuLastModified = new Long(iu.getProperty(FILE_LAST_MODIFIED));
 				Long snapshotLastModified = (Long) snapshot.get(iuFile);
 				if (snapshotLastModified == null || !snapshotLastModified.equals(iuLastModified))
 					return true;
+
+				// match found. Remove from snapshot to prevent it from being added.
 				snapshot.remove(iuFile);
 				return false;
 			}
 		};
-		if (metadataRepository.removeInstallableUnits(removeQuery, null))
-			modified = true;
+		metadataRepository.removeInstallableUnits(removeQuery, null);
 
 		if (!snapshot.isEmpty()) {
-			modified = true;
-			IInstallableUnit[] iusToAdd = generateIUs(snapshot.keySet(), metadataRepository.getLocation().toExternalForm());
+			IInstallableUnit[] iusToAdd = generateIUs(snapshot.keySet());
 			metadataRepository.addInstallableUnits(iusToAdd);
 		}
-		if (modified)
-			lastModifed = System.currentTimeMillis();
 	}
 
 	private void synchronizeArtifactRepository() {
-		final boolean[] modified = {false};
-		final List snapshot = new ArrayList(Arrays.asList(artifactRepository.getArtifactKeys()));
-		Collector collector = new Collector() {
-			public boolean accept(Object object) {
-				IInstallableUnit iu = (IInstallableUnit) object;
-				IArtifactKey[] artifacts = iu.getArtifacts();
-				if (artifacts == null || artifacts.length == 0)
-					return true;
-				IArtifactKey artifact = artifacts[0];
-				if (!snapshot.remove(artifact)) {
-					File iuFile = new File(iu.getProperty("file.name"));
-					IArtifactDescriptor descriptor = generateArtifactDescriptor(iuFile);
-					if (descriptor != null) {
-						artifactRepository.addDescriptor(descriptor);
-						modified[0] = true;
-					}
-				}
-				return true;
-			}
-		};
-		metadataRepository.query(InstallableUnitQuery.ANY, collector, null);
+		final Map snapshot = new HashMap(currentFiles);
+		final List keys = new ArrayList(Arrays.asList(artifactRepository.getArtifactKeys()));
 
-		for (Iterator it = snapshot.iterator(); it.hasNext();) {
+		for (Iterator it = keys.iterator(); it.hasNext();) {
 			IArtifactKey key = (IArtifactKey) it.next();
-			artifactRepository.removeDescriptor(key);
-			modified[0] = true;
+			IArtifactDescriptor[] descriptors = artifactRepository.getArtifactDescriptors(key);
+			for (int i = 0; i < descriptors.length; i++) {
+				ArtifactDescriptor descriptor = (ArtifactDescriptor) descriptors[i];
+				File artifactFile = new File(descriptor.getRepositoryProperty(FILE_NAME));
+				Long artifactLastModified = new Long(descriptor.getRepositoryProperty(FILE_LAST_MODIFIED));
+				Long snapshotLastModified = (Long) snapshot.get(artifactFile);
+				if (snapshotLastModified == null || !snapshotLastModified.equals(artifactLastModified))
+					artifactRepository.removeDescriptor(descriptor);
+				else
+					snapshot.remove(key);
+			}
 		}
 
-		if (modified[0])
-			lastModifed = System.currentTimeMillis();
+		for (Iterator it = snapshot.keySet().iterator(); it.hasNext();) {
+			File file = (File) it.next();
+			IArtifactDescriptor descriptor = generateArtifactDescriptor(file);
+			if (descriptor != null)
+				artifactRepository.addDescriptor(descriptor);
+		}
 	}
 
-	IArtifactDescriptor generateArtifactDescriptor(File candidate) {
+	protected IArtifactDescriptor generateArtifactDescriptor(File candidate) {
 
 		IArtifactDescriptor basicDescriptor = generateBasicDescriptor(candidate);
 		ArtifactDescriptor pathDescriptor = new ArtifactDescriptor(basicDescriptor);
 		try {
-			pathDescriptor.setRepositoryProperty("artifact.reference", candidate.toURL().toExternalForm());
+			pathDescriptor.setRepositoryProperty(ARTIFACT_REFERENCE, candidate.toURL().toExternalForm());
 		} catch (MalformedURLException e) {
 			// unexpected
 			e.printStackTrace();
 			return null;
 		}
 		if (candidate.isDirectory())
-			pathDescriptor.setRepositoryProperty("artifact.folder", "true");
+			pathDescriptor.setRepositoryProperty(ARTIFACT_FOLDER, Boolean.TRUE.toString());
+
+		pathDescriptor.setRepositoryProperty(FILE_NAME, candidate.getAbsolutePath());
+		pathDescriptor.setRepositoryProperty(FILE_LAST_MODIFIED, Long.toString(candidate.lastModified()));
 
 		return pathDescriptor;
 	}
 
 	private IArtifactDescriptor generateBasicDescriptor(File candidate) {
-		// feature check
-		File parent = candidate.getParentFile();
-		if (parent != null && parent.getName().equals("features")) {
+
+		if (isFeature(candidate)) {
 			FeatureParser parser = new FeatureParser();
 			Feature feature = parser.parse(candidate);
 			IArtifactKey featureKey = MetadataGeneratorHelper.createFeatureArtifactKey(feature.getId(), feature.getVersion());
@@ -318,22 +320,16 @@
 		return MetadataGeneratorHelper.createArtifactDescriptor(key, candidate, true, false);
 	}
 
-	private IInstallableUnit[] generateIUs(Collection files, String repositoryId) {
+	private IInstallableUnit[] generateIUs(Collection files) {
 		List ius = new ArrayList();
 		for (Iterator it = files.iterator(); it.hasNext();) {
 			File candidate = (File) it.next();
 
 			Properties props = new Properties();
-			props.setProperty("repository.id", repositoryId);
-			props.setProperty("file.name", candidate.getAbsolutePath());
-			props.setProperty("file.lastModified", Long.toString(candidate.lastModified()));
+			props.setProperty(FILE_NAME, candidate.getAbsolutePath());
+			props.setProperty(FILE_LAST_MODIFIED, Long.toString(candidate.lastModified()));
 
-			if (candidate.isDirectory() && candidate.getName().equals("eclipse"))
-				continue;
-
-			// feature check
-			File parent = candidate.getParentFile();
-			if (parent != null && parent.getName().equals("features")) {
+			if (isFeature(candidate)) {
 				IInstallableUnit[] featureIUs = generateFeatureIUs(candidate, props);
 				if (featureIUs != null)
 					ius.addAll(Arrays.asList(featureIUs));
@@ -378,8 +374,4 @@
 	public IArtifactRepository getArtifactRepository() {
 		return artifactRepository;
 	}
-
-	public long getLastModified() {
-		return lastModifed;
-	}
 }
diff --git a/bundles/org.eclipse.equinox.p2.reconciler.dropins/META-INF/MANIFEST.MF b/bundles/org.eclipse.equinox.p2.reconciler.dropins/META-INF/MANIFEST.MF
index 15e1965..3746432 100644
--- a/bundles/org.eclipse.equinox.p2.reconciler.dropins/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.equinox.p2.reconciler.dropins/META-INF/MANIFEST.MF
@@ -6,7 +6,6 @@
 Bundle-Localization: plugin
 Bundle-Version: 0.1.0.qualifier
 Bundle-Activator: org.eclipse.equinox.internal.p2.reconciler.dropins.Activator
-Eclipse-LazyStart: true
 Bundle-RequiredExecutionEnvironment: J2SE-1.4,
  CDC-1.1/Foundation-1.1
 Import-Package: org.eclipse.equinox.internal.p2.update,
@@ -21,6 +20,9 @@
  org.eclipse.equinox.internal.provisional.p2.metadata.query,
  org.eclipse.equinox.internal.provisional.p2.metadata.repository,
  org.eclipse.equinox.internal.provisional.p2.query,
+ org.eclipse.equinox.internal.provisional.spi.p2.artifact.repository,
+ org.eclipse.equinox.internal.provisional.spi.p2.core.repository,
+ org.eclipse.equinox.internal.provisional.spi.p2.metadata.repository,
  org.eclipse.osgi.service.datalocation;version="1.0.0",
  org.eclipse.osgi.util;version="1.1.0",
  org.osgi.framework;version="1.3.0",
diff --git a/bundles/org.eclipse.equinox.p2.reconciler.dropins/build.properties b/bundles/org.eclipse.equinox.p2.reconciler.dropins/build.properties
index 97493b2..eb63c6e 100644
--- a/bundles/org.eclipse.equinox.p2.reconciler.dropins/build.properties
+++ b/bundles/org.eclipse.equinox.p2.reconciler.dropins/build.properties
@@ -13,4 +13,5 @@
 bin.includes = META-INF/,\
                .,\
                plugin.properties,\
-               about.html
+               about.html,\
+               plugin.xml
diff --git a/bundles/org.eclipse.equinox.p2.reconciler.dropins/plugin.xml b/bundles/org.eclipse.equinox.p2.reconciler.dropins/plugin.xml
new file mode 100644
index 0000000..81c2c8e
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.reconciler.dropins/plugin.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?eclipse version="3.2"?>
+<plugin>
+      <extension
+      id="extensionRepository"
+         point="org.eclipse.equinox.p2.metadata.repository.metadataRepositories">
+      <factory
+            class="org.eclipse.equinox.internal.p2.extensionlocation.ExtensionLocationMetadataRepositoryFactory">
+      </factory>
+      <filter
+            suffix="eclipse">
+      </filter>
+   </extension>
+      <extension
+            point="org.eclipse.equinox.p2.artifact.repository.artifactRepositories">
+         <factory
+               class="org.eclipse.equinox.internal.p2.extensionlocation.ExtensionLocationArtifactRepositoryFactory">
+         </factory>
+         <filter
+               suffix="eclipse">
+         </filter>
+      </extension>
+</plugin>
diff --git a/bundles/org.eclipse.equinox.p2.reconciler.dropins/src/org/eclipse/equinox/internal/p2/extensionlocation/ExtensionLocationArtifactRepository.java b/bundles/org.eclipse.equinox.p2.reconciler.dropins/src/org/eclipse/equinox/internal/p2/extensionlocation/ExtensionLocationArtifactRepository.java
new file mode 100644
index 0000000..8d6581b
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.reconciler.dropins/src/org/eclipse/equinox/internal/p2/extensionlocation/ExtensionLocationArtifactRepository.java
@@ -0,0 +1,151 @@
+package org.eclipse.equinox.internal.p2.extensionlocation;
+
+import java.io.File;
+import java.io.OutputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import org.eclipse.core.runtime.*;
+import org.eclipse.equinox.internal.p2.reconciler.dropins.Activator;
+import org.eclipse.equinox.internal.provisional.p2.artifact.repository.*;
+import org.eclipse.equinox.internal.provisional.p2.core.ProvisionException;
+import org.eclipse.equinox.internal.provisional.p2.directorywatcher.DirectoryWatcher;
+import org.eclipse.equinox.internal.provisional.p2.directorywatcher.RepositoryListener;
+import org.eclipse.equinox.internal.provisional.p2.metadata.IArtifactKey;
+import org.eclipse.equinox.internal.provisional.spi.p2.artifact.repository.SimpleArtifactRepositoryFactory;
+import org.eclipse.equinox.internal.provisional.spi.p2.core.repository.AbstractRepository;
+import org.osgi.framework.BundleContext;
+
+public class ExtensionLocationArtifactRepository extends AbstractRepository implements IFileArtifactRepository {
+
+	//private static final String PROFILE_EXTENSION = "profile.extension"; //$NON-NLS-1$
+	private static final String ECLIPSE = "eclipse"; //$NON-NLS-1$
+	private static final String FEATURES = "features"; //$NON-NLS-1$
+	private static final String PLUGINS = "plugins"; //$NON-NLS-1$
+	private static final String FILE = "file"; //$NON-NLS-1$
+	private final IFileArtifactRepository artifactRepository;
+
+	public ExtensionLocationArtifactRepository(URL location, IProgressMonitor monitor) throws ProvisionException {
+		super("Extension: " + location.toExternalForm(), null, null, location, null, null); //$NON-NLS-1$
+
+		File base = getBaseDirectory(location);
+		File plugins = new File(base, PLUGINS);
+		File features = new File(base, FEATURES);
+
+		BundleContext context = Activator.getContext();
+		String stateDirName = Integer.toString(location.toExternalForm().hashCode());
+		File bundleData = context.getDataFile(null);
+		File stateDir = new File(bundleData, stateDirName);
+		URL localRepositoryURL;
+		try {
+			localRepositoryURL = stateDir.toURL();
+		} catch (MalformedURLException e) {
+			// unexpected
+			throw new ProvisionException(new Status(IStatus.ERROR, Activator.ID, "Failed to create local repository", e)); //$NON-NLS-1$
+		}
+
+		artifactRepository = (IFileArtifactRepository) initializeArtifactRepository(localRepositoryURL, "extension location implementation - " + location.toExternalForm()); //$NON-NLS-1$
+
+		DirectoryWatcher watcher = new DirectoryWatcher(new File[] {plugins, features});
+		RepositoryListener listener = new RepositoryListener(context, null, artifactRepository);
+		watcher.addListener(listener);
+		watcher.poll();
+	}
+
+	private IArtifactRepository initializeArtifactRepository(URL stateDirURL, String repositoryName) {
+		SimpleArtifactRepositoryFactory factory = new SimpleArtifactRepositoryFactory();
+		try {
+			return factory.load(stateDirURL, null);
+
+		} catch (ProvisionException e) {
+			//fall through and create a new repository
+		}
+		IArtifactRepository repository = factory.create(stateDirURL, repositoryName, null);
+		//repository.setProperty(PROFILE_EXTENSION, "true");
+		return repository;
+	}
+
+	public static void validate(URL location, IProgressMonitor monitor) throws ProvisionException {
+		getBaseDirectory(location);
+	}
+
+	public static File getBaseDirectory(URL url) throws ProvisionException {
+		if (url.getProtocol() != FILE)
+			throw new ProvisionException(new Status(IStatus.ERROR, Activator.ID, ProvisionException.REPOSITORY_NOT_FOUND, "location must use file protocol", null));
+
+		File base = new File(url.getPath());
+		if (!base.isDirectory())
+			throw new ProvisionException(new Status(IStatus.ERROR, Activator.ID, ProvisionException.REPOSITORY_NOT_FOUND, "location not a directory", null));
+
+		if (isBaseDirectory(base))
+			return base;
+
+		File eclipseBase = new File(base, ECLIPSE);
+		if (isBaseDirectory(eclipseBase))
+			return eclipseBase;
+
+		throw new ProvisionException(new Status(IStatus.ERROR, Activator.ID, ProvisionException.REPOSITORY_NOT_FOUND, "location is not an extension", null));
+	}
+
+	private static boolean isBaseDirectory(File base) {
+		File plugins = new File(base, PLUGINS);
+		File features = new File(base, FEATURES);
+
+		return plugins.isDirectory() || features.isDirectory();
+	}
+
+	public void addDescriptor(IArtifactDescriptor descriptor) {
+		throw new UnsupportedOperationException();
+	}
+
+	public void addDescriptors(IArtifactDescriptor[] descriptors) {
+		throw new UnsupportedOperationException();
+	}
+
+	public void removeAll() {
+		throw new UnsupportedOperationException();
+	}
+
+	public void removeDescriptor(IArtifactDescriptor descriptor) {
+		throw new UnsupportedOperationException();
+	}
+
+	public void removeDescriptor(IArtifactKey key) {
+		throw new UnsupportedOperationException();
+	}
+
+	public boolean contains(IArtifactDescriptor descriptor) {
+		return artifactRepository.contains(descriptor);
+	}
+
+	public boolean contains(IArtifactKey key) {
+		return artifactRepository.contains(key);
+	}
+
+	public IStatus getArtifact(IArtifactDescriptor descriptor, OutputStream destination, IProgressMonitor monitor) {
+		return artifactRepository.getArtifact(descriptor, destination, monitor);
+	}
+
+	public IArtifactDescriptor[] getArtifactDescriptors(IArtifactKey key) {
+		return artifactRepository.getArtifactDescriptors(key);
+	}
+
+	public IArtifactKey[] getArtifactKeys() {
+		return artifactRepository.getArtifactKeys();
+	}
+
+	public IStatus getArtifacts(IArtifactRequest[] requests, IProgressMonitor monitor) {
+		return artifactRepository.getArtifacts(requests, monitor);
+	}
+
+	public OutputStream getOutputStream(IArtifactDescriptor descriptor) throws ProvisionException {
+		return artifactRepository.getOutputStream(descriptor);
+	}
+
+	public File getArtifactFile(IArtifactKey key) {
+		return artifactRepository.getArtifactFile(key);
+	}
+
+	public File getArtifactFile(IArtifactDescriptor descriptor) {
+		return artifactRepository.getArtifactFile(descriptor);
+	}
+}
diff --git a/bundles/org.eclipse.equinox.p2.reconciler.dropins/src/org/eclipse/equinox/internal/p2/extensionlocation/ExtensionLocationArtifactRepositoryFactory.java b/bundles/org.eclipse.equinox.p2.reconciler.dropins/src/org/eclipse/equinox/internal/p2/extensionlocation/ExtensionLocationArtifactRepositoryFactory.java
new file mode 100644
index 0000000..83cb924
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.reconciler.dropins/src/org/eclipse/equinox/internal/p2/extensionlocation/ExtensionLocationArtifactRepositoryFactory.java
@@ -0,0 +1,28 @@
+package org.eclipse.equinox.internal.p2.extensionlocation;
+
+import java.net.URL;
+import org.eclipse.core.runtime.*;
+import org.eclipse.equinox.internal.provisional.p2.artifact.repository.IArtifactRepository;
+import org.eclipse.equinox.internal.provisional.p2.core.ProvisionException;
+import org.eclipse.equinox.internal.provisional.spi.p2.artifact.repository.IArtifactRepositoryFactory;
+
+public class ExtensionLocationArtifactRepositoryFactory implements IArtifactRepositoryFactory {
+
+	public IArtifactRepository create(URL location, String name, String type) throws ProvisionException {
+		return null;
+	}
+
+	public IArtifactRepository load(URL location, IProgressMonitor monitor) throws ProvisionException {
+		return new ExtensionLocationArtifactRepository(location, monitor);
+	}
+
+	public IStatus validate(URL location, IProgressMonitor monitor) {
+		try {
+			ExtensionLocationArtifactRepository.validate(location, monitor);
+		} catch (ProvisionException e) {
+			return e.getStatus();
+		}
+		return Status.OK_STATUS;
+	}
+
+}
diff --git a/bundles/org.eclipse.equinox.p2.reconciler.dropins/src/org/eclipse/equinox/internal/p2/extensionlocation/ExtensionLocationMetadataRepository.java b/bundles/org.eclipse.equinox.p2.reconciler.dropins/src/org/eclipse/equinox/internal/p2/extensionlocation/ExtensionLocationMetadataRepository.java
new file mode 100644
index 0000000..8004e2d
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.reconciler.dropins/src/org/eclipse/equinox/internal/p2/extensionlocation/ExtensionLocationMetadataRepository.java
@@ -0,0 +1,108 @@
+package org.eclipse.equinox.internal.p2.extensionlocation;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URL;
+import org.eclipse.core.runtime.*;
+import org.eclipse.equinox.internal.p2.reconciler.dropins.Activator;
+import org.eclipse.equinox.internal.provisional.p2.core.ProvisionException;
+import org.eclipse.equinox.internal.provisional.p2.directorywatcher.DirectoryWatcher;
+import org.eclipse.equinox.internal.provisional.p2.directorywatcher.RepositoryListener;
+import org.eclipse.equinox.internal.provisional.p2.metadata.IInstallableUnit;
+import org.eclipse.equinox.internal.provisional.p2.metadata.repository.IMetadataRepository;
+import org.eclipse.equinox.internal.provisional.p2.query.Collector;
+import org.eclipse.equinox.internal.provisional.p2.query.Query;
+import org.eclipse.equinox.internal.provisional.spi.p2.core.repository.AbstractRepository;
+import org.eclipse.equinox.internal.provisional.spi.p2.metadata.repository.SimpleMetadataRepositoryFactory;
+import org.osgi.framework.BundleContext;
+
+public class ExtensionLocationMetadataRepository extends AbstractRepository implements IMetadataRepository {
+
+	private static final String ECLIPSE = "eclipse"; //$NON-NLS-1$
+	private static final String FEATURES = "features"; //$NON-NLS-1$
+	private static final String PLUGINS = "plugins"; //$NON-NLS-1$
+	private static final String FILE = "file"; //$NON-NLS-1$
+	private final IMetadataRepository metadataRepository;
+
+	public ExtensionLocationMetadataRepository(URL location, IProgressMonitor monitor) throws ProvisionException {
+		super("Extension: " + location.toExternalForm(), null, null, location, null, null); //$NON-NLS-1$
+
+		File base = getBaseDirectory(location);
+		File plugins = new File(base, PLUGINS);
+		File features = new File(base, FEATURES);
+
+		BundleContext context = Activator.getContext();
+		String stateDirName = Integer.toString(location.toExternalForm().hashCode());
+		File bundleData = context.getDataFile(null);
+		File stateDir = new File(bundleData, stateDirName);
+		URL localRepositoryURL;
+		try {
+			localRepositoryURL = stateDir.toURL();
+		} catch (MalformedURLException e) {
+			// unexpected
+			throw new ProvisionException(new Status(IStatus.ERROR, Activator.ID, "Failed to create local repository", e)); //$NON-NLS-1$
+		}
+
+		metadataRepository = initializeMetadataRepository(localRepositoryURL, "extension location implementation - " + location.toExternalForm()); //$NON-NLS-1$
+
+		DirectoryWatcher watcher = new DirectoryWatcher(new File[] {plugins, features});
+		RepositoryListener listener = new RepositoryListener(context, metadataRepository, null);
+		watcher.addListener(listener);
+		watcher.poll();
+	}
+
+	private IMetadataRepository initializeMetadataRepository(URL stateDirURL, String repositoryName) {
+		SimpleMetadataRepositoryFactory factory = new SimpleMetadataRepositoryFactory();
+		try {
+			return factory.load(stateDirURL, null);
+		} catch (ProvisionException e) {
+			//fall through and create a new repository
+		}
+		return factory.create(stateDirURL, repositoryName, null);
+	}
+
+	public void addInstallableUnits(IInstallableUnit[] installableUnits) {
+		throw new UnsupportedOperationException();
+	}
+
+	public void removeAll() {
+		throw new UnsupportedOperationException();
+	}
+
+	public boolean removeInstallableUnits(Query query, IProgressMonitor monitor) {
+		throw new UnsupportedOperationException();
+	}
+
+	public Collector query(Query query, Collector collector, IProgressMonitor monitor) {
+		return metadataRepository.query(query, collector, monitor);
+	}
+
+	public static void validate(URL location, IProgressMonitor monitor) throws ProvisionException {
+		getBaseDirectory(location);
+	}
+
+	public static File getBaseDirectory(URL url) throws ProvisionException {
+		if (url.getProtocol() != FILE)
+			throw new ProvisionException(new Status(IStatus.ERROR, Activator.ID, ProvisionException.REPOSITORY_NOT_FOUND, "location must use file protocol", null));
+
+		File base = new File(url.getPath());
+		if (!base.isDirectory())
+			throw new ProvisionException(new Status(IStatus.ERROR, Activator.ID, ProvisionException.REPOSITORY_NOT_FOUND, "location not a directory", null));
+
+		if (isBaseDirectory(base))
+			return base;
+
+		File eclipseBase = new File(base, ECLIPSE);
+		if (isBaseDirectory(eclipseBase))
+			return eclipseBase;
+
+		throw new ProvisionException(new Status(IStatus.ERROR, Activator.ID, ProvisionException.REPOSITORY_NOT_FOUND, "location is not an extension", null));
+	}
+
+	private static boolean isBaseDirectory(File base) {
+		File plugins = new File(base, PLUGINS);
+		File features = new File(base, FEATURES);
+
+		return plugins.isDirectory() || features.isDirectory();
+	}
+}
diff --git a/bundles/org.eclipse.equinox.p2.reconciler.dropins/src/org/eclipse/equinox/internal/p2/extensionlocation/ExtensionLocationMetadataRepositoryFactory.java b/bundles/org.eclipse.equinox.p2.reconciler.dropins/src/org/eclipse/equinox/internal/p2/extensionlocation/ExtensionLocationMetadataRepositoryFactory.java
new file mode 100644
index 0000000..31da09c
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.reconciler.dropins/src/org/eclipse/equinox/internal/p2/extensionlocation/ExtensionLocationMetadataRepositoryFactory.java
@@ -0,0 +1,28 @@
+package org.eclipse.equinox.internal.p2.extensionlocation;
+
+import java.net.URL;
+import org.eclipse.core.runtime.*;
+import org.eclipse.equinox.internal.provisional.p2.core.ProvisionException;
+import org.eclipse.equinox.internal.provisional.p2.metadata.repository.IMetadataRepository;
+import org.eclipse.equinox.internal.provisional.spi.p2.metadata.repository.IMetadataRepositoryFactory;
+
+public class ExtensionLocationMetadataRepositoryFactory implements IMetadataRepositoryFactory {
+
+	public IMetadataRepository create(URL location, String name, String type) throws ProvisionException {
+		return null;
+	}
+
+	public IMetadataRepository load(URL location, IProgressMonitor monitor) throws ProvisionException {
+		return new ExtensionLocationMetadataRepository(location, monitor);
+	}
+
+	public IStatus validate(URL location, IProgressMonitor monitor) {
+		try {
+			ExtensionLocationMetadataRepository.validate(location, monitor);
+		} catch (ProvisionException e) {
+			return e.getStatus();
+		}
+		return Status.OK_STATUS;
+	}
+
+}
diff --git a/bundles/org.eclipse.equinox.p2.reconciler.dropins/src/org/eclipse/equinox/internal/p2/reconciler/dropins/Activator.java b/bundles/org.eclipse.equinox.p2.reconciler.dropins/src/org/eclipse/equinox/internal/p2/reconciler/dropins/Activator.java
index 1a46573..bddfb05 100644
--- a/bundles/org.eclipse.equinox.p2.reconciler.dropins/src/org/eclipse/equinox/internal/p2/reconciler/dropins/Activator.java
+++ b/bundles/org.eclipse.equinox.p2.reconciler.dropins/src/org/eclipse/equinox/internal/p2/reconciler/dropins/Activator.java
@@ -14,26 +14,75 @@
 import java.net.URL;
 import java.util.*;
 import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.equinox.internal.provisional.p2.artifact.repository.IArtifactRepository;
+import org.eclipse.equinox.internal.provisional.p2.artifact.repository.IArtifactRepositoryManager;
 import org.eclipse.equinox.internal.provisional.p2.core.ProvisionException;
 import org.eclipse.equinox.internal.provisional.p2.directorywatcher.DirectoryWatcher;
-import org.eclipse.equinox.internal.provisional.p2.directorywatcher.RepositoryListener;
 import org.eclipse.equinox.internal.provisional.p2.engine.IProfile;
 import org.eclipse.equinox.internal.provisional.p2.engine.IProfileRegistry;
 import org.eclipse.equinox.internal.provisional.p2.metadata.repository.IMetadataRepository;
+import org.eclipse.equinox.internal.provisional.p2.metadata.repository.IMetadataRepositoryManager;
 import org.osgi.framework.*;
 import org.osgi.service.packageadmin.PackageAdmin;
 
 public class Activator implements BundleActivator {
 
+	public static final String ID = "org.eclipse.equinox.p2.reconciler.dropins"; //$NON-NLS-1$
 	private static final String DROPINS_DIRECTORY = "org.eclipse.equinox.p2.reconciler.dropins.directory"; //$NON-NLS-1$
 	private static final String OSGI_CONFIGURATION_AREA = "osgi.configuration.area"; //$NON-NLS-1$
 	private static final String DROPINS = "dropins"; //$NON-NLS-1$
-	private static final String PROFILE_EXTENSION = "profile.extension"; //$NON-NLS-1$
+	//	private static final String PROFILE_EXTENSION = "profile.extension"; //$NON-NLS-1$
 	private static PackageAdmin packageAdmin;
 	private static BundleContext bundleContext;
 	private ServiceReference packageAdminRef;
 	private List watchers = new ArrayList();
-	private static IMetadataRepository dropinRepository;
+	private static IMetadataRepository[] dropinRepositories;
+	private static IMetadataRepository[] configurationRepositories;
+	private static IMetadataRepository[] linksRepositories;
+
+	/**
+	 * Helper method to load a metadata repository from the specified URL.
+	 * This method never returns <code>null</code>.
+	 * 
+	 * @throws IllegalStateException
+	 * @throws ProvisionException 
+	 */
+	public static IMetadataRepository loadMetadataRepository(URL repoURL) throws ProvisionException {
+		BundleContext context = getContext();
+		ServiceReference reference = context.getServiceReference(IMetadataRepositoryManager.class.getName());
+		IMetadataRepositoryManager manager = null;
+		if (reference != null)
+			manager = (IMetadataRepositoryManager) context.getService(reference);
+		if (manager == null)
+			throw new IllegalStateException("MetadataRepositoryManager not registered.");
+		try {
+			return manager.loadRepository(repoURL, null);
+		} finally {
+			context.ungetService(reference);
+		}
+	}
+
+	/**
+	 * Helper method to load an artifact repository from the given URL.
+	 * This method never returns <code>null</code>.
+	 * 
+	 * @throws IllegalStateException
+	 * @throws ProvisionException
+	 */
+	public static IArtifactRepository loadArtifactRepository(URL repoURL) throws ProvisionException {
+		BundleContext context = getContext();
+		ServiceReference reference = context.getServiceReference(IArtifactRepositoryManager.class.getName());
+		IArtifactRepositoryManager manager = null;
+		if (reference != null)
+			manager = (IArtifactRepositoryManager) context.getService(reference);
+		if (manager == null)
+			throw new IllegalStateException("ArtifactRepositoryManager not registered.");
+		try {
+			return manager.loadRepository(repoURL, null);
+		} finally {
+			context.ungetService(reference);
+		}
+	}
 
 	/* (non-Javadoc)
 	 * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
@@ -55,9 +104,9 @@
 
 		// create the watcher for the "drop-ins" folder
 		watchDropins(profile);
-
-		// create watchers for the sites specified in the platform.xml
-		watchConfiguration();
+		// keep an eye on the platform.xml
+		if (false)
+			watchConfiguration();
 
 		synchronize(new ArrayList(0), null);
 	}
@@ -73,14 +122,21 @@
 	/*
 	 * Synchronize the profile.
 	 */
-	public static void synchronize(List extraRepositories, IProgressMonitor monitor) {
+	public static synchronized void synchronize(List extraRepositories, IProgressMonitor monitor) {
 		IProfile profile = getCurrentProfile(bundleContext);
 		if (profile == null)
 			return;
 		// create the profile synchronizer on all available repositories
-		List repositories = new ArrayList(extraRepositories);
-		if (dropinRepository != null)
-			repositories.add(dropinRepository);
+		Set repositories = new HashSet(extraRepositories);
+		if (dropinRepositories != null)
+			repositories.addAll(Arrays.asList(dropinRepositories));
+
+		if (configurationRepositories != null)
+			repositories.addAll(Arrays.asList(configurationRepositories));
+
+		if (linksRepositories != null)
+			repositories.addAll(Arrays.asList(linksRepositories));
+
 		ProfileSynchronizer synchronizer = new ProfileSynchronizer(profile, repositories);
 		synchronizer.synchronize(monitor);
 	}
@@ -88,50 +144,43 @@
 	/*
 	 * Watch the platform.xml file.
 	 */
-	private void watchConfiguration() throws ProvisionException {
+	private void watchConfiguration() {
 		File configFile = new File("configuration/org.eclipse.update/platform.xml"); //$NON-NLS-1$
 		DirectoryWatcher watcher = new DirectoryWatcher(configFile.getParentFile());
 		try {
 			PlatformXmlListener listener = new PlatformXmlListener(configFile);
 			watcher.addListener(listener);
+			watcher.poll();
+			List repositories = listener.getMetadataRepositories();
+			if (repositories != null)
+				configurationRepositories = (IMetadataRepository[]) repositories.toArray(new IMetadataRepository[0]);
 		} catch (ProvisionException e) {
 			// TODO proper logging
 			e.printStackTrace();
 		}
-		watchers.add(watcher);
-		watcher.start();
-
-		// pay attention to the links/ folder too. this is only needed on startup though since
-		// any other changes during execution will be reflected in the platform.xml file
-		LinksManager manager = new LinksManager();
-		manager.synchronize(configFile, new File("links"));
 	}
 
 	/*
 	 * Create a new directory watcher with a repository listener on the drop-ins folder. 
 	 */
 	private void watchDropins(IProfile profile) {
-		File folder = getWatchedDirectory(bundleContext);
-		if (folder == null)
+		List directories = new ArrayList();
+		File dropinsDirectory = getDropinsDirectory();
+		if (dropinsDirectory != null)
+			directories.add(dropinsDirectory);
+		File linksDirectory = getLinksDirectory();
+		if (linksDirectory != null)
+			directories.add(linksDirectory);
+		if (directories.isEmpty())
 			return;
 
-		RepositoryListener listener = new RepositoryListener(Activator.getContext(), Integer.toString(folder.hashCode()));
-		listener.getArtifactRepository().setProperty(PROFILE_EXTENSION, profile.getProfileId());
-
-		List folders = new ArrayList();
-		folders.add(folder);
-		File eclipseFeatures = new File(folder, "eclipse/features");
-		if (eclipseFeatures.isDirectory())
-			folders.add(eclipseFeatures);
-		File eclipsePlugins = new File(folder, "eclipse/plugins");
-		if (eclipsePlugins.isDirectory())
-			folders.add(eclipsePlugins);
-
-		DirectoryWatcher watcher = new DirectoryWatcher((File[]) folders.toArray(new File[folders.size()]));
+		DropinsRepositoryListener listener = new DropinsRepositoryListener(Activator.getContext(), "dropins:" + dropinsDirectory.getAbsolutePath());
+		//		listener.getArtifactRepository().setProperty(PROFILE_EXTENSION, profile.getProfileId());
+		DirectoryWatcher watcher = new DirectoryWatcher((File[]) directories.toArray(new File[directories.size()]));
 		watcher.addListener(listener);
 		watcher.poll();
 
-		dropinRepository = listener.getMetadataRepository();
+		dropinRepositories = listener.getMetadataRepositories();
 	}
 
 	/* (non-Javadoc)
@@ -154,15 +203,27 @@
 		return bundleContext;
 	}
 
-	public static File getWatchedDirectory(BundleContext context) {
-		String watchedDirectoryProperty = context.getProperty(DROPINS_DIRECTORY);
+	private static File getLinksDirectory() {
+		try {
+			//TODO: a proper install area would be better. osgi.install.area is relative to the framework jar
+			URL baseURL = new URL(bundleContext.getProperty(OSGI_CONFIGURATION_AREA));
+			URL folderURL = new URL(baseURL, "../links"); //$NON-NLS-1$
+			return new File(folderURL.getPath());
+		} catch (MalformedURLException e) {
+			e.printStackTrace();
+		}
+		return null;
+	}
+
+	public static File getDropinsDirectory() {
+		String watchedDirectoryProperty = bundleContext.getProperty(DROPINS_DIRECTORY);
 		if (watchedDirectoryProperty != null) {
 			File folder = new File(watchedDirectoryProperty);
 			return folder;
 		}
 		try {
 			//TODO: a proper install area would be better. osgi.install.area is relative to the framework jar
-			URL baseURL = new URL(context.getProperty(OSGI_CONFIGURATION_AREA));
+			URL baseURL = new URL(bundleContext.getProperty(OSGI_CONFIGURATION_AREA));
 			URL folderURL = new URL(baseURL, "../" + DROPINS); //$NON-NLS-1$
 			File folder = new File(folderURL.getPath());
 			return folder;
diff --git a/bundles/org.eclipse.equinox.p2.reconciler.dropins/src/org/eclipse/equinox/internal/p2/reconciler/dropins/DropinsRepositoryListener.java b/bundles/org.eclipse.equinox.p2.reconciler.dropins/src/org/eclipse/equinox/internal/p2/reconciler/dropins/DropinsRepositoryListener.java
new file mode 100644
index 0000000..9c4ce05
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.reconciler.dropins/src/org/eclipse/equinox/internal/p2/reconciler/dropins/DropinsRepositoryListener.java
@@ -0,0 +1,239 @@
+package org.eclipse.equinox.internal.p2.reconciler.dropins;
+
+import java.io.*;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.*;
+import org.eclipse.equinox.internal.provisional.p2.artifact.repository.IArtifactRepository;
+import org.eclipse.equinox.internal.provisional.p2.artifact.repository.IArtifactRepositoryManager;
+import org.eclipse.equinox.internal.provisional.p2.core.ProvisionException;
+import org.eclipse.equinox.internal.provisional.p2.core.repository.IRepository;
+import org.eclipse.equinox.internal.provisional.p2.directorywatcher.RepositoryListener;
+import org.eclipse.equinox.internal.provisional.p2.metadata.repository.IMetadataRepository;
+import org.eclipse.equinox.internal.provisional.p2.metadata.repository.IMetadataRepositoryManager;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+
+public class DropinsRepositoryListener extends RepositoryListener {
+
+	private static final String DROPIN_ARTIFACT_REPOSITORIES = "dropin.artifactRepositories"; //$NON-NLS-1$
+	private static final String DROPIN_METADATA_REPOSITORIES = "dropin.metadataRepositories"; //$NON-NLS-1$
+	private static final String PIPE = "|"; //$NON-NLS-1$
+	private BundleContext context;
+	private List metadataRepositories = new ArrayList();
+	private List artifactRepositories = new ArrayList();
+
+	public DropinsRepositoryListener(BundleContext context, String repositoryName) {
+		super(context, repositoryName);
+		this.context = context;
+	}
+
+	public boolean isInterested(File file) {
+		if (file.isDirectory())
+			return true;
+
+		String name = file.getName();
+		return name.endsWith(".jar") || name.endsWith(".zip") || name.endsWith(".link");
+	}
+
+	public boolean added(File file) {
+		if (super.isInterested(file))
+			return super.added(file);
+
+		URL repositoryURL = createRepositoryURL(file);
+		if (repositoryURL != null) {
+			loadMetadataRepository(repositoryURL);
+			loadArtifactRepository(repositoryURL);
+		}
+		return true;
+	}
+
+	public boolean changed(File file) {
+		if (super.isInterested(file))
+			return super.added(file);
+
+		URL repositoryURL = createRepositoryURL(file);
+		if (repositoryURL != null) {
+			loadMetadataRepository(repositoryURL);
+			loadArtifactRepository(repositoryURL);
+		}
+		return true;
+	}
+
+	private String getLinkPath(File file) {
+		Properties links = new Properties();
+		try {
+			InputStream input = new BufferedInputStream(new FileInputStream(file));
+			try {
+				links.load(input);
+			} finally {
+				input.close();
+			}
+		} catch (IOException e) {
+			// ignore
+		}
+		String path = links.getProperty("path");
+		if (path == null) {
+			// log
+			return null;
+		}
+
+		// parse out link information
+		if (path.startsWith("r ")) { //$NON-NLS-1$
+			path = path.substring(2).trim();
+		} else if (path.startsWith("rw ")) { //$NON-NLS-1$
+			path = path.substring(3).trim();
+		} else {
+			path = path.trim();
+		}
+		return path;
+	}
+
+	private URL createRepositoryURL(File file) {
+		try {
+			if (file.getName().endsWith(".link")) {
+				String path = getLinkPath(file);
+				// todo log
+				if (path == null)
+					return null;
+				file = new File(path);
+				if (!file.isAbsolute())
+					file = new File(file, path).getCanonicalFile();
+			}
+
+			URL repositoryURL = file.toURL();
+			if (file.getName().endsWith(".zip") || file.getName().endsWith(".jar")) {
+				repositoryURL = new URL("jar:" + repositoryURL.toString() + "!/");
+			}
+			return repositoryURL;
+		} catch (IOException e) {
+			// todo log			
+		}
+		return null;
+	}
+
+	public void loadMetadataRepository(URL repoURL) {
+		try {
+			metadataRepositories.add(Activator.loadMetadataRepository(repoURL));
+		} catch (ProvisionException e) {
+			//TODO: log
+			// ignore
+		}
+	}
+
+	public void loadArtifactRepository(URL repoURL) {
+		try {
+			artifactRepositories.add(Activator.loadArtifactRepository(repoURL));
+		} catch (ProvisionException e) {
+			//TODO: log
+			// ignore
+		}
+	}
+
+	public void stopPoll() {
+
+		synchronizeDropinMetadataRepositories();
+		synchronizeDropinArtifactRepositories();
+
+		super.stopPoll();
+	}
+
+	private void synchronizeDropinMetadataRepositories() {
+		List currentRepositories = new ArrayList();
+		for (Iterator it = metadataRepositories.iterator(); it.hasNext();) {
+			IMetadataRepository repository = (IMetadataRepository) it.next();
+			String urlString = repository.getLocation().toExternalForm();
+			currentRepositories.add(urlString);
+		}
+		List previousRepositories = getListRepositoryProperty(getMetadataRepository(), DROPIN_METADATA_REPOSITORIES);
+		for (Iterator iterator = previousRepositories.iterator(); iterator.hasNext();) {
+			String repository = (String) iterator.next();
+			if (!currentRepositories.contains(repository))
+				removeMetadataRepository(repository);
+		}
+		setListRepositoryProperty(getMetadataRepository(), DROPIN_METADATA_REPOSITORIES, currentRepositories);
+	}
+
+	private void removeMetadataRepository(String urlString) {
+		ServiceReference reference = context.getServiceReference(IMetadataRepositoryManager.class.getName());
+		IMetadataRepositoryManager manager = null;
+		if (reference != null)
+			manager = (IMetadataRepositoryManager) context.getService(reference);
+		if (manager == null)
+			throw new IllegalStateException("MetadataRepositoryManager not registered.");
+
+		try {
+			manager.removeRepository(new URL(urlString));
+		} catch (MalformedURLException e) {
+			// TODO: log
+			// ignore
+		} finally {
+			context.ungetService(reference);
+		}
+	}
+
+	private void synchronizeDropinArtifactRepositories() {
+		List currentRepositories = new ArrayList();
+		for (Iterator it = artifactRepositories.iterator(); it.hasNext();) {
+			IArtifactRepository repository = (IArtifactRepository) it.next();
+			String urlString = repository.getLocation().toExternalForm();
+			currentRepositories.add(urlString);
+		}
+		List previousRepositories = getListRepositoryProperty(getArtifactRepository(), DROPIN_ARTIFACT_REPOSITORIES);
+		for (Iterator iterator = previousRepositories.iterator(); iterator.hasNext();) {
+			String repository = (String) iterator.next();
+			if (!currentRepositories.contains(repository))
+				removeArtifactRepository(repository);
+		}
+		setListRepositoryProperty(getArtifactRepository(), DROPIN_ARTIFACT_REPOSITORIES, currentRepositories);
+	}
+
+	public void removeArtifactRepository(String urlString) {
+		ServiceReference reference = context.getServiceReference(IArtifactRepositoryManager.class.getName());
+		IArtifactRepositoryManager manager = null;
+		if (reference != null)
+			manager = (IArtifactRepositoryManager) context.getService(reference);
+		if (manager == null)
+			throw new IllegalStateException("ArtifactRepositoryManager not registered.");
+
+		try {
+			manager.removeRepository(new URL(urlString));
+		} catch (MalformedURLException e) {
+			//TODO: log
+			// ignore
+		} finally {
+			context.ungetService(reference);
+		}
+	}
+
+	private List getListRepositoryProperty(IRepository repository, String key) {
+		List listProperty = new ArrayList();
+		String dropinRepositories = (String) repository.getProperties().get(key);
+		if (dropinRepositories != null) {
+			StringTokenizer tokenizer = new StringTokenizer(dropinRepositories, PIPE); //$NON-NLS-1$			
+			while (tokenizer.hasMoreTokens()) {
+				listProperty.add(tokenizer.nextToken());
+			}
+		}
+		return listProperty;
+	}
+
+	private void setListRepositoryProperty(IRepository repository, String key, List listProperty) {
+		StringBuffer buffer = new StringBuffer();
+		for (Iterator it = listProperty.iterator(); it.hasNext();) {
+			String repositoryString = (String) it.next();
+			buffer.append(repositoryString);
+			if (it.hasNext())
+				buffer.append(PIPE);
+		}
+		String value = (buffer.length() == 0) ? null : buffer.toString();
+		repository.setProperty(key, value);
+	}
+
+	public IMetadataRepository[] getMetadataRepositories() {
+		List result = new ArrayList(metadataRepositories);
+		result.add(getMetadataRepository());
+		return (IMetadataRepository[]) result.toArray(new IMetadataRepository[0]);
+	}
+
+}
diff --git a/bundles/org.eclipse.equinox.p2.reconciler.dropins/src/org/eclipse/equinox/internal/p2/reconciler/dropins/LinksManager.java b/bundles/org.eclipse.equinox.p2.reconciler.dropins/src/org/eclipse/equinox/internal/p2/reconciler/dropins/LinksManager.java
deleted file mode 100644
index 4a494d2..0000000
--- a/bundles/org.eclipse.equinox.p2.reconciler.dropins/src/org/eclipse/equinox/internal/p2/reconciler/dropins/LinksManager.java
+++ /dev/null
@@ -1,177 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2008 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.reconciler.dropins;
-
-import java.io.*;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.Iterator;
-import java.util.Properties;
-import org.eclipse.equinox.internal.p2.update.*;
-import org.eclipse.equinox.internal.provisional.p2.core.ProvisionException;
-
-/**
- * @since 1.0
- */
-public class LinksManager {
-
-	private static final String EXTENSION_LINK = ".link"; //$NON-NLS-1$
-	private static final String PATH_PROPERTY = "path"; //$NON-NLS-1$
-	private static final String PLATFORM_PROTOCOL = "platform:"; //$NON-NLS-1$
-	private static final String ECLIPSE_FOLDER = "eclipse"; //$NON-NLS-1$
-	private String defaultPolicy;
-	private Configuration configuration;
-	private boolean dirty = false;
-
-	/*
-	 * If one site has a MANAGED_ONLY policy, then newly discovered sites must also have
-	 * the same thing. Otherwise, they will have a policy of USER_EXCLUDE.
-	 */
-	private String getDefaultPolicy() {
-		if (defaultPolicy == null) {
-			for (Iterator iter = configuration.getSites().iterator(); defaultPolicy == null && iter.hasNext();) {
-				if (Site.POLICY_MANAGED_ONLY.equals(((Site) iter.next()).getPolicy()))
-					defaultPolicy = Site.POLICY_MANAGED_ONLY;
-			}
-			defaultPolicy = Site.POLICY_USER_EXCLUDE;
-		}
-		return defaultPolicy;
-	}
-
-	/*
-	 * Synchronize the given configuration file with the files that are in the specified links folder.
-	 * If any extension locations from the links folder are missing from the file, then update
-	 * the configuration.
-	 */
-	public void synchronize(File configurationFile, File linksFolder) throws ProvisionException {
-		if (!configurationFile.exists() || !linksFolder.exists())
-			return;
-
-		// read the existing configuration from disk
-		configuration = ConfigurationParser.parse(configurationFile, null);
-		if (configuration == null)
-			return;
-
-		// get the list of extension locations from the links folder
-		linksFolder.listFiles(new FileFilter() {
-			public boolean accept(File file) {
-				if (file.isFile() && file.getName().endsWith(EXTENSION_LINK))
-					configure(file);
-				return false;
-			}
-		});
-
-		// write out a new file if there were any changes.
-		if (dirty)
-			ConfigurationWriter.save(configuration, configurationFile, null);
-		dirty = false;
-	}
-
-	/*
-	 * Roughly copied from PlatformConfiguration#configureExternalLinkSite in 
-	 * Update Configurator.
-	 */
-	void configure(File location) {
-		String path = readExtension(location);
-		boolean updateable = true;
-
-		// parse out link information
-		if (path.startsWith("r ")) { //$NON-NLS-1$
-			updateable = false;
-			path = path.substring(2).trim();
-		} else if (path.startsWith("rw ")) { //$NON-NLS-1$
-			path = path.substring(3).trim();
-		} else {
-			path = path.trim();
-		}
-
-		URL url;
-		// 	make sure we have a valid link specification
-		try {
-			File siteFile = new File(path);
-			siteFile = new File(siteFile, ECLIPSE_FOLDER);
-			url = siteFile.toURL();
-			if (findConfiguredSite(url) != null)
-				// linked site is already known
-				return;
-		} catch (MalformedURLException e) {
-			// ignore bad links ...
-			e.printStackTrace();
-			return;
-		}
-
-		Site site = new Site();
-		site.setLinkFile(location.getAbsolutePath());
-		site.setEnabled(true);
-		site.setPolicy(getDefaultPolicy());
-		site.setUpdateable(updateable);
-		site.setUrl(url.toExternalForm());
-		configuration.add(site);
-		dirty = true;
-	}
-
-	/*
-	 * Look through the list of sites already known to this configuration
-	 * and determine if there is one with the given URL.
-	 */
-	private Site findConfiguredSite(URL url) {
-		String urlString = url.toExternalForm();
-		Site result = internalFindConfiguredSite(urlString);
-		if (result != null)
-			return result;
-		// try again with fixed URLs since they can be tricky
-		try {
-			urlString = Utils.decode(urlString, "UTF-8"); //$NON-NLS-1$
-		} catch (UnsupportedEncodingException e) {
-			// ignore
-		}
-		urlString = Utils.canonicalizeURL(urlString);
-		return internalFindConfiguredSite(urlString);
-	}
-
-	private Site internalFindConfiguredSite(String url) {
-		for (Iterator iter = configuration.getSites().iterator(); iter.hasNext();) {
-			Site site = (Site) iter.next();
-			String urlString = site.getUrl();
-			urlString = Utils.canonicalizeURL(urlString);
-			if (urlString.startsWith(PLATFORM_PROTOCOL))
-				continue;
-			if (urlString.equals(url))
-				return site;
-		}
-		return null;
-	}
-
-	/*
-	 * Read the contents of a link file and return the path. May or may not include
-	 * a prefix indicating read-only or read-write status.
-	 */
-	private String readExtension(File file) {
-		Properties props = new Properties();
-		InputStream input = null;
-		try {
-			input = new BufferedInputStream(new FileInputStream(file));
-			props.load(input);
-		} catch (IOException e) {
-			// TODO
-			e.printStackTrace();
-			return null;
-		} finally {
-			if (input != null)
-				try {
-					input.close();
-				} catch (IOException e) {
-					// ignore
-				}
-		}
-		return props.getProperty(PATH_PROPERTY);
-	}
-}
diff --git a/bundles/org.eclipse.equinox.p2.reconciler.dropins/src/org/eclipse/equinox/internal/p2/reconciler/dropins/PlatformXmlListener.java b/bundles/org.eclipse.equinox.p2.reconciler.dropins/src/org/eclipse/equinox/internal/p2/reconciler/dropins/PlatformXmlListener.java
index 9597335..6359520 100644
--- a/bundles/org.eclipse.equinox.p2.reconciler.dropins/src/org/eclipse/equinox/internal/p2/reconciler/dropins/PlatformXmlListener.java
+++ b/bundles/org.eclipse.equinox.p2.reconciler.dropins/src/org/eclipse/equinox/internal/p2/reconciler/dropins/PlatformXmlListener.java
@@ -20,7 +20,7 @@
 import org.eclipse.equinox.internal.p2.update.*;
 import org.eclipse.equinox.internal.provisional.p2.core.ProvisionException;
 import org.eclipse.equinox.internal.provisional.p2.directorywatcher.DirectoryChangeListener;
-import org.eclipse.equinox.internal.provisional.p2.directorywatcher.DirectoryWatcher;
+import org.eclipse.equinox.internal.provisional.p2.metadata.repository.IMetadataRepository;
 
 /**
  * @since 1.0
@@ -101,7 +101,7 @@
 		added(delta.added());
 		removed(delta.removed());
 		changed(delta.changed());
-		Activator.synchronize(getMetadataRepositories(), null); // TODO proper progress monitoring?
+		//Activator.synchronize(getMetadataRepositories(), null); // TODO proper progress monitoring?
 	}
 
 	// iterate over the site listeners and collect the metadata repositories
@@ -109,7 +109,7 @@
 		List result = new ArrayList();
 		for (Iterator iter = sites.values().iterator(); iter.hasNext();) {
 			SiteInfo info = (SiteInfo) iter.next();
-			result.add(info.getListener().getMetadataRepository());
+			result.add(info.getRepository());
 		}
 		return result;
 	}
@@ -133,12 +133,11 @@
 				} catch (IOException e) {
 					throw new ProvisionException(Messages.errorProcessingConfg, e);
 				}
-				File file = new File(url.getPath(), "plugins"); //$NON-NLS-1$
-				DirectoryWatcher watcher = new DirectoryWatcher(file);
-				SiteListener listener = new SiteListener(site);
-				watcher.addListener(listener);
-				watcher.poll();
-				sites.put(site.getUrl(), new SiteInfo(site, watcher, listener));
+				IMetadataRepository repo = Activator.loadMetadataRepository(url);
+				if (repo == null) {
+					// todo
+				} else
+					sites.put(site.getUrl(), new SiteInfo(site, repo));
 			} catch (MalformedURLException e) {
 				throw new ProvisionException(Messages.errorProcessingConfg, e);
 			}
@@ -159,7 +158,7 @@
 			if (info == null) {
 				// 
 			}
-			info.getWatcher().stop();
+
 			sites.remove(site.getUrl());
 		}
 	}
diff --git a/bundles/org.eclipse.equinox.p2.reconciler.dropins/src/org/eclipse/equinox/internal/p2/reconciler/dropins/ProfileSynchronizer.java b/bundles/org.eclipse.equinox.p2.reconciler.dropins/src/org/eclipse/equinox/internal/p2/reconciler/dropins/ProfileSynchronizer.java
index 4cf256e..1088115 100644
--- a/bundles/org.eclipse.equinox.p2.reconciler.dropins/src/org/eclipse/equinox/internal/p2/reconciler/dropins/ProfileSynchronizer.java
+++ b/bundles/org.eclipse.equinox.p2.reconciler.dropins/src/org/eclipse/equinox/internal/p2/reconciler/dropins/ProfileSynchronizer.java
@@ -10,208 +10,217 @@
 package org.eclipse.equinox.internal.p2.reconciler.dropins;
 
 import java.io.IOException;
+import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.*;
+import java.util.Map.Entry;
 import org.eclipse.core.runtime.*;
 import org.eclipse.equinox.internal.provisional.configurator.Configurator;
+import org.eclipse.equinox.internal.provisional.p2.artifact.repository.IArtifactRepository;
+import org.eclipse.equinox.internal.provisional.p2.artifact.repository.IFileArtifactRepository;
+import org.eclipse.equinox.internal.provisional.p2.core.ProvisionException;
 import org.eclipse.equinox.internal.provisional.p2.director.*;
 import org.eclipse.equinox.internal.provisional.p2.engine.*;
 import org.eclipse.equinox.internal.provisional.p2.metadata.IInstallableUnit;
 import org.eclipse.equinox.internal.provisional.p2.metadata.query.InstallableUnitQuery;
 import org.eclipse.equinox.internal.provisional.p2.metadata.repository.IMetadataRepository;
 import org.eclipse.equinox.internal.provisional.p2.query.Collector;
+import org.eclipse.equinox.internal.provisional.p2.query.Query;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.ServiceReference;
 
 public class ProfileSynchronizer {
 
-	private static final String FILE_LAST_MODIFIED = "file.lastModified"; //$NON-NLS-1$
-	private static final String FILE_NAME = "file.name"; //$NON-NLS-1$
-	private static final String REPOSITORY_ID = "repository.id"; //$NON-NLS-1$
-	private IInstallableUnit[] iusToRemove;
-	private IInstallableUnit[] iusToAdd;
-	private IProfile profile;
-	private List repositories;
+	public class ListCollector extends Collector {
+		public List getList() {
+			return super.getList();
+		}
+	}
+
+	private static final String SYNCH_REPOSITORY_ID = "synch.repository.id"; //$NON-NLS-1$
+	private static final String CACHE_EXTENSIONS = "org.eclipse.equinox.p2.cache.extensions"; //$NON-NLS-1$
+	private static final String PIPE = "|"; //$NON-NLS-1$
+	final IProfile profile;
+	final Map repositoryMap;
 
 	/*
 	 * Constructor for the class.
 	 */
-	public ProfileSynchronizer(IProfile profile, List repositories) {
-		super();
+	public ProfileSynchronizer(IProfile profile, Collection repositories) {
 		this.profile = profile;
-		this.repositories = repositories;
-		initialize();
-	}
-
-	public void add(List additions) {
-		this.repositories.addAll(repositories);
-		initialize();
-		// TODO progress monitoring
-		synchronize(null);
-	}
-
-	/*
-	 * Initialize the synchronizer with default values.
-	 */
-	private void initialize() {
-		// snapshot is a table of all the IUs from this repository which are installed in the profile 
-		final Map snapshot = new HashMap();
-		for (Iterator iter = repositories.iterator(); iter.hasNext();) {
-			IMetadataRepository metadataRepository = (IMetadataRepository) iter.next();
-			String repositoryId = metadataRepository.getLocation().toExternalForm();
-			Iterator it = profile.query(InstallableUnitQuery.ANY, new Collector(), null).iterator();
-			while (it.hasNext()) {
-				IInstallableUnit iu = (IInstallableUnit) it.next();
-				if (repositoryId.equals(iu.getProperty(REPOSITORY_ID))) {
-					String fileName = iu.getProperty(FILE_NAME);
-					if (fileName != null)
-						snapshot.put(fileName, iu);
-				}
-			}
-		}
-
-		final List toAdd = new ArrayList();
-		//create the collector that will visit all the IUs in the repositories being synchronized
-		Collector syncCollector = new Collector() {
-			public boolean accept(Object object) {
-				IInstallableUnit iu = (IInstallableUnit) object;
-				String iuFileName = iu.getProperty(FILE_NAME);
-				// TODO is this right?
-				if (iuFileName == null)
-					return true;
-
-				// if the repository contains an IU that the profile doesn't, then add it to the list to install
-				IInstallableUnit profileIU = (IInstallableUnit) snapshot.get(iuFileName);
-				if (profileIU == null) {
-					toAdd.add(iu);
-					return true;
-				}
-
-				Long iuLastModified = new Long(iu.getProperty(FILE_LAST_MODIFIED));
-				Long profileIULastModified = new Long(profileIU.getProperty(FILE_LAST_MODIFIED));
-				// TODO is this right?
-				if (iuLastModified == null || profileIULastModified == null)
-					return true;
-
-				// if the timestamp hasn't changed, then there is nothing to do so remove
-				// the IU from the snapshot so we don't accidentally remove it later
-				if (iuLastModified.equals(profileIULastModified))
-					snapshot.remove(iuFileName);
-				else
-					toAdd.add(iu);
-				return true;
-			}
-		};
-
+		this.repositoryMap = new HashMap();
 		for (Iterator it = repositories.iterator(); it.hasNext();) {
-			IMetadataRepository repo = (IMetadataRepository) it.next();
-			// TODO report progress
-			repo.query(InstallableUnitQuery.ANY, syncCollector, null);
-		}
-
-		// the IUs to remove is everything left that hasn't been removed from the snapshot
-		if (!snapshot.isEmpty()) {
-			iusToRemove = (IInstallableUnit[]) snapshot.values().toArray(new IInstallableUnit[snapshot.size()]);
-		}
-
-		// the list of IUs to add
-		if (!toAdd.isEmpty()) {
-			iusToAdd = (IInstallableUnit[]) toAdd.toArray(new IInstallableUnit[toAdd.size()]);
+			IMetadataRepository repository = (IMetadataRepository) it.next();
+			repositoryMap.put(repository.getLocation().toExternalForm(), repository);
 		}
 	}
 
 	/*
 	 * Synchronize the profile with the list of metadata repositories.
 	 */
-	public void synchronize(IProgressMonitor monitor) {
-		IStatus status = Status.OK_STATUS;
-		if (iusToRemove != null)
-			status = removeIUs(iusToRemove, null); // TODO proper progress monitoring
-		if (!status.isOK()) {
-			// TODO
-			throw new RuntimeException(new CoreException(status));
-		}
+	public IStatus synchronize(IProgressMonitor monitor) {
 
-		// disable repo cleanup for now until we see how we want to handle support for links folders and eclipse extensions
-		//removeUnwatchedRepositories(context, profile, watchedFolder);
+		IStatus status = synchronizeCacheExtensions();
+		if (!status.isOK())
+			return status;
 
-		if (iusToAdd != null)
-			status = addIUs(iusToAdd, null); // TODO proper progress monitoring
-		if (!status.isOK()) {
-			// TODO
-			throw new RuntimeException(new CoreException(status));
-		}
-		// if we did any work we have to apply the changes
-		if (iusToAdd != null || iusToRemove != null)
-			applyConfiguration();
-	}
-
-	/*
-	 * Install the given list of IUs.
-	 */
-	private IStatus addIUs(IInstallableUnit[] toAdd, IProgressMonitor monitor) {
-		BundleContext context = Activator.getContext();
+		ProfileChangeRequest request = createProfileChangeRequest();
+		if (request == null)
+			return Status.OK_STATUS;
 
 		SubMonitor sub = SubMonitor.convert(monitor, 100);
+		ProvisioningContext provisioningContext = new ProvisioningContext(new URL[0]);
 
-		ServiceReference reference = context.getServiceReference(IPlanner.class.getName());
-		IPlanner planner = (IPlanner) context.getService(reference);
+		ProvisioningPlan plan = createProvisioningPlan(request, provisioningContext, sub.newChild(50));
+		if (!plan.getStatus().isOK())
+			return plan.getStatus();
 
-		try {
-			ProfileChangeRequest request = new ProfileChangeRequest(profile);
-			request.addInstallableUnits(toAdd);
-			// mark the roots as such
-			for (int i = 0; i < toAdd.length; i++) {
-				if (Boolean.valueOf(toAdd[i].getProperty(IInstallableUnit.PROP_TYPE_GROUP)).booleanValue())
-					request.setInstallableUnitProfileProperty(toAdd[i], IInstallableUnit.PROP_PROFILE_ROOT_IU, Boolean.toString(true));
+		if (plan.getOperands().length == 0) {
+			sub.done();
+			return Status.OK_STATUS;
+		}
+
+		status = executePlan(plan, provisioningContext, sub.newChild(50));
+		if (!status.isOK())
+			return status;
+
+		applyConfiguration();
+		return Status.OK_STATUS;
+	}
+
+	private IStatus synchronizeCacheExtensions() {
+		List currentExtensions = new ArrayList();
+		StringBuffer buffer = new StringBuffer();
+		for (Iterator it = repositoryMap.keySet().iterator(); it.hasNext();) {
+			String repositoryId = (String) it.next();
+			try {
+				IArtifactRepository repository = Activator.loadArtifactRepository(new URL(repositoryId));
+
+				if (repository instanceof IFileArtifactRepository) {
+					currentExtensions.add(repositoryId);
+					buffer.append(repositoryId);
+					if (it.hasNext())
+						buffer.append(PIPE);
+				}
+			} catch (ProvisionException e) {
+				// ignore
+			} catch (MalformedURLException e) {
+				// unexpected
+				e.printStackTrace();
 			}
-
-			ProvisioningContext provisioningContext = new ProvisioningContext(new URL[0]);
-			ProvisioningPlan plan = planner.getProvisioningPlan(request, provisioningContext, sub.newChild(50));
-			if (!plan.getStatus().isOK())
-				return plan.getStatus();
-
-			return executePlan(plan, provisioningContext, sub.newChild(50));
-
-		} finally {
-			context.ungetService(reference);
 		}
+		String currentExtensionsProperty = (buffer.length() == 0) ? null : buffer.toString();
+
+		List previousExtensions = new ArrayList();
+		String previousExtensionsProperty = profile.getProperty(CACHE_EXTENSIONS);
+		if (previousExtensionsProperty != null) {
+			StringTokenizer tokenizer = new StringTokenizer(previousExtensionsProperty, PIPE);
+			while (tokenizer.hasMoreTokens()) {
+				previousExtensions.add(tokenizer.nextToken());
+			}
+		}
+
+		if (previousExtensions.size() == currentExtensions.size() && previousExtensions.containsAll(currentExtensions))
+			return Status.OK_STATUS;
+
+		Operand operand = new PropertyOperand(CACHE_EXTENSIONS, previousExtensionsProperty, currentExtensionsProperty);
+
+		return executeOperands(new ProvisioningContext(new URL[0]), new Operand[] {operand}, null);
 	}
 
-	/*
-	 * Uninstall the given list of IUs.
-	 */
-	private IStatus removeIUs(IInstallableUnit[] toRemove, IProgressMonitor monitor) {
+	private ProfileChangeRequest createProfileChangeRequest() {
+		boolean modified = false;
+		Collection profileIUs = getProfileIUs();
+		Collection toRemove = getStaleIUs();
+		profileIUs.removeAll(toRemove);
+
+		ProfileChangeRequest request = new ProfileChangeRequest(profile);
+		for (Iterator it = repositoryMap.entrySet().iterator(); it.hasNext();) {
+			Entry entry = (Entry) it.next();
+			String repositoryId = (String) entry.getKey();
+			IMetadataRepository repository = (IMetadataRepository) entry.getValue();
+			Iterator repositoryIterator = repository.query(InstallableUnitQuery.ANY, new Collector(), null).iterator();
+			while (repositoryIterator.hasNext()) {
+				IInstallableUnit iu = (IInstallableUnit) repositoryIterator.next();
+				if (profileIUs.contains(iu))
+					continue;
+
+				if (toRemove.contains(iu)) {
+					toRemove.remove(iu);
+				} else {
+					request.addInstallableUnits(new IInstallableUnit[] {iu});
+					if (Boolean.valueOf(iu.getProperty(IInstallableUnit.PROP_TYPE_GROUP)).booleanValue())
+						request.setInstallableUnitProfileProperty(iu, IInstallableUnit.PROP_PROFILE_ROOT_IU, Boolean.TRUE.toString());
+				}
+				request.setInstallableUnitProfileProperty(iu, SYNCH_REPOSITORY_ID, repositoryId);
+				profileIUs.add(iu);
+				modified = true;
+			}
+		}
+		if (!toRemove.isEmpty()) {
+			request.removeInstallableUnits((IInstallableUnit[]) toRemove.toArray(new IInstallableUnit[0]));
+			modified = true;
+		}
+
+		if (!modified)
+			return null;
+
+		return request;
+	}
+
+	private Collection getStaleIUs() {
+		Query removeQuery = new Query() {
+			public boolean isMatch(Object object) {
+				IInstallableUnit iu = (IInstallableUnit) object;
+				String repositoryId = profile.getInstallableUnitProperty(iu, SYNCH_REPOSITORY_ID);
+				if (repositoryId == null)
+					return false;
+
+				IMetadataRepository repo = (IMetadataRepository) repositoryMap.get(repositoryId);
+				Query iuQuery = new InstallableUnitQuery(iu.getId(), iu.getVersion());
+				return (repo == null || repo.query(iuQuery, new Collector(), null).isEmpty());
+			}
+		};
+
+		ListCollector listCollector = new ListCollector();
+		profile.query(removeQuery, listCollector, null);
+
+		List result = listCollector.getList();
+		return (result != null) ? result : Collections.EMPTY_LIST;
+	}
+
+	private List getProfileIUs() {
+		ListCollector listCollector = new ListCollector();
+		profile.query(InstallableUnitQuery.ANY, listCollector, null);
+
+		List result = listCollector.getList();
+		return (result != null) ? result : Collections.EMPTY_LIST;
+	}
+
+	private ProvisioningPlan createProvisioningPlan(ProfileChangeRequest request, ProvisioningContext provisioningContext, IProgressMonitor monitor) {
 		BundleContext context = Activator.getContext();
-
-		SubMonitor sub = SubMonitor.convert(monitor, 100);
-
 		ServiceReference reference = context.getServiceReference(IPlanner.class.getName());
 		IPlanner planner = (IPlanner) context.getService(reference);
 
 		try {
-			ProfileChangeRequest request = new ProfileChangeRequest(profile);
-			request.removeInstallableUnits(toRemove);
-
-			ProvisioningContext provisioningContext = new ProvisioningContext(new URL[0]);
-			ProvisioningPlan plan = planner.getProvisioningPlan(request, provisioningContext, sub.newChild(50));
-			if (!plan.getStatus().isOK())
-				return plan.getStatus();
-
-			return executePlan(plan, provisioningContext, sub.newChild(50));
-
+			return planner.getProvisioningPlan(request, provisioningContext, monitor);
 		} finally {
 			context.ungetService(reference);
 		}
 	}
 
 	private IStatus executePlan(ProvisioningPlan plan, ProvisioningContext provisioningContext, IProgressMonitor monitor) {
+		Operand[] operands = plan.getOperands();
+		return executeOperands(provisioningContext, operands, monitor);
+	}
+
+	private IStatus executeOperands(ProvisioningContext provisioningContext, Operand[] operands, IProgressMonitor monitor) {
 		BundleContext context = Activator.getContext();
 		ServiceReference reference = context.getServiceReference(IEngine.class.getName());
 		IEngine engine = (IEngine) context.getService(reference);
 		try {
 			PhaseSet phaseSet = new DefaultPhaseSet();
-			IStatus engineResult = engine.perform(profile, phaseSet, plan.getOperands(), provisioningContext, monitor);
+			IStatus engineResult = engine.perform(profile, phaseSet, operands, provisioningContext, monitor);
 			return engineResult;
 		} finally {
 			context.ungetService(reference);
@@ -234,5 +243,4 @@
 			context.ungetService(reference);
 		}
 	}
-
 }
diff --git a/bundles/org.eclipse.equinox.p2.reconciler.dropins/src/org/eclipse/equinox/internal/p2/reconciler/dropins/SiteInfo.java b/bundles/org.eclipse.equinox.p2.reconciler.dropins/src/org/eclipse/equinox/internal/p2/reconciler/dropins/SiteInfo.java
index e9c3e47..d891811 100644
--- a/bundles/org.eclipse.equinox.p2.reconciler.dropins/src/org/eclipse/equinox/internal/p2/reconciler/dropins/SiteInfo.java
+++ b/bundles/org.eclipse.equinox.p2.reconciler.dropins/src/org/eclipse/equinox/internal/p2/reconciler/dropins/SiteInfo.java
@@ -11,23 +11,21 @@
 package org.eclipse.equinox.internal.p2.reconciler.dropins;
 
 import org.eclipse.equinox.internal.p2.update.Site;
-import org.eclipse.equinox.internal.provisional.p2.directorywatcher.DirectoryWatcher;
+import org.eclipse.equinox.internal.provisional.p2.metadata.repository.IMetadataRepository;
 
 /*
  * Internal class contains information about watchers and sites. Used for caching.
  */
 public class SiteInfo {
-	private DirectoryWatcher watcher;
-	private SiteListener listener;
+	private IMetadataRepository repository;
 	private Site site;
 	private String url;
 
-	public SiteInfo(Site site, DirectoryWatcher watcher, SiteListener listener) {
+	public SiteInfo(Site site, IMetadataRepository repository) {
 		super();
 		this.site = site;
 		this.url = site.getUrl();
-		this.watcher = watcher;
-		this.listener = listener;
+		this.repository = repository;
 	}
 
 	public Site getSite() {
@@ -38,12 +36,8 @@
 		return url;
 	}
 
-	public DirectoryWatcher getWatcher() {
-		return watcher;
-	}
-
-	public SiteListener getListener() {
-		return listener;
+	public IMetadataRepository getRepository() {
+		return repository;
 	}
 
 	/* (non-Javadoc)
diff --git a/bundles/org.eclipse.equinox.p2.reconciler.dropins/src/org/eclipse/equinox/internal/p2/reconciler/dropins/SiteListener.java b/bundles/org.eclipse.equinox.p2.reconciler.dropins/src/org/eclipse/equinox/internal/p2/reconciler/dropins/SiteListener.java
deleted file mode 100644
index f63e16b..0000000
--- a/bundles/org.eclipse.equinox.p2.reconciler.dropins/src/org/eclipse/equinox/internal/p2/reconciler/dropins/SiteListener.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2007, 2008 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.reconciler.dropins;
-
-import java.io.File;
-import org.eclipse.equinox.internal.p2.update.Site;
-import org.eclipse.equinox.internal.provisional.p2.directorywatcher.RepositoryListener;
-
-/**
- * @since 1.0
- */
-public class SiteListener extends RepositoryListener {
-
-	private Site site;
-
-	/*
-	 * Create a new site listener on the given site.
-	 */
-	public SiteListener(Site site) {
-		super(Activator.getContext(), Integer.toString(site.getUrl().hashCode()));
-		this.site = site;
-	}
-
-	/*
-	 * Return true if the given list contains the symbolic name for the bundle
-	 * represented by the given file handle. Return false otherwise.
-	 */
-	private boolean contains(String[] plugins, File file) {
-		String filename = file.getAbsolutePath();
-		for (int i = 0; i < plugins.length; i++)
-			if (plugins[i].endsWith(filename))
-				return true;
-		return false;
-	}
-
-	/* (non-Javadoc)
-	 * @see org.eclipse.equinox.internal.provisional.p2.directorywatcher.DirectoryChangeListener#isInterested(java.io.File)
-	 */
-	public boolean isInterested(File file) {
-		String policy = site.getPolicy();
-		String[] plugins = site.getList();
-		if (Site.POLICY_MANAGED_ONLY.equals(policy)) {
-			// TODO
-		} else if (Site.POLICY_USER_EXCLUDE.equals(policy)) {
-			// ensure the file doesn't refer to a plug-in in our list
-			return plugins.length == 0 ? true : !contains(plugins, file);
-		} else if (Site.POLICY_USER_INCLUDE.equals(policy)) {
-			// we are only interested in plug-ins in the list
-			return plugins.length == 0 ? false : contains(plugins, file);
-		}
-		return false;
-	}
-}
diff --git a/bundles/org.eclipse.equinox.p2.reconciler/META-INF/MANIFEST.MF b/bundles/org.eclipse.equinox.p2.reconciler/META-INF/MANIFEST.MF
index 15e1965..3746432 100644
--- a/bundles/org.eclipse.equinox.p2.reconciler/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.equinox.p2.reconciler/META-INF/MANIFEST.MF
@@ -6,7 +6,6 @@
 Bundle-Localization: plugin
 Bundle-Version: 0.1.0.qualifier
 Bundle-Activator: org.eclipse.equinox.internal.p2.reconciler.dropins.Activator
-Eclipse-LazyStart: true
 Bundle-RequiredExecutionEnvironment: J2SE-1.4,
  CDC-1.1/Foundation-1.1
 Import-Package: org.eclipse.equinox.internal.p2.update,
@@ -21,6 +20,9 @@
  org.eclipse.equinox.internal.provisional.p2.metadata.query,
  org.eclipse.equinox.internal.provisional.p2.metadata.repository,
  org.eclipse.equinox.internal.provisional.p2.query,
+ org.eclipse.equinox.internal.provisional.spi.p2.artifact.repository,
+ org.eclipse.equinox.internal.provisional.spi.p2.core.repository,
+ org.eclipse.equinox.internal.provisional.spi.p2.metadata.repository,
  org.eclipse.osgi.service.datalocation;version="1.0.0",
  org.eclipse.osgi.util;version="1.1.0",
  org.osgi.framework;version="1.3.0",
diff --git a/bundles/org.eclipse.equinox.p2.reconciler/build.properties b/bundles/org.eclipse.equinox.p2.reconciler/build.properties
index 97493b2..eb63c6e 100644
--- a/bundles/org.eclipse.equinox.p2.reconciler/build.properties
+++ b/bundles/org.eclipse.equinox.p2.reconciler/build.properties
@@ -13,4 +13,5 @@
 bin.includes = META-INF/,\
                .,\
                plugin.properties,\
-               about.html
+               about.html,\
+               plugin.xml
diff --git a/bundles/org.eclipse.equinox.p2.reconciler/plugin.xml b/bundles/org.eclipse.equinox.p2.reconciler/plugin.xml
new file mode 100644
index 0000000..81c2c8e
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.reconciler/plugin.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?eclipse version="3.2"?>
+<plugin>
+      <extension
+      id="extensionRepository"
+         point="org.eclipse.equinox.p2.metadata.repository.metadataRepositories">
+      <factory
+            class="org.eclipse.equinox.internal.p2.extensionlocation.ExtensionLocationMetadataRepositoryFactory">
+      </factory>
+      <filter
+            suffix="eclipse">
+      </filter>
+   </extension>
+      <extension
+            point="org.eclipse.equinox.p2.artifact.repository.artifactRepositories">
+         <factory
+               class="org.eclipse.equinox.internal.p2.extensionlocation.ExtensionLocationArtifactRepositoryFactory">
+         </factory>
+         <filter
+               suffix="eclipse">
+         </filter>
+      </extension>
+</plugin>
diff --git a/bundles/org.eclipse.equinox.p2.reconciler/src/org/eclipse/equinox/internal/p2/extensionlocation/ExtensionLocationArtifactRepository.java b/bundles/org.eclipse.equinox.p2.reconciler/src/org/eclipse/equinox/internal/p2/extensionlocation/ExtensionLocationArtifactRepository.java
new file mode 100644
index 0000000..8d6581b
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.reconciler/src/org/eclipse/equinox/internal/p2/extensionlocation/ExtensionLocationArtifactRepository.java
@@ -0,0 +1,151 @@
+package org.eclipse.equinox.internal.p2.extensionlocation;
+
+import java.io.File;
+import java.io.OutputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import org.eclipse.core.runtime.*;
+import org.eclipse.equinox.internal.p2.reconciler.dropins.Activator;
+import org.eclipse.equinox.internal.provisional.p2.artifact.repository.*;
+import org.eclipse.equinox.internal.provisional.p2.core.ProvisionException;
+import org.eclipse.equinox.internal.provisional.p2.directorywatcher.DirectoryWatcher;
+import org.eclipse.equinox.internal.provisional.p2.directorywatcher.RepositoryListener;
+import org.eclipse.equinox.internal.provisional.p2.metadata.IArtifactKey;
+import org.eclipse.equinox.internal.provisional.spi.p2.artifact.repository.SimpleArtifactRepositoryFactory;
+import org.eclipse.equinox.internal.provisional.spi.p2.core.repository.AbstractRepository;
+import org.osgi.framework.BundleContext;
+
+public class ExtensionLocationArtifactRepository extends AbstractRepository implements IFileArtifactRepository {
+
+	//private static final String PROFILE_EXTENSION = "profile.extension"; //$NON-NLS-1$
+	private static final String ECLIPSE = "eclipse"; //$NON-NLS-1$
+	private static final String FEATURES = "features"; //$NON-NLS-1$
+	private static final String PLUGINS = "plugins"; //$NON-NLS-1$
+	private static final String FILE = "file"; //$NON-NLS-1$
+	private final IFileArtifactRepository artifactRepository;
+
+	public ExtensionLocationArtifactRepository(URL location, IProgressMonitor monitor) throws ProvisionException {
+		super("Extension: " + location.toExternalForm(), null, null, location, null, null); //$NON-NLS-1$
+
+		File base = getBaseDirectory(location);
+		File plugins = new File(base, PLUGINS);
+		File features = new File(base, FEATURES);
+
+		BundleContext context = Activator.getContext();
+		String stateDirName = Integer.toString(location.toExternalForm().hashCode());
+		File bundleData = context.getDataFile(null);
+		File stateDir = new File(bundleData, stateDirName);
+		URL localRepositoryURL;
+		try {
+			localRepositoryURL = stateDir.toURL();
+		} catch (MalformedURLException e) {
+			// unexpected
+			throw new ProvisionException(new Status(IStatus.ERROR, Activator.ID, "Failed to create local repository", e)); //$NON-NLS-1$
+		}
+
+		artifactRepository = (IFileArtifactRepository) initializeArtifactRepository(localRepositoryURL, "extension location implementation - " + location.toExternalForm()); //$NON-NLS-1$
+
+		DirectoryWatcher watcher = new DirectoryWatcher(new File[] {plugins, features});
+		RepositoryListener listener = new RepositoryListener(context, null, artifactRepository);
+		watcher.addListener(listener);
+		watcher.poll();
+	}
+
+	private IArtifactRepository initializeArtifactRepository(URL stateDirURL, String repositoryName) {
+		SimpleArtifactRepositoryFactory factory = new SimpleArtifactRepositoryFactory();
+		try {
+			return factory.load(stateDirURL, null);
+
+		} catch (ProvisionException e) {
+			//fall through and create a new repository
+		}
+		IArtifactRepository repository = factory.create(stateDirURL, repositoryName, null);
+		//repository.setProperty(PROFILE_EXTENSION, "true");
+		return repository;
+	}
+
+	public static void validate(URL location, IProgressMonitor monitor) throws ProvisionException {
+		getBaseDirectory(location);
+	}
+
+	public static File getBaseDirectory(URL url) throws ProvisionException {
+		if (url.getProtocol() != FILE)
+			throw new ProvisionException(new Status(IStatus.ERROR, Activator.ID, ProvisionException.REPOSITORY_NOT_FOUND, "location must use file protocol", null));
+
+		File base = new File(url.getPath());
+		if (!base.isDirectory())
+			throw new ProvisionException(new Status(IStatus.ERROR, Activator.ID, ProvisionException.REPOSITORY_NOT_FOUND, "location not a directory", null));
+
+		if (isBaseDirectory(base))
+			return base;
+
+		File eclipseBase = new File(base, ECLIPSE);
+		if (isBaseDirectory(eclipseBase))
+			return eclipseBase;
+
+		throw new ProvisionException(new Status(IStatus.ERROR, Activator.ID, ProvisionException.REPOSITORY_NOT_FOUND, "location is not an extension", null));
+	}
+
+	private static boolean isBaseDirectory(File base) {
+		File plugins = new File(base, PLUGINS);
+		File features = new File(base, FEATURES);
+
+		return plugins.isDirectory() || features.isDirectory();
+	}
+
+	public void addDescriptor(IArtifactDescriptor descriptor) {
+		throw new UnsupportedOperationException();
+	}
+
+	public void addDescriptors(IArtifactDescriptor[] descriptors) {
+		throw new UnsupportedOperationException();
+	}
+
+	public void removeAll() {
+		throw new UnsupportedOperationException();
+	}
+
+	public void removeDescriptor(IArtifactDescriptor descriptor) {
+		throw new UnsupportedOperationException();
+	}
+
+	public void removeDescriptor(IArtifactKey key) {
+		throw new UnsupportedOperationException();
+	}
+
+	public boolean contains(IArtifactDescriptor descriptor) {
+		return artifactRepository.contains(descriptor);
+	}
+
+	public boolean contains(IArtifactKey key) {
+		return artifactRepository.contains(key);
+	}
+
+	public IStatus getArtifact(IArtifactDescriptor descriptor, OutputStream destination, IProgressMonitor monitor) {
+		return artifactRepository.getArtifact(descriptor, destination, monitor);
+	}
+
+	public IArtifactDescriptor[] getArtifactDescriptors(IArtifactKey key) {
+		return artifactRepository.getArtifactDescriptors(key);
+	}
+
+	public IArtifactKey[] getArtifactKeys() {
+		return artifactRepository.getArtifactKeys();
+	}
+
+	public IStatus getArtifacts(IArtifactRequest[] requests, IProgressMonitor monitor) {
+		return artifactRepository.getArtifacts(requests, monitor);
+	}
+
+	public OutputStream getOutputStream(IArtifactDescriptor descriptor) throws ProvisionException {
+		return artifactRepository.getOutputStream(descriptor);
+	}
+
+	public File getArtifactFile(IArtifactKey key) {
+		return artifactRepository.getArtifactFile(key);
+	}
+
+	public File getArtifactFile(IArtifactDescriptor descriptor) {
+		return artifactRepository.getArtifactFile(descriptor);
+	}
+}
diff --git a/bundles/org.eclipse.equinox.p2.reconciler/src/org/eclipse/equinox/internal/p2/extensionlocation/ExtensionLocationArtifactRepositoryFactory.java b/bundles/org.eclipse.equinox.p2.reconciler/src/org/eclipse/equinox/internal/p2/extensionlocation/ExtensionLocationArtifactRepositoryFactory.java
new file mode 100644
index 0000000..83cb924
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.reconciler/src/org/eclipse/equinox/internal/p2/extensionlocation/ExtensionLocationArtifactRepositoryFactory.java
@@ -0,0 +1,28 @@
+package org.eclipse.equinox.internal.p2.extensionlocation;
+
+import java.net.URL;
+import org.eclipse.core.runtime.*;
+import org.eclipse.equinox.internal.provisional.p2.artifact.repository.IArtifactRepository;
+import org.eclipse.equinox.internal.provisional.p2.core.ProvisionException;
+import org.eclipse.equinox.internal.provisional.spi.p2.artifact.repository.IArtifactRepositoryFactory;
+
+public class ExtensionLocationArtifactRepositoryFactory implements IArtifactRepositoryFactory {
+
+	public IArtifactRepository create(URL location, String name, String type) throws ProvisionException {
+		return null;
+	}
+
+	public IArtifactRepository load(URL location, IProgressMonitor monitor) throws ProvisionException {
+		return new ExtensionLocationArtifactRepository(location, monitor);
+	}
+
+	public IStatus validate(URL location, IProgressMonitor monitor) {
+		try {
+			ExtensionLocationArtifactRepository.validate(location, monitor);
+		} catch (ProvisionException e) {
+			return e.getStatus();
+		}
+		return Status.OK_STATUS;
+	}
+
+}
diff --git a/bundles/org.eclipse.equinox.p2.reconciler/src/org/eclipse/equinox/internal/p2/extensionlocation/ExtensionLocationMetadataRepository.java b/bundles/org.eclipse.equinox.p2.reconciler/src/org/eclipse/equinox/internal/p2/extensionlocation/ExtensionLocationMetadataRepository.java
new file mode 100644
index 0000000..8004e2d
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.reconciler/src/org/eclipse/equinox/internal/p2/extensionlocation/ExtensionLocationMetadataRepository.java
@@ -0,0 +1,108 @@
+package org.eclipse.equinox.internal.p2.extensionlocation;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URL;
+import org.eclipse.core.runtime.*;
+import org.eclipse.equinox.internal.p2.reconciler.dropins.Activator;
+import org.eclipse.equinox.internal.provisional.p2.core.ProvisionException;
+import org.eclipse.equinox.internal.provisional.p2.directorywatcher.DirectoryWatcher;
+import org.eclipse.equinox.internal.provisional.p2.directorywatcher.RepositoryListener;
+import org.eclipse.equinox.internal.provisional.p2.metadata.IInstallableUnit;
+import org.eclipse.equinox.internal.provisional.p2.metadata.repository.IMetadataRepository;
+import org.eclipse.equinox.internal.provisional.p2.query.Collector;
+import org.eclipse.equinox.internal.provisional.p2.query.Query;
+import org.eclipse.equinox.internal.provisional.spi.p2.core.repository.AbstractRepository;
+import org.eclipse.equinox.internal.provisional.spi.p2.metadata.repository.SimpleMetadataRepositoryFactory;
+import org.osgi.framework.BundleContext;
+
+public class ExtensionLocationMetadataRepository extends AbstractRepository implements IMetadataRepository {
+
+	private static final String ECLIPSE = "eclipse"; //$NON-NLS-1$
+	private static final String FEATURES = "features"; //$NON-NLS-1$
+	private static final String PLUGINS = "plugins"; //$NON-NLS-1$
+	private static final String FILE = "file"; //$NON-NLS-1$
+	private final IMetadataRepository metadataRepository;
+
+	public ExtensionLocationMetadataRepository(URL location, IProgressMonitor monitor) throws ProvisionException {
+		super("Extension: " + location.toExternalForm(), null, null, location, null, null); //$NON-NLS-1$
+
+		File base = getBaseDirectory(location);
+		File plugins = new File(base, PLUGINS);
+		File features = new File(base, FEATURES);
+
+		BundleContext context = Activator.getContext();
+		String stateDirName = Integer.toString(location.toExternalForm().hashCode());
+		File bundleData = context.getDataFile(null);
+		File stateDir = new File(bundleData, stateDirName);
+		URL localRepositoryURL;
+		try {
+			localRepositoryURL = stateDir.toURL();
+		} catch (MalformedURLException e) {
+			// unexpected
+			throw new ProvisionException(new Status(IStatus.ERROR, Activator.ID, "Failed to create local repository", e)); //$NON-NLS-1$
+		}
+
+		metadataRepository = initializeMetadataRepository(localRepositoryURL, "extension location implementation - " + location.toExternalForm()); //$NON-NLS-1$
+
+		DirectoryWatcher watcher = new DirectoryWatcher(new File[] {plugins, features});
+		RepositoryListener listener = new RepositoryListener(context, metadataRepository, null);
+		watcher.addListener(listener);
+		watcher.poll();
+	}
+
+	private IMetadataRepository initializeMetadataRepository(URL stateDirURL, String repositoryName) {
+		SimpleMetadataRepositoryFactory factory = new SimpleMetadataRepositoryFactory();
+		try {
+			return factory.load(stateDirURL, null);
+		} catch (ProvisionException e) {
+			//fall through and create a new repository
+		}
+		return factory.create(stateDirURL, repositoryName, null);
+	}
+
+	public void addInstallableUnits(IInstallableUnit[] installableUnits) {
+		throw new UnsupportedOperationException();
+	}
+
+	public void removeAll() {
+		throw new UnsupportedOperationException();
+	}
+
+	public boolean removeInstallableUnits(Query query, IProgressMonitor monitor) {
+		throw new UnsupportedOperationException();
+	}
+
+	public Collector query(Query query, Collector collector, IProgressMonitor monitor) {
+		return metadataRepository.query(query, collector, monitor);
+	}
+
+	public static void validate(URL location, IProgressMonitor monitor) throws ProvisionException {
+		getBaseDirectory(location);
+	}
+
+	public static File getBaseDirectory(URL url) throws ProvisionException {
+		if (url.getProtocol() != FILE)
+			throw new ProvisionException(new Status(IStatus.ERROR, Activator.ID, ProvisionException.REPOSITORY_NOT_FOUND, "location must use file protocol", null));
+
+		File base = new File(url.getPath());
+		if (!base.isDirectory())
+			throw new ProvisionException(new Status(IStatus.ERROR, Activator.ID, ProvisionException.REPOSITORY_NOT_FOUND, "location not a directory", null));
+
+		if (isBaseDirectory(base))
+			return base;
+
+		File eclipseBase = new File(base, ECLIPSE);
+		if (isBaseDirectory(eclipseBase))
+			return eclipseBase;
+
+		throw new ProvisionException(new Status(IStatus.ERROR, Activator.ID, ProvisionException.REPOSITORY_NOT_FOUND, "location is not an extension", null));
+	}
+
+	private static boolean isBaseDirectory(File base) {
+		File plugins = new File(base, PLUGINS);
+		File features = new File(base, FEATURES);
+
+		return plugins.isDirectory() || features.isDirectory();
+	}
+}
diff --git a/bundles/org.eclipse.equinox.p2.reconciler/src/org/eclipse/equinox/internal/p2/extensionlocation/ExtensionLocationMetadataRepositoryFactory.java b/bundles/org.eclipse.equinox.p2.reconciler/src/org/eclipse/equinox/internal/p2/extensionlocation/ExtensionLocationMetadataRepositoryFactory.java
new file mode 100644
index 0000000..31da09c
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.reconciler/src/org/eclipse/equinox/internal/p2/extensionlocation/ExtensionLocationMetadataRepositoryFactory.java
@@ -0,0 +1,28 @@
+package org.eclipse.equinox.internal.p2.extensionlocation;
+
+import java.net.URL;
+import org.eclipse.core.runtime.*;
+import org.eclipse.equinox.internal.provisional.p2.core.ProvisionException;
+import org.eclipse.equinox.internal.provisional.p2.metadata.repository.IMetadataRepository;
+import org.eclipse.equinox.internal.provisional.spi.p2.metadata.repository.IMetadataRepositoryFactory;
+
+public class ExtensionLocationMetadataRepositoryFactory implements IMetadataRepositoryFactory {
+
+	public IMetadataRepository create(URL location, String name, String type) throws ProvisionException {
+		return null;
+	}
+
+	public IMetadataRepository load(URL location, IProgressMonitor monitor) throws ProvisionException {
+		return new ExtensionLocationMetadataRepository(location, monitor);
+	}
+
+	public IStatus validate(URL location, IProgressMonitor monitor) {
+		try {
+			ExtensionLocationMetadataRepository.validate(location, monitor);
+		} catch (ProvisionException e) {
+			return e.getStatus();
+		}
+		return Status.OK_STATUS;
+	}
+
+}
diff --git a/bundles/org.eclipse.equinox.p2.reconciler/src/org/eclipse/equinox/internal/p2/reconciler/dropins/Activator.java b/bundles/org.eclipse.equinox.p2.reconciler/src/org/eclipse/equinox/internal/p2/reconciler/dropins/Activator.java
index 1a46573..bddfb05 100644
--- a/bundles/org.eclipse.equinox.p2.reconciler/src/org/eclipse/equinox/internal/p2/reconciler/dropins/Activator.java
+++ b/bundles/org.eclipse.equinox.p2.reconciler/src/org/eclipse/equinox/internal/p2/reconciler/dropins/Activator.java
@@ -14,26 +14,75 @@
 import java.net.URL;
 import java.util.*;
 import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.equinox.internal.provisional.p2.artifact.repository.IArtifactRepository;
+import org.eclipse.equinox.internal.provisional.p2.artifact.repository.IArtifactRepositoryManager;
 import org.eclipse.equinox.internal.provisional.p2.core.ProvisionException;
 import org.eclipse.equinox.internal.provisional.p2.directorywatcher.DirectoryWatcher;
-import org.eclipse.equinox.internal.provisional.p2.directorywatcher.RepositoryListener;
 import org.eclipse.equinox.internal.provisional.p2.engine.IProfile;
 import org.eclipse.equinox.internal.provisional.p2.engine.IProfileRegistry;
 import org.eclipse.equinox.internal.provisional.p2.metadata.repository.IMetadataRepository;
+import org.eclipse.equinox.internal.provisional.p2.metadata.repository.IMetadataRepositoryManager;
 import org.osgi.framework.*;
 import org.osgi.service.packageadmin.PackageAdmin;
 
 public class Activator implements BundleActivator {
 
+	public static final String ID = "org.eclipse.equinox.p2.reconciler.dropins"; //$NON-NLS-1$
 	private static final String DROPINS_DIRECTORY = "org.eclipse.equinox.p2.reconciler.dropins.directory"; //$NON-NLS-1$
 	private static final String OSGI_CONFIGURATION_AREA = "osgi.configuration.area"; //$NON-NLS-1$
 	private static final String DROPINS = "dropins"; //$NON-NLS-1$
-	private static final String PROFILE_EXTENSION = "profile.extension"; //$NON-NLS-1$
+	//	private static final String PROFILE_EXTENSION = "profile.extension"; //$NON-NLS-1$
 	private static PackageAdmin packageAdmin;
 	private static BundleContext bundleContext;
 	private ServiceReference packageAdminRef;
 	private List watchers = new ArrayList();
-	private static IMetadataRepository dropinRepository;
+	private static IMetadataRepository[] dropinRepositories;
+	private static IMetadataRepository[] configurationRepositories;
+	private static IMetadataRepository[] linksRepositories;
+
+	/**
+	 * Helper method to load a metadata repository from the specified URL.
+	 * This method never returns <code>null</code>.
+	 * 
+	 * @throws IllegalStateException
+	 * @throws ProvisionException 
+	 */
+	public static IMetadataRepository loadMetadataRepository(URL repoURL) throws ProvisionException {
+		BundleContext context = getContext();
+		ServiceReference reference = context.getServiceReference(IMetadataRepositoryManager.class.getName());
+		IMetadataRepositoryManager manager = null;
+		if (reference != null)
+			manager = (IMetadataRepositoryManager) context.getService(reference);
+		if (manager == null)
+			throw new IllegalStateException("MetadataRepositoryManager not registered.");
+		try {
+			return manager.loadRepository(repoURL, null);
+		} finally {
+			context.ungetService(reference);
+		}
+	}
+
+	/**
+	 * Helper method to load an artifact repository from the given URL.
+	 * This method never returns <code>null</code>.
+	 * 
+	 * @throws IllegalStateException
+	 * @throws ProvisionException
+	 */
+	public static IArtifactRepository loadArtifactRepository(URL repoURL) throws ProvisionException {
+		BundleContext context = getContext();
+		ServiceReference reference = context.getServiceReference(IArtifactRepositoryManager.class.getName());
+		IArtifactRepositoryManager manager = null;
+		if (reference != null)
+			manager = (IArtifactRepositoryManager) context.getService(reference);
+		if (manager == null)
+			throw new IllegalStateException("ArtifactRepositoryManager not registered.");
+		try {
+			return manager.loadRepository(repoURL, null);
+		} finally {
+			context.ungetService(reference);
+		}
+	}
 
 	/* (non-Javadoc)
 	 * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
@@ -55,9 +104,9 @@
 
 		// create the watcher for the "drop-ins" folder
 		watchDropins(profile);
-
-		// create watchers for the sites specified in the platform.xml
-		watchConfiguration();
+		// keep an eye on the platform.xml
+		if (false)
+			watchConfiguration();
 
 		synchronize(new ArrayList(0), null);
 	}
@@ -73,14 +122,21 @@
 	/*
 	 * Synchronize the profile.
 	 */
-	public static void synchronize(List extraRepositories, IProgressMonitor monitor) {
+	public static synchronized void synchronize(List extraRepositories, IProgressMonitor monitor) {
 		IProfile profile = getCurrentProfile(bundleContext);
 		if (profile == null)
 			return;
 		// create the profile synchronizer on all available repositories
-		List repositories = new ArrayList(extraRepositories);
-		if (dropinRepository != null)
-			repositories.add(dropinRepository);
+		Set repositories = new HashSet(extraRepositories);
+		if (dropinRepositories != null)
+			repositories.addAll(Arrays.asList(dropinRepositories));
+
+		if (configurationRepositories != null)
+			repositories.addAll(Arrays.asList(configurationRepositories));
+
+		if (linksRepositories != null)
+			repositories.addAll(Arrays.asList(linksRepositories));
+
 		ProfileSynchronizer synchronizer = new ProfileSynchronizer(profile, repositories);
 		synchronizer.synchronize(monitor);
 	}
@@ -88,50 +144,43 @@
 	/*
 	 * Watch the platform.xml file.
 	 */
-	private void watchConfiguration() throws ProvisionException {
+	private void watchConfiguration() {
 		File configFile = new File("configuration/org.eclipse.update/platform.xml"); //$NON-NLS-1$
 		DirectoryWatcher watcher = new DirectoryWatcher(configFile.getParentFile());
 		try {
 			PlatformXmlListener listener = new PlatformXmlListener(configFile);
 			watcher.addListener(listener);
+			watcher.poll();
+			List repositories = listener.getMetadataRepositories();
+			if (repositories != null)
+				configurationRepositories = (IMetadataRepository[]) repositories.toArray(new IMetadataRepository[0]);
 		} catch (ProvisionException e) {
 			// TODO proper logging
 			e.printStackTrace();
 		}
-		watchers.add(watcher);
-		watcher.start();
-
-		// pay attention to the links/ folder too. this is only needed on startup though since
-		// any other changes during execution will be reflected in the platform.xml file
-		LinksManager manager = new LinksManager();
-		manager.synchronize(configFile, new File("links"));
 	}
 
 	/*
 	 * Create a new directory watcher with a repository listener on the drop-ins folder. 
 	 */
 	private void watchDropins(IProfile profile) {
-		File folder = getWatchedDirectory(bundleContext);
-		if (folder == null)
+		List directories = new ArrayList();
+		File dropinsDirectory = getDropinsDirectory();
+		if (dropinsDirectory != null)
+			directories.add(dropinsDirectory);
+		File linksDirectory = getLinksDirectory();
+		if (linksDirectory != null)
+			directories.add(linksDirectory);
+		if (directories.isEmpty())
 			return;
 
-		RepositoryListener listener = new RepositoryListener(Activator.getContext(), Integer.toString(folder.hashCode()));
-		listener.getArtifactRepository().setProperty(PROFILE_EXTENSION, profile.getProfileId());
-
-		List folders = new ArrayList();
-		folders.add(folder);
-		File eclipseFeatures = new File(folder, "eclipse/features");
-		if (eclipseFeatures.isDirectory())
-			folders.add(eclipseFeatures);
-		File eclipsePlugins = new File(folder, "eclipse/plugins");
-		if (eclipsePlugins.isDirectory())
-			folders.add(eclipsePlugins);
-
-		DirectoryWatcher watcher = new DirectoryWatcher((File[]) folders.toArray(new File[folders.size()]));
+		DropinsRepositoryListener listener = new DropinsRepositoryListener(Activator.getContext(), "dropins:" + dropinsDirectory.getAbsolutePath());
+		//		listener.getArtifactRepository().setProperty(PROFILE_EXTENSION, profile.getProfileId());
+		DirectoryWatcher watcher = new DirectoryWatcher((File[]) directories.toArray(new File[directories.size()]));
 		watcher.addListener(listener);
 		watcher.poll();
 
-		dropinRepository = listener.getMetadataRepository();
+		dropinRepositories = listener.getMetadataRepositories();
 	}
 
 	/* (non-Javadoc)
@@ -154,15 +203,27 @@
 		return bundleContext;
 	}
 
-	public static File getWatchedDirectory(BundleContext context) {
-		String watchedDirectoryProperty = context.getProperty(DROPINS_DIRECTORY);
+	private static File getLinksDirectory() {
+		try {
+			//TODO: a proper install area would be better. osgi.install.area is relative to the framework jar
+			URL baseURL = new URL(bundleContext.getProperty(OSGI_CONFIGURATION_AREA));
+			URL folderURL = new URL(baseURL, "../links"); //$NON-NLS-1$
+			return new File(folderURL.getPath());
+		} catch (MalformedURLException e) {
+			e.printStackTrace();
+		}
+		return null;
+	}
+
+	public static File getDropinsDirectory() {
+		String watchedDirectoryProperty = bundleContext.getProperty(DROPINS_DIRECTORY);
 		if (watchedDirectoryProperty != null) {
 			File folder = new File(watchedDirectoryProperty);
 			return folder;
 		}
 		try {
 			//TODO: a proper install area would be better. osgi.install.area is relative to the framework jar
-			URL baseURL = new URL(context.getProperty(OSGI_CONFIGURATION_AREA));
+			URL baseURL = new URL(bundleContext.getProperty(OSGI_CONFIGURATION_AREA));
 			URL folderURL = new URL(baseURL, "../" + DROPINS); //$NON-NLS-1$
 			File folder = new File(folderURL.getPath());
 			return folder;
diff --git a/bundles/org.eclipse.equinox.p2.reconciler/src/org/eclipse/equinox/internal/p2/reconciler/dropins/DropinsRepositoryListener.java b/bundles/org.eclipse.equinox.p2.reconciler/src/org/eclipse/equinox/internal/p2/reconciler/dropins/DropinsRepositoryListener.java
new file mode 100644
index 0000000..9c4ce05
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.reconciler/src/org/eclipse/equinox/internal/p2/reconciler/dropins/DropinsRepositoryListener.java
@@ -0,0 +1,239 @@
+package org.eclipse.equinox.internal.p2.reconciler.dropins;
+
+import java.io.*;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.*;
+import org.eclipse.equinox.internal.provisional.p2.artifact.repository.IArtifactRepository;
+import org.eclipse.equinox.internal.provisional.p2.artifact.repository.IArtifactRepositoryManager;
+import org.eclipse.equinox.internal.provisional.p2.core.ProvisionException;
+import org.eclipse.equinox.internal.provisional.p2.core.repository.IRepository;
+import org.eclipse.equinox.internal.provisional.p2.directorywatcher.RepositoryListener;
+import org.eclipse.equinox.internal.provisional.p2.metadata.repository.IMetadataRepository;
+import org.eclipse.equinox.internal.provisional.p2.metadata.repository.IMetadataRepositoryManager;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+
+public class DropinsRepositoryListener extends RepositoryListener {
+
+	private static final String DROPIN_ARTIFACT_REPOSITORIES = "dropin.artifactRepositories"; //$NON-NLS-1$
+	private static final String DROPIN_METADATA_REPOSITORIES = "dropin.metadataRepositories"; //$NON-NLS-1$
+	private static final String PIPE = "|"; //$NON-NLS-1$
+	private BundleContext context;
+	private List metadataRepositories = new ArrayList();
+	private List artifactRepositories = new ArrayList();
+
+	public DropinsRepositoryListener(BundleContext context, String repositoryName) {
+		super(context, repositoryName);
+		this.context = context;
+	}
+
+	public boolean isInterested(File file) {
+		if (file.isDirectory())
+			return true;
+
+		String name = file.getName();
+		return name.endsWith(".jar") || name.endsWith(".zip") || name.endsWith(".link");
+	}
+
+	public boolean added(File file) {
+		if (super.isInterested(file))
+			return super.added(file);
+
+		URL repositoryURL = createRepositoryURL(file);
+		if (repositoryURL != null) {
+			loadMetadataRepository(repositoryURL);
+			loadArtifactRepository(repositoryURL);
+		}
+		return true;
+	}
+
+	public boolean changed(File file) {
+		if (super.isInterested(file))
+			return super.added(file);
+
+		URL repositoryURL = createRepositoryURL(file);
+		if (repositoryURL != null) {
+			loadMetadataRepository(repositoryURL);
+			loadArtifactRepository(repositoryURL);
+		}
+		return true;
+	}
+
+	private String getLinkPath(File file) {
+		Properties links = new Properties();
+		try {
+			InputStream input = new BufferedInputStream(new FileInputStream(file));
+			try {
+				links.load(input);
+			} finally {
+				input.close();
+			}
+		} catch (IOException e) {
+			// ignore
+		}
+		String path = links.getProperty("path");
+		if (path == null) {
+			// log
+			return null;
+		}
+
+		// parse out link information
+		if (path.startsWith("r ")) { //$NON-NLS-1$
+			path = path.substring(2).trim();
+		} else if (path.startsWith("rw ")) { //$NON-NLS-1$
+			path = path.substring(3).trim();
+		} else {
+			path = path.trim();
+		}
+		return path;
+	}
+
+	private URL createRepositoryURL(File file) {
+		try {
+			if (file.getName().endsWith(".link")) {
+				String path = getLinkPath(file);
+				// todo log
+				if (path == null)
+					return null;
+				file = new File(path);
+				if (!file.isAbsolute())
+					file = new File(file, path).getCanonicalFile();
+			}
+
+			URL repositoryURL = file.toURL();
+			if (file.getName().endsWith(".zip") || file.getName().endsWith(".jar")) {
+				repositoryURL = new URL("jar:" + repositoryURL.toString() + "!/");
+			}
+			return repositoryURL;
+		} catch (IOException e) {
+			// todo log			
+		}
+		return null;
+	}
+
+	public void loadMetadataRepository(URL repoURL) {
+		try {
+			metadataRepositories.add(Activator.loadMetadataRepository(repoURL));
+		} catch (ProvisionException e) {
+			//TODO: log
+			// ignore
+		}
+	}
+
+	public void loadArtifactRepository(URL repoURL) {
+		try {
+			artifactRepositories.add(Activator.loadArtifactRepository(repoURL));
+		} catch (ProvisionException e) {
+			//TODO: log
+			// ignore
+		}
+	}
+
+	public void stopPoll() {
+
+		synchronizeDropinMetadataRepositories();
+		synchronizeDropinArtifactRepositories();
+
+		super.stopPoll();
+	}
+
+	private void synchronizeDropinMetadataRepositories() {
+		List currentRepositories = new ArrayList();
+		for (Iterator it = metadataRepositories.iterator(); it.hasNext();) {
+			IMetadataRepository repository = (IMetadataRepository) it.next();
+			String urlString = repository.getLocation().toExternalForm();
+			currentRepositories.add(urlString);
+		}
+		List previousRepositories = getListRepositoryProperty(getMetadataRepository(), DROPIN_METADATA_REPOSITORIES);
+		for (Iterator iterator = previousRepositories.iterator(); iterator.hasNext();) {
+			String repository = (String) iterator.next();
+			if (!currentRepositories.contains(repository))
+				removeMetadataRepository(repository);
+		}
+		setListRepositoryProperty(getMetadataRepository(), DROPIN_METADATA_REPOSITORIES, currentRepositories);
+	}
+
+	private void removeMetadataRepository(String urlString) {
+		ServiceReference reference = context.getServiceReference(IMetadataRepositoryManager.class.getName());
+		IMetadataRepositoryManager manager = null;
+		if (reference != null)
+			manager = (IMetadataRepositoryManager) context.getService(reference);
+		if (manager == null)
+			throw new IllegalStateException("MetadataRepositoryManager not registered.");
+
+		try {
+			manager.removeRepository(new URL(urlString));
+		} catch (MalformedURLException e) {
+			// TODO: log
+			// ignore
+		} finally {
+			context.ungetService(reference);
+		}
+	}
+
+	private void synchronizeDropinArtifactRepositories() {
+		List currentRepositories = new ArrayList();
+		for (Iterator it = artifactRepositories.iterator(); it.hasNext();) {
+			IArtifactRepository repository = (IArtifactRepository) it.next();
+			String urlString = repository.getLocation().toExternalForm();
+			currentRepositories.add(urlString);
+		}
+		List previousRepositories = getListRepositoryProperty(getArtifactRepository(), DROPIN_ARTIFACT_REPOSITORIES);
+		for (Iterator iterator = previousRepositories.iterator(); iterator.hasNext();) {
+			String repository = (String) iterator.next();
+			if (!currentRepositories.contains(repository))
+				removeArtifactRepository(repository);
+		}
+		setListRepositoryProperty(getArtifactRepository(), DROPIN_ARTIFACT_REPOSITORIES, currentRepositories);
+	}
+
+	public void removeArtifactRepository(String urlString) {
+		ServiceReference reference = context.getServiceReference(IArtifactRepositoryManager.class.getName());
+		IArtifactRepositoryManager manager = null;
+		if (reference != null)
+			manager = (IArtifactRepositoryManager) context.getService(reference);
+		if (manager == null)
+			throw new IllegalStateException("ArtifactRepositoryManager not registered.");
+
+		try {
+			manager.removeRepository(new URL(urlString));
+		} catch (MalformedURLException e) {
+			//TODO: log
+			// ignore
+		} finally {
+			context.ungetService(reference);
+		}
+	}
+
+	private List getListRepositoryProperty(IRepository repository, String key) {
+		List listProperty = new ArrayList();
+		String dropinRepositories = (String) repository.getProperties().get(key);
+		if (dropinRepositories != null) {
+			StringTokenizer tokenizer = new StringTokenizer(dropinRepositories, PIPE); //$NON-NLS-1$			
+			while (tokenizer.hasMoreTokens()) {
+				listProperty.add(tokenizer.nextToken());
+			}
+		}
+		return listProperty;
+	}
+
+	private void setListRepositoryProperty(IRepository repository, String key, List listProperty) {
+		StringBuffer buffer = new StringBuffer();
+		for (Iterator it = listProperty.iterator(); it.hasNext();) {
+			String repositoryString = (String) it.next();
+			buffer.append(repositoryString);
+			if (it.hasNext())
+				buffer.append(PIPE);
+		}
+		String value = (buffer.length() == 0) ? null : buffer.toString();
+		repository.setProperty(key, value);
+	}
+
+	public IMetadataRepository[] getMetadataRepositories() {
+		List result = new ArrayList(metadataRepositories);
+		result.add(getMetadataRepository());
+		return (IMetadataRepository[]) result.toArray(new IMetadataRepository[0]);
+	}
+
+}
diff --git a/bundles/org.eclipse.equinox.p2.reconciler/src/org/eclipse/equinox/internal/p2/reconciler/dropins/LinksManager.java b/bundles/org.eclipse.equinox.p2.reconciler/src/org/eclipse/equinox/internal/p2/reconciler/dropins/LinksManager.java
deleted file mode 100644
index 4a494d2..0000000
--- a/bundles/org.eclipse.equinox.p2.reconciler/src/org/eclipse/equinox/internal/p2/reconciler/dropins/LinksManager.java
+++ /dev/null
@@ -1,177 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2008 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.reconciler.dropins;
-
-import java.io.*;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.Iterator;
-import java.util.Properties;
-import org.eclipse.equinox.internal.p2.update.*;
-import org.eclipse.equinox.internal.provisional.p2.core.ProvisionException;
-
-/**
- * @since 1.0
- */
-public class LinksManager {
-
-	private static final String EXTENSION_LINK = ".link"; //$NON-NLS-1$
-	private static final String PATH_PROPERTY = "path"; //$NON-NLS-1$
-	private static final String PLATFORM_PROTOCOL = "platform:"; //$NON-NLS-1$
-	private static final String ECLIPSE_FOLDER = "eclipse"; //$NON-NLS-1$
-	private String defaultPolicy;
-	private Configuration configuration;
-	private boolean dirty = false;
-
-	/*
-	 * If one site has a MANAGED_ONLY policy, then newly discovered sites must also have
-	 * the same thing. Otherwise, they will have a policy of USER_EXCLUDE.
-	 */
-	private String getDefaultPolicy() {
-		if (defaultPolicy == null) {
-			for (Iterator iter = configuration.getSites().iterator(); defaultPolicy == null && iter.hasNext();) {
-				if (Site.POLICY_MANAGED_ONLY.equals(((Site) iter.next()).getPolicy()))
-					defaultPolicy = Site.POLICY_MANAGED_ONLY;
-			}
-			defaultPolicy = Site.POLICY_USER_EXCLUDE;
-		}
-		return defaultPolicy;
-	}
-
-	/*
-	 * Synchronize the given configuration file with the files that are in the specified links folder.
-	 * If any extension locations from the links folder are missing from the file, then update
-	 * the configuration.
-	 */
-	public void synchronize(File configurationFile, File linksFolder) throws ProvisionException {
-		if (!configurationFile.exists() || !linksFolder.exists())
-			return;
-
-		// read the existing configuration from disk
-		configuration = ConfigurationParser.parse(configurationFile, null);
-		if (configuration == null)
-			return;
-
-		// get the list of extension locations from the links folder
-		linksFolder.listFiles(new FileFilter() {
-			public boolean accept(File file) {
-				if (file.isFile() && file.getName().endsWith(EXTENSION_LINK))
-					configure(file);
-				return false;
-			}
-		});
-
-		// write out a new file if there were any changes.
-		if (dirty)
-			ConfigurationWriter.save(configuration, configurationFile, null);
-		dirty = false;
-	}
-
-	/*
-	 * Roughly copied from PlatformConfiguration#configureExternalLinkSite in 
-	 * Update Configurator.
-	 */
-	void configure(File location) {
-		String path = readExtension(location);
-		boolean updateable = true;
-
-		// parse out link information
-		if (path.startsWith("r ")) { //$NON-NLS-1$
-			updateable = false;
-			path = path.substring(2).trim();
-		} else if (path.startsWith("rw ")) { //$NON-NLS-1$
-			path = path.substring(3).trim();
-		} else {
-			path = path.trim();
-		}
-
-		URL url;
-		// 	make sure we have a valid link specification
-		try {
-			File siteFile = new File(path);
-			siteFile = new File(siteFile, ECLIPSE_FOLDER);
-			url = siteFile.toURL();
-			if (findConfiguredSite(url) != null)
-				// linked site is already known
-				return;
-		} catch (MalformedURLException e) {
-			// ignore bad links ...
-			e.printStackTrace();
-			return;
-		}
-
-		Site site = new Site();
-		site.setLinkFile(location.getAbsolutePath());
-		site.setEnabled(true);
-		site.setPolicy(getDefaultPolicy());
-		site.setUpdateable(updateable);
-		site.setUrl(url.toExternalForm());
-		configuration.add(site);
-		dirty = true;
-	}
-
-	/*
-	 * Look through the list of sites already known to this configuration
-	 * and determine if there is one with the given URL.
-	 */
-	private Site findConfiguredSite(URL url) {
-		String urlString = url.toExternalForm();
-		Site result = internalFindConfiguredSite(urlString);
-		if (result != null)
-			return result;
-		// try again with fixed URLs since they can be tricky
-		try {
-			urlString = Utils.decode(urlString, "UTF-8"); //$NON-NLS-1$
-		} catch (UnsupportedEncodingException e) {
-			// ignore
-		}
-		urlString = Utils.canonicalizeURL(urlString);
-		return internalFindConfiguredSite(urlString);
-	}
-
-	private Site internalFindConfiguredSite(String url) {
-		for (Iterator iter = configuration.getSites().iterator(); iter.hasNext();) {
-			Site site = (Site) iter.next();
-			String urlString = site.getUrl();
-			urlString = Utils.canonicalizeURL(urlString);
-			if (urlString.startsWith(PLATFORM_PROTOCOL))
-				continue;
-			if (urlString.equals(url))
-				return site;
-		}
-		return null;
-	}
-
-	/*
-	 * Read the contents of a link file and return the path. May or may not include
-	 * a prefix indicating read-only or read-write status.
-	 */
-	private String readExtension(File file) {
-		Properties props = new Properties();
-		InputStream input = null;
-		try {
-			input = new BufferedInputStream(new FileInputStream(file));
-			props.load(input);
-		} catch (IOException e) {
-			// TODO
-			e.printStackTrace();
-			return null;
-		} finally {
-			if (input != null)
-				try {
-					input.close();
-				} catch (IOException e) {
-					// ignore
-				}
-		}
-		return props.getProperty(PATH_PROPERTY);
-	}
-}
diff --git a/bundles/org.eclipse.equinox.p2.reconciler/src/org/eclipse/equinox/internal/p2/reconciler/dropins/PlatformXmlListener.java b/bundles/org.eclipse.equinox.p2.reconciler/src/org/eclipse/equinox/internal/p2/reconciler/dropins/PlatformXmlListener.java
index 9597335..6359520 100644
--- a/bundles/org.eclipse.equinox.p2.reconciler/src/org/eclipse/equinox/internal/p2/reconciler/dropins/PlatformXmlListener.java
+++ b/bundles/org.eclipse.equinox.p2.reconciler/src/org/eclipse/equinox/internal/p2/reconciler/dropins/PlatformXmlListener.java
@@ -20,7 +20,7 @@
 import org.eclipse.equinox.internal.p2.update.*;
 import org.eclipse.equinox.internal.provisional.p2.core.ProvisionException;
 import org.eclipse.equinox.internal.provisional.p2.directorywatcher.DirectoryChangeListener;
-import org.eclipse.equinox.internal.provisional.p2.directorywatcher.DirectoryWatcher;
+import org.eclipse.equinox.internal.provisional.p2.metadata.repository.IMetadataRepository;
 
 /**
  * @since 1.0
@@ -101,7 +101,7 @@
 		added(delta.added());
 		removed(delta.removed());
 		changed(delta.changed());
-		Activator.synchronize(getMetadataRepositories(), null); // TODO proper progress monitoring?
+		//Activator.synchronize(getMetadataRepositories(), null); // TODO proper progress monitoring?
 	}
 
 	// iterate over the site listeners and collect the metadata repositories
@@ -109,7 +109,7 @@
 		List result = new ArrayList();
 		for (Iterator iter = sites.values().iterator(); iter.hasNext();) {
 			SiteInfo info = (SiteInfo) iter.next();
-			result.add(info.getListener().getMetadataRepository());
+			result.add(info.getRepository());
 		}
 		return result;
 	}
@@ -133,12 +133,11 @@
 				} catch (IOException e) {
 					throw new ProvisionException(Messages.errorProcessingConfg, e);
 				}
-				File file = new File(url.getPath(), "plugins"); //$NON-NLS-1$
-				DirectoryWatcher watcher = new DirectoryWatcher(file);
-				SiteListener listener = new SiteListener(site);
-				watcher.addListener(listener);
-				watcher.poll();
-				sites.put(site.getUrl(), new SiteInfo(site, watcher, listener));
+				IMetadataRepository repo = Activator.loadMetadataRepository(url);
+				if (repo == null) {
+					// todo
+				} else
+					sites.put(site.getUrl(), new SiteInfo(site, repo));
 			} catch (MalformedURLException e) {
 				throw new ProvisionException(Messages.errorProcessingConfg, e);
 			}
@@ -159,7 +158,7 @@
 			if (info == null) {
 				// 
 			}
-			info.getWatcher().stop();
+
 			sites.remove(site.getUrl());
 		}
 	}
diff --git a/bundles/org.eclipse.equinox.p2.reconciler/src/org/eclipse/equinox/internal/p2/reconciler/dropins/ProfileSynchronizer.java b/bundles/org.eclipse.equinox.p2.reconciler/src/org/eclipse/equinox/internal/p2/reconciler/dropins/ProfileSynchronizer.java
index 4cf256e..1088115 100644
--- a/bundles/org.eclipse.equinox.p2.reconciler/src/org/eclipse/equinox/internal/p2/reconciler/dropins/ProfileSynchronizer.java
+++ b/bundles/org.eclipse.equinox.p2.reconciler/src/org/eclipse/equinox/internal/p2/reconciler/dropins/ProfileSynchronizer.java
@@ -10,208 +10,217 @@
 package org.eclipse.equinox.internal.p2.reconciler.dropins;
 
 import java.io.IOException;
+import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.*;
+import java.util.Map.Entry;
 import org.eclipse.core.runtime.*;
 import org.eclipse.equinox.internal.provisional.configurator.Configurator;
+import org.eclipse.equinox.internal.provisional.p2.artifact.repository.IArtifactRepository;
+import org.eclipse.equinox.internal.provisional.p2.artifact.repository.IFileArtifactRepository;
+import org.eclipse.equinox.internal.provisional.p2.core.ProvisionException;
 import org.eclipse.equinox.internal.provisional.p2.director.*;
 import org.eclipse.equinox.internal.provisional.p2.engine.*;
 import org.eclipse.equinox.internal.provisional.p2.metadata.IInstallableUnit;
 import org.eclipse.equinox.internal.provisional.p2.metadata.query.InstallableUnitQuery;
 import org.eclipse.equinox.internal.provisional.p2.metadata.repository.IMetadataRepository;
 import org.eclipse.equinox.internal.provisional.p2.query.Collector;
+import org.eclipse.equinox.internal.provisional.p2.query.Query;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.ServiceReference;
 
 public class ProfileSynchronizer {
 
-	private static final String FILE_LAST_MODIFIED = "file.lastModified"; //$NON-NLS-1$
-	private static final String FILE_NAME = "file.name"; //$NON-NLS-1$
-	private static final String REPOSITORY_ID = "repository.id"; //$NON-NLS-1$
-	private IInstallableUnit[] iusToRemove;
-	private IInstallableUnit[] iusToAdd;
-	private IProfile profile;
-	private List repositories;
+	public class ListCollector extends Collector {
+		public List getList() {
+			return super.getList();
+		}
+	}
+
+	private static final String SYNCH_REPOSITORY_ID = "synch.repository.id"; //$NON-NLS-1$
+	private static final String CACHE_EXTENSIONS = "org.eclipse.equinox.p2.cache.extensions"; //$NON-NLS-1$
+	private static final String PIPE = "|"; //$NON-NLS-1$
+	final IProfile profile;
+	final Map repositoryMap;
 
 	/*
 	 * Constructor for the class.
 	 */
-	public ProfileSynchronizer(IProfile profile, List repositories) {
-		super();
+	public ProfileSynchronizer(IProfile profile, Collection repositories) {
 		this.profile = profile;
-		this.repositories = repositories;
-		initialize();
-	}
-
-	public void add(List additions) {
-		this.repositories.addAll(repositories);
-		initialize();
-		// TODO progress monitoring
-		synchronize(null);
-	}
-
-	/*
-	 * Initialize the synchronizer with default values.
-	 */
-	private void initialize() {
-		// snapshot is a table of all the IUs from this repository which are installed in the profile 
-		final Map snapshot = new HashMap();
-		for (Iterator iter = repositories.iterator(); iter.hasNext();) {
-			IMetadataRepository metadataRepository = (IMetadataRepository) iter.next();
-			String repositoryId = metadataRepository.getLocation().toExternalForm();
-			Iterator it = profile.query(InstallableUnitQuery.ANY, new Collector(), null).iterator();
-			while (it.hasNext()) {
-				IInstallableUnit iu = (IInstallableUnit) it.next();
-				if (repositoryId.equals(iu.getProperty(REPOSITORY_ID))) {
-					String fileName = iu.getProperty(FILE_NAME);
-					if (fileName != null)
-						snapshot.put(fileName, iu);
-				}
-			}
-		}
-
-		final List toAdd = new ArrayList();
-		//create the collector that will visit all the IUs in the repositories being synchronized
-		Collector syncCollector = new Collector() {
-			public boolean accept(Object object) {
-				IInstallableUnit iu = (IInstallableUnit) object;
-				String iuFileName = iu.getProperty(FILE_NAME);
-				// TODO is this right?
-				if (iuFileName == null)
-					return true;
-
-				// if the repository contains an IU that the profile doesn't, then add it to the list to install
-				IInstallableUnit profileIU = (IInstallableUnit) snapshot.get(iuFileName);
-				if (profileIU == null) {
-					toAdd.add(iu);
-					return true;
-				}
-
-				Long iuLastModified = new Long(iu.getProperty(FILE_LAST_MODIFIED));
-				Long profileIULastModified = new Long(profileIU.getProperty(FILE_LAST_MODIFIED));
-				// TODO is this right?
-				if (iuLastModified == null || profileIULastModified == null)
-					return true;
-
-				// if the timestamp hasn't changed, then there is nothing to do so remove
-				// the IU from the snapshot so we don't accidentally remove it later
-				if (iuLastModified.equals(profileIULastModified))
-					snapshot.remove(iuFileName);
-				else
-					toAdd.add(iu);
-				return true;
-			}
-		};
-
+		this.repositoryMap = new HashMap();
 		for (Iterator it = repositories.iterator(); it.hasNext();) {
-			IMetadataRepository repo = (IMetadataRepository) it.next();
-			// TODO report progress
-			repo.query(InstallableUnitQuery.ANY, syncCollector, null);
-		}
-
-		// the IUs to remove is everything left that hasn't been removed from the snapshot
-		if (!snapshot.isEmpty()) {
-			iusToRemove = (IInstallableUnit[]) snapshot.values().toArray(new IInstallableUnit[snapshot.size()]);
-		}
-
-		// the list of IUs to add
-		if (!toAdd.isEmpty()) {
-			iusToAdd = (IInstallableUnit[]) toAdd.toArray(new IInstallableUnit[toAdd.size()]);
+			IMetadataRepository repository = (IMetadataRepository) it.next();
+			repositoryMap.put(repository.getLocation().toExternalForm(), repository);
 		}
 	}
 
 	/*
 	 * Synchronize the profile with the list of metadata repositories.
 	 */
-	public void synchronize(IProgressMonitor monitor) {
-		IStatus status = Status.OK_STATUS;
-		if (iusToRemove != null)
-			status = removeIUs(iusToRemove, null); // TODO proper progress monitoring
-		if (!status.isOK()) {
-			// TODO
-			throw new RuntimeException(new CoreException(status));
-		}
+	public IStatus synchronize(IProgressMonitor monitor) {
 
-		// disable repo cleanup for now until we see how we want to handle support for links folders and eclipse extensions
-		//removeUnwatchedRepositories(context, profile, watchedFolder);
+		IStatus status = synchronizeCacheExtensions();
+		if (!status.isOK())
+			return status;
 
-		if (iusToAdd != null)
-			status = addIUs(iusToAdd, null); // TODO proper progress monitoring
-		if (!status.isOK()) {
-			// TODO
-			throw new RuntimeException(new CoreException(status));
-		}
-		// if we did any work we have to apply the changes
-		if (iusToAdd != null || iusToRemove != null)
-			applyConfiguration();
-	}
-
-	/*
-	 * Install the given list of IUs.
-	 */
-	private IStatus addIUs(IInstallableUnit[] toAdd, IProgressMonitor monitor) {
-		BundleContext context = Activator.getContext();
+		ProfileChangeRequest request = createProfileChangeRequest();
+		if (request == null)
+			return Status.OK_STATUS;
 
 		SubMonitor sub = SubMonitor.convert(monitor, 100);
+		ProvisioningContext provisioningContext = new ProvisioningContext(new URL[0]);
 
-		ServiceReference reference = context.getServiceReference(IPlanner.class.getName());
-		IPlanner planner = (IPlanner) context.getService(reference);
+		ProvisioningPlan plan = createProvisioningPlan(request, provisioningContext, sub.newChild(50));
+		if (!plan.getStatus().isOK())
+			return plan.getStatus();
 
-		try {
-			ProfileChangeRequest request = new ProfileChangeRequest(profile);
-			request.addInstallableUnits(toAdd);
-			// mark the roots as such
-			for (int i = 0; i < toAdd.length; i++) {
-				if (Boolean.valueOf(toAdd[i].getProperty(IInstallableUnit.PROP_TYPE_GROUP)).booleanValue())
-					request.setInstallableUnitProfileProperty(toAdd[i], IInstallableUnit.PROP_PROFILE_ROOT_IU, Boolean.toString(true));
+		if (plan.getOperands().length == 0) {
+			sub.done();
+			return Status.OK_STATUS;
+		}
+
+		status = executePlan(plan, provisioningContext, sub.newChild(50));
+		if (!status.isOK())
+			return status;
+
+		applyConfiguration();
+		return Status.OK_STATUS;
+	}
+
+	private IStatus synchronizeCacheExtensions() {
+		List currentExtensions = new ArrayList();
+		StringBuffer buffer = new StringBuffer();
+		for (Iterator it = repositoryMap.keySet().iterator(); it.hasNext();) {
+			String repositoryId = (String) it.next();
+			try {
+				IArtifactRepository repository = Activator.loadArtifactRepository(new URL(repositoryId));
+
+				if (repository instanceof IFileArtifactRepository) {
+					currentExtensions.add(repositoryId);
+					buffer.append(repositoryId);
+					if (it.hasNext())
+						buffer.append(PIPE);
+				}
+			} catch (ProvisionException e) {
+				// ignore
+			} catch (MalformedURLException e) {
+				// unexpected
+				e.printStackTrace();
 			}
-
-			ProvisioningContext provisioningContext = new ProvisioningContext(new URL[0]);
-			ProvisioningPlan plan = planner.getProvisioningPlan(request, provisioningContext, sub.newChild(50));
-			if (!plan.getStatus().isOK())
-				return plan.getStatus();
-
-			return executePlan(plan, provisioningContext, sub.newChild(50));
-
-		} finally {
-			context.ungetService(reference);
 		}
+		String currentExtensionsProperty = (buffer.length() == 0) ? null : buffer.toString();
+
+		List previousExtensions = new ArrayList();
+		String previousExtensionsProperty = profile.getProperty(CACHE_EXTENSIONS);
+		if (previousExtensionsProperty != null) {
+			StringTokenizer tokenizer = new StringTokenizer(previousExtensionsProperty, PIPE);
+			while (tokenizer.hasMoreTokens()) {
+				previousExtensions.add(tokenizer.nextToken());
+			}
+		}
+
+		if (previousExtensions.size() == currentExtensions.size() && previousExtensions.containsAll(currentExtensions))
+			return Status.OK_STATUS;
+
+		Operand operand = new PropertyOperand(CACHE_EXTENSIONS, previousExtensionsProperty, currentExtensionsProperty);
+
+		return executeOperands(new ProvisioningContext(new URL[0]), new Operand[] {operand}, null);
 	}
 
-	/*
-	 * Uninstall the given list of IUs.
-	 */
-	private IStatus removeIUs(IInstallableUnit[] toRemove, IProgressMonitor monitor) {
+	private ProfileChangeRequest createProfileChangeRequest() {
+		boolean modified = false;
+		Collection profileIUs = getProfileIUs();
+		Collection toRemove = getStaleIUs();
+		profileIUs.removeAll(toRemove);
+
+		ProfileChangeRequest request = new ProfileChangeRequest(profile);
+		for (Iterator it = repositoryMap.entrySet().iterator(); it.hasNext();) {
+			Entry entry = (Entry) it.next();
+			String repositoryId = (String) entry.getKey();
+			IMetadataRepository repository = (IMetadataRepository) entry.getValue();
+			Iterator repositoryIterator = repository.query(InstallableUnitQuery.ANY, new Collector(), null).iterator();
+			while (repositoryIterator.hasNext()) {
+				IInstallableUnit iu = (IInstallableUnit) repositoryIterator.next();
+				if (profileIUs.contains(iu))
+					continue;
+
+				if (toRemove.contains(iu)) {
+					toRemove.remove(iu);
+				} else {
+					request.addInstallableUnits(new IInstallableUnit[] {iu});
+					if (Boolean.valueOf(iu.getProperty(IInstallableUnit.PROP_TYPE_GROUP)).booleanValue())
+						request.setInstallableUnitProfileProperty(iu, IInstallableUnit.PROP_PROFILE_ROOT_IU, Boolean.TRUE.toString());
+				}
+				request.setInstallableUnitProfileProperty(iu, SYNCH_REPOSITORY_ID, repositoryId);
+				profileIUs.add(iu);
+				modified = true;
+			}
+		}
+		if (!toRemove.isEmpty()) {
+			request.removeInstallableUnits((IInstallableUnit[]) toRemove.toArray(new IInstallableUnit[0]));
+			modified = true;
+		}
+
+		if (!modified)
+			return null;
+
+		return request;
+	}
+
+	private Collection getStaleIUs() {
+		Query removeQuery = new Query() {
+			public boolean isMatch(Object object) {
+				IInstallableUnit iu = (IInstallableUnit) object;
+				String repositoryId = profile.getInstallableUnitProperty(iu, SYNCH_REPOSITORY_ID);
+				if (repositoryId == null)
+					return false;
+
+				IMetadataRepository repo = (IMetadataRepository) repositoryMap.get(repositoryId);
+				Query iuQuery = new InstallableUnitQuery(iu.getId(), iu.getVersion());
+				return (repo == null || repo.query(iuQuery, new Collector(), null).isEmpty());
+			}
+		};
+
+		ListCollector listCollector = new ListCollector();
+		profile.query(removeQuery, listCollector, null);
+
+		List result = listCollector.getList();
+		return (result != null) ? result : Collections.EMPTY_LIST;
+	}
+
+	private List getProfileIUs() {
+		ListCollector listCollector = new ListCollector();
+		profile.query(InstallableUnitQuery.ANY, listCollector, null);
+
+		List result = listCollector.getList();
+		return (result != null) ? result : Collections.EMPTY_LIST;
+	}
+
+	private ProvisioningPlan createProvisioningPlan(ProfileChangeRequest request, ProvisioningContext provisioningContext, IProgressMonitor monitor) {
 		BundleContext context = Activator.getContext();
-
-		SubMonitor sub = SubMonitor.convert(monitor, 100);
-
 		ServiceReference reference = context.getServiceReference(IPlanner.class.getName());
 		IPlanner planner = (IPlanner) context.getService(reference);
 
 		try {
-			ProfileChangeRequest request = new ProfileChangeRequest(profile);
-			request.removeInstallableUnits(toRemove);
-
-			ProvisioningContext provisioningContext = new ProvisioningContext(new URL[0]);
-			ProvisioningPlan plan = planner.getProvisioningPlan(request, provisioningContext, sub.newChild(50));
-			if (!plan.getStatus().isOK())
-				return plan.getStatus();
-
-			return executePlan(plan, provisioningContext, sub.newChild(50));
-
+			return planner.getProvisioningPlan(request, provisioningContext, monitor);
 		} finally {
 			context.ungetService(reference);
 		}
 	}
 
 	private IStatus executePlan(ProvisioningPlan plan, ProvisioningContext provisioningContext, IProgressMonitor monitor) {
+		Operand[] operands = plan.getOperands();
+		return executeOperands(provisioningContext, operands, monitor);
+	}
+
+	private IStatus executeOperands(ProvisioningContext provisioningContext, Operand[] operands, IProgressMonitor monitor) {
 		BundleContext context = Activator.getContext();
 		ServiceReference reference = context.getServiceReference(IEngine.class.getName());
 		IEngine engine = (IEngine) context.getService(reference);
 		try {
 			PhaseSet phaseSet = new DefaultPhaseSet();
-			IStatus engineResult = engine.perform(profile, phaseSet, plan.getOperands(), provisioningContext, monitor);
+			IStatus engineResult = engine.perform(profile, phaseSet, operands, provisioningContext, monitor);
 			return engineResult;
 		} finally {
 			context.ungetService(reference);
@@ -234,5 +243,4 @@
 			context.ungetService(reference);
 		}
 	}
-
 }
diff --git a/bundles/org.eclipse.equinox.p2.reconciler/src/org/eclipse/equinox/internal/p2/reconciler/dropins/SiteInfo.java b/bundles/org.eclipse.equinox.p2.reconciler/src/org/eclipse/equinox/internal/p2/reconciler/dropins/SiteInfo.java
index e9c3e47..d891811 100644
--- a/bundles/org.eclipse.equinox.p2.reconciler/src/org/eclipse/equinox/internal/p2/reconciler/dropins/SiteInfo.java
+++ b/bundles/org.eclipse.equinox.p2.reconciler/src/org/eclipse/equinox/internal/p2/reconciler/dropins/SiteInfo.java
@@ -11,23 +11,21 @@
 package org.eclipse.equinox.internal.p2.reconciler.dropins;
 
 import org.eclipse.equinox.internal.p2.update.Site;
-import org.eclipse.equinox.internal.provisional.p2.directorywatcher.DirectoryWatcher;
+import org.eclipse.equinox.internal.provisional.p2.metadata.repository.IMetadataRepository;
 
 /*
  * Internal class contains information about watchers and sites. Used for caching.
  */
 public class SiteInfo {
-	private DirectoryWatcher watcher;
-	private SiteListener listener;
+	private IMetadataRepository repository;
 	private Site site;
 	private String url;
 
-	public SiteInfo(Site site, DirectoryWatcher watcher, SiteListener listener) {
+	public SiteInfo(Site site, IMetadataRepository repository) {
 		super();
 		this.site = site;
 		this.url = site.getUrl();
-		this.watcher = watcher;
-		this.listener = listener;
+		this.repository = repository;
 	}
 
 	public Site getSite() {
@@ -38,12 +36,8 @@
 		return url;
 	}
 
-	public DirectoryWatcher getWatcher() {
-		return watcher;
-	}
-
-	public SiteListener getListener() {
-		return listener;
+	public IMetadataRepository getRepository() {
+		return repository;
 	}
 
 	/* (non-Javadoc)
diff --git a/bundles/org.eclipse.equinox.p2.reconciler/src/org/eclipse/equinox/internal/p2/reconciler/dropins/SiteListener.java b/bundles/org.eclipse.equinox.p2.reconciler/src/org/eclipse/equinox/internal/p2/reconciler/dropins/SiteListener.java
deleted file mode 100644
index f63e16b..0000000
--- a/bundles/org.eclipse.equinox.p2.reconciler/src/org/eclipse/equinox/internal/p2/reconciler/dropins/SiteListener.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2007, 2008 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.reconciler.dropins;
-
-import java.io.File;
-import org.eclipse.equinox.internal.p2.update.Site;
-import org.eclipse.equinox.internal.provisional.p2.directorywatcher.RepositoryListener;
-
-/**
- * @since 1.0
- */
-public class SiteListener extends RepositoryListener {
-
-	private Site site;
-
-	/*
-	 * Create a new site listener on the given site.
-	 */
-	public SiteListener(Site site) {
-		super(Activator.getContext(), Integer.toString(site.getUrl().hashCode()));
-		this.site = site;
-	}
-
-	/*
-	 * Return true if the given list contains the symbolic name for the bundle
-	 * represented by the given file handle. Return false otherwise.
-	 */
-	private boolean contains(String[] plugins, File file) {
-		String filename = file.getAbsolutePath();
-		for (int i = 0; i < plugins.length; i++)
-			if (plugins[i].endsWith(filename))
-				return true;
-		return false;
-	}
-
-	/* (non-Javadoc)
-	 * @see org.eclipse.equinox.internal.provisional.p2.directorywatcher.DirectoryChangeListener#isInterested(java.io.File)
-	 */
-	public boolean isInterested(File file) {
-		String policy = site.getPolicy();
-		String[] plugins = site.getList();
-		if (Site.POLICY_MANAGED_ONLY.equals(policy)) {
-			// TODO
-		} else if (Site.POLICY_USER_EXCLUDE.equals(policy)) {
-			// ensure the file doesn't refer to a plug-in in our list
-			return plugins.length == 0 ? true : !contains(plugins, file);
-		} else if (Site.POLICY_USER_INCLUDE.equals(policy)) {
-			// we are only interested in plug-ins in the list
-			return plugins.length == 0 ? false : contains(plugins, file);
-		}
-		return false;
-	}
-}
diff --git a/bundles/org.eclipse.equinox.p2.touchpoint.eclipse/src/org/eclipse/equinox/internal/p2/touchpoint/eclipse/EclipseTouchpoint.java b/bundles/org.eclipse.equinox.p2.touchpoint.eclipse/src/org/eclipse/equinox/internal/p2/touchpoint/eclipse/EclipseTouchpoint.java
index 8464bce..4f57c42 100644
--- a/bundles/org.eclipse.equinox.p2.touchpoint.eclipse/src/org/eclipse/equinox/internal/p2/touchpoint/eclipse/EclipseTouchpoint.java
+++ b/bundles/org.eclipse.equinox.p2.touchpoint.eclipse/src/org/eclipse/equinox/internal/p2/touchpoint/eclipse/EclipseTouchpoint.java
@@ -891,7 +891,12 @@
 			featureVersion = artifactKey.getVersion().toString();
 		}
 
-		return configuration.addFeatureEntry(featureId, featureVersion, artifactKey.getId(), artifactKey.getVersion().toString(), /*primary*/false, /*application*/null, /*root*/null);
+		IProfile profile = (IProfile) parameters.get(PARM_PROFILE);
+		File file = Util.getBundleFile(artifactKey, profile);
+		if (file == null || !file.exists()) {
+			// todo
+		}
+		return configuration.addFeatureEntry(file, featureId, featureVersion, artifactKey.getId(), artifactKey.getVersion().toString(), /*primary*/false, /*application*/null, /*root*/null);
 	}
 
 	protected IStatus uninstallFeature(Map parameters) {
diff --git a/bundles/org.eclipse.equinox.p2.touchpoint.eclipse/src/org/eclipse/equinox/internal/p2/touchpoint/eclipse/PlatformConfigurationWrapper.java b/bundles/org.eclipse.equinox.p2.touchpoint.eclipse/src/org/eclipse/equinox/internal/p2/touchpoint/eclipse/PlatformConfigurationWrapper.java
index 8f6b80c..0157c2e 100644
--- a/bundles/org.eclipse.equinox.p2.touchpoint.eclipse/src/org/eclipse/equinox/internal/p2/touchpoint/eclipse/PlatformConfigurationWrapper.java
+++ b/bundles/org.eclipse.equinox.p2.touchpoint.eclipse/src/org/eclipse/equinox/internal/p2/touchpoint/eclipse/PlatformConfigurationWrapper.java
@@ -11,6 +11,7 @@
 package org.eclipse.equinox.internal.p2.touchpoint.eclipse;
 
 import java.io.File;
+import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.Iterator;
 import java.util.List;
@@ -55,38 +56,90 @@
 			throw new IllegalStateException("Error parsing platform configuration."); //$NON-NLS-1$;
 		}
 
-		List sites = configuration.getSites();
-		for (Iterator iter = sites.iterator(); iter.hasNext();) {
-			Site nextSite = (Site) iter.next();
-			String nextURL = nextSite.getUrl();
-			if (new Path(nextURL).equals(new Path(poolURL.toExternalForm()))) {
-				poolSite = nextSite;
-				break;
-			}
-		}
-
+		poolSite = getSite(poolURL);
 		if (poolSite == null) {
-			poolSite = new Site();
-			poolSite.setUrl(poolURL.toExternalForm());
-			poolSite.setPolicy(Site.POLICY_MANAGED_ONLY);
-			poolSite.setEnabled(true);
+			poolSite = createSite(poolURL);
 			configuration.add(poolSite);
 		}
 	}
 
 	/*
+	 * Create and return a site object based on the given location.
+	 */
+	private Site createSite(URL location) {
+		Site result = new Site();
+		result.setUrl(location.toExternalForm());
+		result.setPolicy(Site.POLICY_MANAGED_ONLY);
+		result.setEnabled(true);
+		return result;
+	}
+
+	/*
+	 * Look in the configuration and return the site object whose location matches
+	 * the given URL. Return null if there is no match.
+	 */
+	private Site getSite(URL url) {
+		List sites = configuration.getSites();
+		for (Iterator iter = sites.iterator(); iter.hasNext();) {
+			Site nextSite = (Site) iter.next();
+			String nextURL = nextSite.getUrl();
+			if (new Path(nextURL).equals(new Path(url.toExternalForm()))) {
+				return nextSite;
+			}
+		}
+		return null;
+	}
+
+	/*
+	 * Look in the configuration and return the site which contains the feature
+	 * with the given identifier and version. Return null if there is none.
+	 */
+	private Site getSite(String id, String version) {
+		List sites = configuration.getSites();
+		for (Iterator iter = sites.iterator(); iter.hasNext();) {
+			Site site = (Site) iter.next();
+			Feature[] features = site.getFeatures();
+			for (int i = 0; i < features.length; i++) {
+				if (id.equals(features[i].getId()) && version.equals(features[i].getVersion()))
+					return site;
+			}
+		}
+		return null;
+	}
+
+	/*
 	 * @see org.eclipse.update.configurator.IPlatformConfiguration#createFeatureEntry(java.lang.String, java.lang.String, java.lang.String, java.lang.String, boolean, java.lang.String, java.net.URL[])
 	 */
-	public IStatus addFeatureEntry(String id, String version, String pluginIdentifier, String pluginVersion, boolean primary, String application, URL[] root) {
+	public IStatus addFeatureEntry(File file, String id, String version, String pluginIdentifier, String pluginVersion, boolean primary, String application, URL[] root) {
 		loadDelegate();
 		if (configuration == null)
 			return new Status(IStatus.WARNING, Activator.ID, "Platform configuration not available.", null); //$NON-NLS-1$
 
-		Feature addedFeature = new Feature(poolSite);
+		URL fileURL = null;
+		try {
+			File featureDir = file.getParentFile();
+			if (featureDir == null || featureDir.getName().equals("features"))
+				return new Status(IStatus.ERROR, Activator.ID, "Parent directory should be \"features\": " + file.getAbsolutePath(), null);
+			File locationDir = featureDir.getParentFile();
+			if (locationDir == null)
+				return new Status(IStatus.ERROR, Activator.ID, "Unable to calculate extension location for: " + file.getAbsolutePath(), null);
+
+			fileURL = locationDir.toURL();
+		} catch (MalformedURLException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+			return new Status(IStatus.ERROR, Activator.ID, "Unable to create URL from file: " + file.getAbsolutePath(), null);
+		}
+		Site site = getSite(fileURL);
+		if (site == null) {
+			site = createSite(fileURL);
+			configuration.add(site);
+		}
+		Feature addedFeature = new Feature(site);
 		addedFeature.setId(id);
 		addedFeature.setVersion(version);
 		addedFeature.setUrl(makeFeatureURL(id, version));
-		poolSite.addFeature(addedFeature);
+		site.addFeature(addedFeature);
 		return Status.OK_STATUS;
 	}
 
@@ -98,7 +151,10 @@
 		if (configuration == null)
 			return new Status(IStatus.WARNING, Activator.ID, "Platform configuration not available.", null); //$NON-NLS-1$
 
-		Feature removedFeature = poolSite.removeFeature(makeFeatureURL(id, version));
+		Site site = getSite(id, version);
+		if (site == null)
+			site = poolSite;
+		Feature removedFeature = site.removeFeature(makeFeatureURL(id, version));
 		return (removedFeature != null ? Status.OK_STATUS : new Status(IStatus.ERROR, Activator.ID, "A feature with the specified id was not found.", null)); //$NON-NLS-1$
 	}
 
diff --git a/bundles/org.eclipse.equinox.p2.touchpoint.eclipse/src/org/eclipse/equinox/internal/p2/touchpoint/eclipse/Util.java b/bundles/org.eclipse.equinox.p2.touchpoint.eclipse/src/org/eclipse/equinox/internal/p2/touchpoint/eclipse/Util.java
index 0c0e8aa..e807aa7 100644
--- a/bundles/org.eclipse.equinox.p2.touchpoint.eclipse/src/org/eclipse/equinox/internal/p2/touchpoint/eclipse/Util.java
+++ b/bundles/org.eclipse.equinox.p2.touchpoint.eclipse/src/org/eclipse/equinox/internal/p2/touchpoint/eclipse/Util.java
@@ -40,7 +40,8 @@
 	 */
 	private final static String CONFIG_FOLDER = "eclipse.configurationFolder"; //$NON-NLS-1$
 	private static final String REPOSITORY_TYPE = IArtifactRepositoryManager.TYPE_SIMPLE_REPOSITORY;
-	private static final Object PROFILE_EXTENSION = "profile.extension"; //$NON-NLS-1$
+	private static final String CACHE_EXTENSIONS = "org.eclipse.equinox.p2.cache.extensions"; //$NON-NLS-1$
+	private static final String PIPE = "|"; //$NON-NLS-1$
 
 	static AgentLocation getAgentLocation() {
 		return (AgentLocation) ServiceHelper.getService(Activator.getContext(), AgentLocation.class.getName());
@@ -90,20 +91,36 @@
 		bundleRepositories.add(Util.getBundlePoolRepository(profile));
 
 		IArtifactRepositoryManager manager = getArtifactRepositoryManager();
-		URL[] knownRepositories = manager.getKnownRepositories(IArtifactRepositoryManager.REPOSITORIES_ALL);
-		for (int i = 0; i < knownRepositories.length; i++) {
+		List extensions = getListProfileProperty(profile, CACHE_EXTENSIONS);
+		for (Iterator iterator = extensions.iterator(); iterator.hasNext();) {
 			try {
-				IArtifactRepository repository = manager.loadRepository(knownRepositories[i], null);
-				String profileExtension = (String) repository.getProperties().get(PROFILE_EXTENSION);
-				if (profileExtension != null && profileExtension.equals(profile.getProfileId()))
+				String extension = (String) iterator.next();
+				URL extensionURL = new URL(extension);
+				IArtifactRepository repository = manager.loadRepository(extensionURL, null);
+				if (repository != null)
 					bundleRepositories.add(repository);
 			} catch (ProvisionException e) {
 				//skip repositories that could not be read
+			} catch (MalformedURLException e) {
+				// unexpected, URLs should be pre-checked
+				e.printStackTrace();
 			}
 		}
 		return new AggregatedBundleRepository(bundleRepositories);
 	}
 
+	private static List getListProfileProperty(IProfile profile, String key) {
+		List listProperty = new ArrayList();
+		String dropinRepositories = profile.getProperty(key);
+		if (dropinRepositories != null) {
+			StringTokenizer tokenizer = new StringTokenizer(dropinRepositories, PIPE);
+			while (tokenizer.hasMoreTokens()) {
+				listProperty.add(tokenizer.nextToken());
+			}
+		}
+		return listProperty;
+	}
+
 	static BundleInfo createBundleInfo(File bundleFile, String manifest) {
 		BundleInfo bundleInfo = new BundleInfo();
 		try {