Integrating changes for adopting the publisher
diff --git a/bundles/org.eclipse.equinox.p2.directorywatcher/META-INF/MANIFEST.MF b/bundles/org.eclipse.equinox.p2.directorywatcher/META-INF/MANIFEST.MF
index db569a4..88746d4 100644
--- a/bundles/org.eclipse.equinox.p2.directorywatcher/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.equinox.p2.directorywatcher/META-INF/MANIFEST.MF
@@ -6,16 +6,18 @@
 Bundle-Localization: plugin
 Bundle-Version: 1.0.100.qualifier
 Import-Package: org.eclipse.equinox.internal.p2.core.helpers,
- org.eclipse.equinox.internal.p2.metadata.generator.features,
+ org.eclipse.equinox.internal.p2.metadata,
  org.eclipse.equinox.internal.provisional.p2.artifact.repository,
  org.eclipse.equinox.internal.provisional.p2.core,
  org.eclipse.equinox.internal.provisional.p2.core.eventbus,
  org.eclipse.equinox.internal.provisional.p2.core.repository,
  org.eclipse.equinox.internal.provisional.p2.metadata,
- org.eclipse.equinox.internal.provisional.p2.metadata.generator,
  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.p2.publisher,
+ org.eclipse.equinox.p2.publisher.actions,
+ org.eclipse.equinox.p2.publisher.eclipse,
  org.eclipse.osgi.service.resolver;version="1.1.0",
  org.eclipse.osgi.util,
  org.osgi.framework;version="1.4.0",
diff --git a/bundles/org.eclipse.equinox.p2.directorywatcher/src/org/eclipse/equinox/internal/provisional/p2/directorywatcher/Activator.java b/bundles/org.eclipse.equinox.p2.directorywatcher/src/org/eclipse/equinox/internal/provisional/p2/directorywatcher/Activator.java
index e3bf2c4..63f3ad8 100644
--- a/bundles/org.eclipse.equinox.p2.directorywatcher/src/org/eclipse/equinox/internal/provisional/p2/directorywatcher/Activator.java
+++ b/bundles/org.eclipse.equinox.p2.directorywatcher/src/org/eclipse/equinox/internal/provisional/p2/directorywatcher/Activator.java
@@ -7,11 +7,18 @@
  * 
  * Contributors:
  *     IBM Corporation - initial API and implementation
+ *     Code 9 - ongoing development
  *******************************************************************************/
 package org.eclipse.equinox.internal.provisional.p2.directorywatcher;
 
-import org.osgi.framework.BundleActivator;
-import org.osgi.framework.BundleContext;
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URL;
+import org.eclipse.equinox.internal.p2.core.helpers.ServiceHelper;
+import org.eclipse.equinox.internal.provisional.p2.artifact.repository.IArtifactRepositoryManager;
+import org.eclipse.equinox.internal.provisional.p2.metadata.repository.IMetadataRepositoryManager;
+import org.osgi.framework.*;
+import org.osgi.service.packageadmin.PackageAdmin;
 
 /**
  * Bundle activator for directory watcher bundle.
@@ -25,18 +32,33 @@
 		return context;
 	}
 
-	/* (non-Javadoc)
-	 * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
-	 */
 	public void start(BundleContext aContext) throws Exception {
 		context = aContext;
 	}
 
-	/* (non-Javadoc)
-	 * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
-	 */
 	public void stop(BundleContext aContext) throws Exception {
 		context = null;
 	}
 
+	public static IArtifactRepositoryManager getArtifactRepositoryManager() {
+		return (IArtifactRepositoryManager) ServiceHelper.getService(context, IArtifactRepositoryManager.class.getName());
+	}
+
+	public static IMetadataRepositoryManager getMetadataRepositoryManager() {
+		return (IMetadataRepositoryManager) ServiceHelper.getService(context, IMetadataRepositoryManager.class.getName());
+	}
+
+	public static URL getDefaultRepositoryLocation(Object object, String repositoryName) {
+		PackageAdmin packageAdmin = (PackageAdmin) ServiceHelper.getService(context, PackageAdmin.class.getName());
+		Bundle bundle = packageAdmin.getBundle(object.getClass());
+		BundleContext context = bundle.getBundleContext();
+		File base = context.getDataFile(""); //$NON-NLS-1$
+		File result = new File(base, "listener_" + repositoryName.hashCode()); //$NON-NLS-1$
+		result.mkdirs();
+		try {
+			return result.toURL();
+		} catch (MalformedURLException e) {
+			return null;
+		}
+	}
 }
diff --git a/bundles/org.eclipse.equinox.p2.directorywatcher/src/org/eclipse/equinox/internal/provisional/p2/directorywatcher/CachingArtifactRepository.java b/bundles/org.eclipse.equinox.p2.directorywatcher/src/org/eclipse/equinox/internal/provisional/p2/directorywatcher/CachingArtifactRepository.java
new file mode 100644
index 0000000..0c52b62
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.directorywatcher/src/org/eclipse/equinox/internal/provisional/p2/directorywatcher/CachingArtifactRepository.java
@@ -0,0 +1,233 @@
+/*******************************************************************************
+ * Copyright (c) 2008 Code 9 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: 
+ *   Code 9 - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.equinox.internal.provisional.p2.directorywatcher;
+
+import java.io.File;
+import java.io.OutputStream;
+import java.net.URL;
+import java.util.*;
+import org.eclipse.core.runtime.*;
+import org.eclipse.equinox.internal.provisional.p2.artifact.repository.*;
+import org.eclipse.equinox.internal.provisional.p2.metadata.IArtifactKey;
+
+public class CachingArtifactRepository implements IArtifactRepository, IFileArtifactRepository {
+
+	private static final String NULL = ""; //$NON-NLS-1$
+	private IArtifactRepository innerRepo;
+	private Set descriptorsToAdd = new HashSet();
+	private Map artifactMap = new HashMap();
+	private Set descriptorsToRemove = new HashSet();
+	private Map propertyChanges = new HashMap();
+
+	protected CachingArtifactRepository(IArtifactRepository innerRepo) {
+		this.innerRepo = innerRepo;
+	}
+
+	public void save() {
+		savePropertyChanges();
+		saveAdditions();
+		saveRemovals();
+	}
+
+	private void saveRemovals() {
+		for (Iterator i = descriptorsToRemove.iterator(); i.hasNext();)
+			innerRepo.removeDescriptor((IArtifactDescriptor) i.next());
+		descriptorsToRemove.clear();
+	}
+
+	private void saveAdditions() {
+		if (descriptorsToAdd.isEmpty())
+			return;
+		innerRepo.addDescriptors((IArtifactDescriptor[]) descriptorsToAdd.toArray(new IArtifactDescriptor[descriptorsToAdd.size()]));
+		descriptorsToAdd.clear();
+		artifactMap.clear();
+	}
+
+	private void savePropertyChanges() {
+		for (Iterator i = propertyChanges.keySet().iterator(); i.hasNext();) {
+			String key = (String) i.next();
+			String value = (String) propertyChanges.get(key);
+			innerRepo.setProperty(key, value == NULL ? null : value);
+		}
+		propertyChanges.clear();
+	}
+
+	private void mapDescriptor(IArtifactDescriptor descriptor) {
+		IArtifactKey key = descriptor.getArtifactKey();
+		Collection descriptors = (Collection) artifactMap.get(key);
+		if (descriptors == null) {
+			descriptors = new ArrayList();
+			artifactMap.put(key, descriptors);
+		}
+		descriptors.add(descriptor);
+	}
+
+	private void unmapDescriptor(IArtifactDescriptor descriptor) {
+		IArtifactKey key = descriptor.getArtifactKey();
+		Collection descriptors = (Collection) artifactMap.get(key);
+		if (descriptors == null) {
+			// we do not have the descriptor locally so remember it to be removed from
+			// the inner repo on save.
+			descriptorsToRemove.add(descriptor);
+			return;
+		}
+
+		descriptors.remove(descriptor);
+		if (descriptors.isEmpty())
+			artifactMap.remove(key);
+	}
+
+	public synchronized void addDescriptors(IArtifactDescriptor[] descriptors) {
+		for (int i = 0; i < descriptors.length; i++) {
+			((ArtifactDescriptor) descriptors[i]).setRepository(this);
+			descriptorsToAdd.add(descriptors[i]);
+			mapDescriptor(descriptors[i]);
+		}
+	}
+
+	public synchronized void addDescriptor(IArtifactDescriptor toAdd) {
+		((ArtifactDescriptor) toAdd).setRepository(this);
+		descriptorsToAdd.add(toAdd);
+		mapDescriptor(toAdd);
+	}
+
+	public synchronized IArtifactDescriptor[] getArtifactDescriptors(IArtifactKey key) {
+		Collection result = (Collection) artifactMap.get(key);
+		if (result == null)
+			return innerRepo.getArtifactDescriptors(key);
+		result.addAll(Arrays.asList(innerRepo.getArtifactDescriptors(key)));
+		return (IArtifactDescriptor[]) result.toArray(new IArtifactDescriptor[result.size()]);
+	}
+
+	public synchronized IArtifactKey[] getArtifactKeys() {
+		// there may be more descriptors than keys to collect up the unique keys
+		HashSet result = new HashSet();
+		result.addAll(artifactMap.keySet());
+		result.addAll(Arrays.asList(innerRepo.getArtifactKeys()));
+		return (IArtifactKey[]) result.toArray(new IArtifactKey[result.size()]);
+	}
+
+	public synchronized boolean contains(IArtifactDescriptor descriptor) {
+		return descriptorsToAdd.contains(descriptor) || innerRepo.contains(descriptor);
+	}
+
+	public synchronized boolean contains(IArtifactKey key) {
+		return artifactMap.containsKey(key) || innerRepo.contains(key);
+	}
+
+	public IStatus getArtifact(IArtifactDescriptor descriptor, OutputStream destination, IProgressMonitor monitor) {
+		return innerRepo.getArtifact(descriptor, destination, monitor);
+	}
+
+	public IStatus getArtifacts(IArtifactRequest[] requests, IProgressMonitor monitor) {
+		return Status.OK_STATUS;
+	}
+
+	public OutputStream getOutputStream(IArtifactDescriptor descriptor) {
+		return null;
+	}
+
+	public synchronized void removeAll() {
+		IArtifactDescriptor[] toRemove = (IArtifactDescriptor[]) descriptorsToAdd.toArray(new IArtifactDescriptor[descriptorsToAdd.size()]);
+		for (int i = 0; i < toRemove.length; i++)
+			doRemoveArtifact(toRemove[i]);
+	}
+
+	public synchronized void removeDescriptor(IArtifactDescriptor descriptor) {
+		doRemoveArtifact(descriptor);
+	}
+
+	public synchronized void removeDescriptor(IArtifactKey key) {
+		IArtifactDescriptor[] toRemove = getArtifactDescriptors(key);
+		for (int i = 0; i < toRemove.length; i++)
+			doRemoveArtifact(toRemove[i]);
+	}
+
+	/**
+	 * Removes the given descriptor and returns <code>true</code> if and only if the
+	 * descriptor existed in the repository, and was successfully removed.
+	 */
+	private boolean doRemoveArtifact(IArtifactDescriptor descriptor) {
+		// if the descriptor is already in the pending additoins, remove it
+		boolean result = descriptorsToAdd.remove(descriptor);
+		if (result)
+			unmapDescriptor(descriptor);
+		// either way, note this as a descriptor to remove from the inner repo
+		descriptorsToRemove.add(descriptor);
+		return result;
+	}
+
+	public String getDescription() {
+		return innerRepo.getDescription();
+	}
+
+	public URL getLocation() {
+		return innerRepo.getLocation();
+	}
+
+	public String getName() {
+		return innerRepo.getName();
+	}
+
+	public Map getProperties() {
+		// TODO need to combine the local and inner properties
+		return innerRepo.getProperties();
+	}
+
+	public String getProvider() {
+		return innerRepo.getProvider();
+	}
+
+	public String getType() {
+		return innerRepo.getType();
+	}
+
+	public String getVersion() {
+		return innerRepo.getVersion();
+	}
+
+	public boolean isModifiable() {
+		return innerRepo.isModifiable();
+	}
+
+	public void setDescription(String description) {
+		innerRepo.setDescription(description);
+	}
+
+	public void setName(String name) {
+		innerRepo.setName(name);
+	}
+
+	public String setProperty(String key, String value) {
+		String result = (String) getProperties().get(key);
+		propertyChanges.put(key, value == null ? NULL : value);
+		return result;
+	}
+
+	public void setProvider(String provider) {
+		innerRepo.setProvider(provider);
+	}
+
+	public Object getAdapter(Class adapter) {
+		return innerRepo.getAdapter(adapter);
+	}
+
+	public File getArtifactFile(IArtifactKey key) {
+		if (innerRepo instanceof IFileArtifactRepository)
+			return ((IFileArtifactRepository) innerRepo).getArtifactFile(key);
+		return null;
+	}
+
+	public File getArtifactFile(IArtifactDescriptor descriptor) {
+		if (innerRepo instanceof IFileArtifactRepository)
+			return ((IFileArtifactRepository) innerRepo).getArtifactFile(descriptor);
+		return null;
+	}
+}
diff --git a/bundles/org.eclipse.equinox.p2.directorywatcher/src/org/eclipse/equinox/internal/provisional/p2/directorywatcher/DirectoryChangeListener.java b/bundles/org.eclipse.equinox.p2.directorywatcher/src/org/eclipse/equinox/internal/provisional/p2/directorywatcher/DirectoryChangeListener.java
index 295d1b6..515e19d 100644
--- a/bundles/org.eclipse.equinox.p2.directorywatcher/src/org/eclipse/equinox/internal/provisional/p2/directorywatcher/DirectoryChangeListener.java
+++ b/bundles/org.eclipse.equinox.p2.directorywatcher/src/org/eclipse/equinox/internal/provisional/p2/directorywatcher/DirectoryChangeListener.java
@@ -42,6 +42,7 @@
 		return false;
 	}
 
+	//TODO this method name needs to be more descriptive.  getLastModified?
 	public Long getSeenFile(File file) {
 		return null;
 	}
diff --git a/bundles/org.eclipse.equinox.p2.directorywatcher/src/org/eclipse/equinox/internal/provisional/p2/directorywatcher/EntryAdvice.java b/bundles/org.eclipse.equinox.p2.directorywatcher/src/org/eclipse/equinox/internal/provisional/p2/directorywatcher/EntryAdvice.java
new file mode 100644
index 0000000..68bdf31
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.directorywatcher/src/org/eclipse/equinox/internal/provisional/p2/directorywatcher/EntryAdvice.java
@@ -0,0 +1,61 @@
+/*******************************************************************************
+ * Copyright (c) 2008 Code 9 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: 
+ *   Code 9 - initial API and implementation
+ ******************************************************************************/
+ package org.eclipse.equinox.internal.provisional.p2.directorywatcher;
+
+import java.io.File;
+import java.net.URL;
+import java.util.Map;
+import java.util.Properties;
+import org.eclipse.equinox.p2.publisher.eclipse.*;
+import org.osgi.framework.Version;
+
+/**
+ * Entry advice captures the name, location, modified time, shape etc of something
+ * discovered by the repository listener.  It is a simplified structure intended to represent
+ * only one entry at a time and that entry is the the only entry being published.  
+ */
+public class EntryAdvice implements IFeatureAdvice, IBundleAdvice {
+	private Properties metadataProps = new Properties();
+	private Properties artifactProps = new Properties();
+
+	public Properties getProperties(Feature feature, File location) {
+		return metadataProps;
+	}
+
+	public boolean isApplicable(String configSpec, boolean includeDefault, String id, Version version) {
+		return true;
+	}
+
+	public Properties getIUProperties(File location) {
+		return metadataProps;
+	}
+
+	public Properties getArtifactProperties(File location) {
+		return artifactProps;
+	}
+
+	void setProperties(File location, long timestamp, URL reference) {
+		if (reference == null)
+			artifactProps.remove(RepositoryListener.ARTIFACT_REFERENCE);
+		else
+			artifactProps.setProperty(RepositoryListener.ARTIFACT_REFERENCE, reference.toExternalForm());
+		if (location.isDirectory())
+			artifactProps.setProperty(RepositoryListener.ARTIFACT_FOLDER, Boolean.TRUE.toString());
+		else
+			artifactProps.remove(RepositoryListener.ARTIFACT_FOLDER);
+		artifactProps.setProperty(RepositoryListener.FILE_NAME, location.getAbsolutePath());
+		metadataProps.setProperty(RepositoryListener.FILE_NAME, location.getAbsolutePath());
+		metadataProps.setProperty(RepositoryListener.FILE_LAST_MODIFIED, Long.toString(timestamp));
+	}
+
+	public Map getInstructions(File location) {
+		return null;
+	}
+}
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 04132b7..af5484a 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
@@ -5,7 +5,8 @@
  * and is available at http://www.eclipse.org/legal/epl-v10.html
  * 
  * Contributors: 
- * IBM Corporation - initial implementation and ideas 
+ *   IBM Corporation - initial implementation and ideas 
+ *   Code 9 - ongoing development
  ******************************************************************************/
 package org.eclipse.equinox.internal.provisional.p2.directorywatcher;
 
@@ -13,42 +14,38 @@
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.*;
-import org.eclipse.core.runtime.IStatus;
-import org.eclipse.core.runtime.Status;
 import org.eclipse.equinox.internal.p2.core.helpers.LogHelper;
-import org.eclipse.equinox.internal.p2.core.helpers.ServiceHelper;
-import org.eclipse.equinox.internal.p2.metadata.generator.features.FeatureParser;
 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.core.eventbus.IProvisioningEventBus;
 import org.eclipse.equinox.internal.provisional.p2.core.repository.IRepository;
-import org.eclipse.equinox.internal.provisional.p2.core.repository.RepositoryEvent;
 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.eclipse.equinox.p2.publisher.*;
+import org.eclipse.equinox.p2.publisher.eclipse.BundlesAction;
+import org.eclipse.equinox.p2.publisher.eclipse.FeaturesAction;
+import org.eclipse.osgi.service.resolver.BundleDescription;
 import org.eclipse.osgi.util.NLS;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.ServiceReference;
 
 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$
+	public static final String ARTIFACT_FOLDER = "artifact.folder"; //$NON-NLS-1$
+	public static final String ARTIFACT_REFERENCE = "artifact.reference"; //$NON-NLS-1$
+	public static final String FILE_LAST_MODIFIED = "file.lastModified"; //$NON-NLS-1$
+	public 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 Collection polledSeenFiles = new HashSet();
-	private Collection polledIUsToAdd = new ArrayList();
-	private Collection polledArtifactsToAdd = new ArrayList();
+	private final CachingArtifactRepository artifactRepository;
+	// at any point in time currentFiles is the list of files/dirs that the watcher has seen and 
+	// believes to be on disk.
+	private Map currentFiles;
+	private Collection filesToRemove;
+	private EntryAdvice advice = new EntryAdvice();
+	private PublisherInfo info;
+	private IPublisherResult iusToAdd;
+	private IPublisherResult iusToChange;
 
 	/**
 	 * Create a repository listener that watches the specified folder and generates repositories
@@ -59,351 +56,215 @@
 	 * bundle's data location should be used.
 	 * @param hidden <code>true</code> if the repository should be hidden, <code>false</code> if not.
 	 */
-	public RepositoryListener(BundleContext context, String repositoryName, File repositoryFolder, boolean hidden) {
-		File stateDir;
-		if (repositoryFolder == null) {
-			String stateDirName = "listener_" + repositoryName.hashCode(); //$NON-NLS-1$
-			stateDir = context.getDataFile(stateDirName);
-			stateDir.mkdirs();
-		} else {
-			stateDir = repositoryFolder;
-		}
-
-		URL stateDirURL;
-		try {
-			stateDirURL = stateDir.toURL();
-		} catch (MalformedURLException e) {
-			throw new IllegalStateException(e.getMessage());
-		}
-
-		metadataRepository = initializeMetadataRepository(context, repositoryName, stateDirURL, hidden);
-		artifactRepository = initializeArtifactRepository(context, repositoryName, stateDirURL, hidden);
-		bundleDescriptionFactory = initializeBundleDescriptionFactory(context);
-		synchronizeCurrentFiles();
+	public RepositoryListener(String repositoryName, boolean hidden) {
+		URL location = Activator.getDefaultRepositoryLocation(this, repositoryName);
+		metadataRepository = initiailzeMetadataRepository(repositoryName, location, hidden);
+		artifactRepository = initializeArtifactRepository(repositoryName, location, hidden);
+		initializePublisher();
 	}
 
-	public RepositoryListener(BundleContext context, IMetadataRepository metadataRepository, IArtifactRepository artifactRepository) {
-		this.artifactRepository = artifactRepository;
+	public RepositoryListener(IMetadataRepository metadataRepository, IArtifactRepository artifactRepository) {
+		this.artifactRepository = new CachingArtifactRepository(artifactRepository);
 		this.metadataRepository = metadataRepository;
-		bundleDescriptionFactory = initializeBundleDescriptionFactory(context);
-		synchronizeCurrentFiles();
+		initializePublisher();
 	}
 
-	/**
-	 * Broadcast events for any discovery sites associated with the feature
-	 * so the repository managers add them to their list of known repositories.
-	 */
-	private void publishSites(Feature feature) {
-		IProvisioningEventBus bus = (IProvisioningEventBus) ServiceHelper.getService(Activator.getContext(), IProvisioningEventBus.SERVICE_NAME);
-		if (bus == null)
-			return;
-		URLEntry[] discoverySites = feature.getDiscoverySites();
-		for (int i = 0; i < discoverySites.length; i++)
-			publishSite(feature, bus, discoverySites[i].getURL(), false);
-		String updateSite = feature.getUpdateSiteURL();
-		if (updateSite != null)
-			publishSite(feature, bus, updateSite, true);
+	private void initializePublisher() {
+		info = new PublisherInfo();
+		info.setArtifactRepository(artifactRepository);
+		info.setMetadataRepository(metadataRepository);
+		info.addAdvice(advice);
+		info.setArtifactOptions(IPublisherInfo.A_INDEX);
 	}
 
-	/**
-	 * Broadcast a discovery event for the given repository location.
-	 */
-	private void publishSite(Feature feature, IProvisioningEventBus bus, String locationString, boolean isEnabled) {
-		try {
-			URL location = new URL(locationString);
-			bus.publishEvent(new RepositoryEvent(location, IRepository.TYPE_METADATA, RepositoryEvent.DISCOVERED, isEnabled));
-			bus.publishEvent(new RepositoryEvent(location, IRepository.TYPE_ARTIFACT, RepositoryEvent.DISCOVERED, isEnabled));
-		} catch (MalformedURLException e) {
-			LogHelper.log(new Status(IStatus.WARNING, Activator.ID, "Feature references invalid site: " + feature.getId(), e)); //$NON-NLS-1$
-		}
-	}
-
-	private BundleDescriptionFactory initializeBundleDescriptionFactory(BundleContext context) {
-		ServiceReference reference = context.getServiceReference(PlatformAdmin.class.getName());
-		if (reference == null)
-			throw new IllegalStateException(Messages.platformadmin_not_registered);
-		PlatformAdmin platformAdmin = (PlatformAdmin) context.getService(reference);
-		if (platformAdmin == null)
-			throw new IllegalStateException(Messages.platformadmin_not_registered);
-
-		try {
-			StateObjectFactory stateObjectFactory = platformAdmin.getFactory();
-			return new BundleDescriptionFactory(stateObjectFactory, null);
-		} finally {
-			context.ungetService(reference);
-		}
-	}
-
-	private IArtifactRepository initializeArtifactRepository(BundleContext context, String repositoryName, URL stateDirURL, boolean hidden) {
-		ServiceReference reference = context.getServiceReference(IArtifactRepositoryManager.class.getName());
-		IArtifactRepositoryManager manager = null;
-		if (reference != null)
-			manager = (IArtifactRepositoryManager) context.getService(reference);
+	protected CachingArtifactRepository initializeArtifactRepository(String repositoryName, URL repositoryLocation, boolean hidden) {
+		IArtifactRepositoryManager manager = Activator.getArtifactRepositoryManager();
 		if (manager == null)
 			throw new IllegalStateException(Messages.artifact_repo_manager_not_registered);
 
 		try {
-			try {
-				return manager.loadRepository(stateDirURL, null);
-			} catch (ProvisionException e) {
-				//fall through and create a new repository
+			IArtifactRepository result = manager.loadRepository(repositoryLocation, null);
+			return result == null ? null : new CachingArtifactRepository(result);
+		} catch (ProvisionException e) {
+			//fall through and create a new repository
+		}
+		try {
+			String name = repositoryName;
+			Map properties = new HashMap(1);
+			if (hidden) {
+				properties.put(IRepository.PROP_SYSTEM, Boolean.TRUE.toString());
+				name = "artifact listener " + repositoryName; //$NON-NLS-1$
 			}
-			try {
-				String name = repositoryName;
-				Map properties = new HashMap(1);
-				if (hidden) {
-					properties.put(IRepository.PROP_SYSTEM, Boolean.TRUE.toString());
-					name = "artifact listener " + repositoryName; //$NON-NLS-1$
-				}
-				return manager.createRepository(stateDirURL, name, IArtifactRepositoryManager.TYPE_SIMPLE_REPOSITORY, properties);
-			} catch (ProvisionException e) {
-				LogHelper.log(e);
-				throw new IllegalStateException(NLS.bind(Messages.failed_create_artifact_repo, stateDirURL));
-			}
-		} finally {
-			context.ungetService(reference);
+			IArtifactRepository result = manager.createRepository(repositoryLocation, name, IArtifactRepositoryManager.TYPE_SIMPLE_REPOSITORY, properties);
+			return result == null ? null : new CachingArtifactRepository(result);
+		} catch (ProvisionException e) {
+			LogHelper.log(e);
+			throw new IllegalStateException(NLS.bind(Messages.failed_create_artifact_repo, repositoryLocation));
 		}
 	}
 
-	private IMetadataRepository initializeMetadataRepository(BundleContext context, String repositoryName, URL stateDirURL, boolean hidden) {
-		ServiceReference reference = context.getServiceReference(IMetadataRepositoryManager.class.getName());
-		IMetadataRepositoryManager manager = null;
-		if (reference != null)
-			manager = (IMetadataRepositoryManager) context.getService(reference);
+	protected IMetadataRepository initiailzeMetadataRepository(String repositoryName, URL repositoryLocation, boolean hidden) {
+		IMetadataRepositoryManager manager = Activator.getMetadataRepositoryManager();
 		if (manager == null)
 			throw new IllegalStateException(Messages.metadata_repo_manager_not_registered);
 
 		try {
-			try {
-				return manager.loadRepository(stateDirURL, null);
-			} catch (ProvisionException e) {
-				//fall through and create new repository
-			}
+			return manager.loadRepository(repositoryLocation, null);
+		} catch (ProvisionException e) {
+			//fall through and create new repository
+		}
+		try {
 			String name = repositoryName;
 			Map properties = new HashMap(1);
 			if (hidden) {
 				properties.put(IRepository.PROP_SYSTEM, Boolean.TRUE.toString());
 				name = "metadata listener " + repositoryName; //$NON-NLS-1$
 			}
-			return manager.createRepository(stateDirURL, name, IMetadataRepositoryManager.TYPE_SIMPLE_REPOSITORY, properties);
+			return manager.createRepository(repositoryLocation, name, IMetadataRepositoryManager.TYPE_SIMPLE_REPOSITORY, properties);
 		} catch (ProvisionException e) {
 			LogHelper.log(e);
-			throw new IllegalStateException(NLS.bind(Messages.failed_create_metadata_repo, stateDirURL));
-		} finally {
-			context.ungetService(reference);
+			throw new IllegalStateException(NLS.bind(Messages.failed_create_metadata_repo, repositoryLocation));
 		}
 	}
 
-	/* (non-Javadoc)
-	 * @see org.eclipse.equinox.internal.provisional.p2.directorywatcher.IDirectoryChangeListener#added(java.io.File)
-	 */
 	public boolean added(File file) {
-		return process(file);
+		return process(file, true);
 	}
 
-	/* (non-Javadoc)
-	 * @see org.eclipse.equinox.internal.provisional.p2.directorywatcher.IDirectoryChangeListener#changed(java.io.File)
-	 */
 	public boolean changed(File file) {
-		// this sequence will trigger removal and then addition during stopPoll
-		polledSeenFiles.remove(file);
-		return process(file);
+		return process(file, false);
 	}
 
 	public boolean removed(File file) {
-		// this file will get removed in stopPoll
-		return currentFiles.containsKey(file);
+		filesToRemove.add(file);
+		return true;
 	}
 
-	private boolean process(File file) {
+	private boolean process(File file, boolean isAddition) {
 		boolean isDirectory = file.isDirectory();
 		// is it a feature ?
 		if (isDirectory && file.getParentFile() != null && file.getParentFile().getName().equals("features") && new File(file, "feature.xml").exists()) //$NON-NLS-1$ //$NON-NLS-2$)
-			return processFeature(file);
-
-		// is it a bundle ?
+			return processFeature(file, isAddition);
+		// could it be a bundle ?
 		if (isDirectory || file.getName().endsWith(".jar")) //$NON-NLS-1$
-			return processBundle(file, isDirectory);
-
+			return processBundle(file, isDirectory, isAddition);
 		return false;
 	}
 
-	/* (non-Javadoc)
-	 * @see org.eclipse.equinox.internal.provisional.p2.directorywatcher.IDirectoryChangeListener#removed(java.io.File)
-	 */
-
-	private boolean processBundle(File file, boolean isDirectory) {
-		BundleDescription bundleDescription = bundleDescriptionFactory.getBundleDescription(file);
+	private boolean processBundle(File file, boolean isDirectory, boolean isAddition) {
+		BundleDescription bundleDescription = BundlesAction.createBundleDescription(file);
 		if (bundleDescription == null)
 			return false;
-
-		String fileName = file.getAbsolutePath();
-		String lastModified = Long.toString(file.lastModified());
-
-		// Add Bundle IU
-		Properties props = new Properties();
-		props.setProperty(FILE_NAME, fileName);
-		props.setProperty(FILE_LAST_MODIFIED, lastModified);
-
-		IArtifactKey key = MetadataGeneratorHelper.createBundleArtifactKey(bundleDescription.getSymbolicName(), bundleDescription.getVersion().toString());
-		IInstallableUnit[] ius = MetadataGeneratorHelper.createEclipseIU(bundleDescription, (Map) bundleDescription.getUserObject(), isDirectory, key, props);
-
-		// see bug 222370
+		try {
+			advice.setProperties(file, file.lastModified(), file.toURL());
+		} catch (MalformedURLException e) {
+			// should never happen
+		}
+		return publish(new BundlesAction(new BundleDescription[] {bundleDescription}), isAddition);
+		// TODO see bug 222370
 		// we only want to return the bundle IU so must exclude all fragment IUs
-		IInstallableUnit bundleIU = null;
-		for (int i = 0; i < ius.length; i++) {
-			if (!ius[i].isFragment()) {
-				bundleIU = ius[i];
-				break;
-			}
-		}
-
-		if (bundleIU == null) {
-			if (ius.length == 0)
-				return false;
-			throw new IllegalStateException(Messages.multiple_bundle_ius);
-		}
-		polledIUsToAdd.add(bundleIU);
-
-		// Add Bundle Artifact
-		ArtifactDescriptor descriptor = new ArtifactDescriptor(MetadataGeneratorHelper.createArtifactDescriptor(key, file, true, false));
-		try {
-			descriptor.setRepositoryProperty(ARTIFACT_REFERENCE, file.toURL().toExternalForm());
-		} catch (MalformedURLException e) {
-			// unexpected
-			e.printStackTrace();
-			return false;
-		}
-		if (isDirectory)
-			descriptor.setRepositoryProperty(ARTIFACT_FOLDER, Boolean.TRUE.toString());
-		descriptor.setRepositoryProperty(FILE_NAME, fileName);
-		descriptor.setRepositoryProperty(FILE_LAST_MODIFIED, lastModified);
-
-		polledArtifactsToAdd.add(descriptor);
-		return true;
+		// not sure if this is still relevant but we should investigate.
 	}
 
-	private boolean processFeature(File file) {
-		FeatureParser parser = new FeatureParser();
-		Feature feature = parser.parse(file);
-		if (feature == null)
-			return false;
-
-		publishSites(feature);
-
-		String fileName = file.getAbsolutePath();
-		String lastModified = Long.toString(file.lastModified());
-
-		// Add Feature IUs
-		Properties props = new Properties();
-		props.setProperty(FILE_NAME, fileName);
-		props.setProperty(FILE_LAST_MODIFIED, lastModified);
-
-		IInstallableUnit featureIU = MetadataGeneratorHelper.createFeatureJarIU(feature, true, props);
-		IInstallableUnit groupIU = MetadataGeneratorHelper.createGroupIU(feature, featureIU, props);
-
-		polledIUsToAdd.add(featureIU);
-		polledIUsToAdd.add(groupIU);
-
-		// Add Feature Artifact
-		IArtifactKey featureKey = MetadataGeneratorHelper.createFeatureArtifactKey(feature.getId(), feature.getVersion());
-		ArtifactDescriptor descriptor = new ArtifactDescriptor(featureKey);
-
+	private boolean processFeature(File file, boolean isAddition) {
 		try {
-			descriptor.setRepositoryProperty(ARTIFACT_REFERENCE, file.toURL().toExternalForm());
+			advice.setProperties(file, file.lastModified(), file.toURL());
 		} catch (MalformedURLException e) {
-			// unexpected
-			e.printStackTrace();
-			return false;
+			// should never happen
 		}
-		descriptor.setRepositoryProperty(ARTIFACT_FOLDER, Boolean.TRUE.toString());
-		descriptor.setRepositoryProperty(FILE_NAME, fileName);
-		descriptor.setRepositoryProperty(FILE_LAST_MODIFIED, lastModified);
-
-		polledArtifactsToAdd.add(descriptor);
-		return true;
+		return publish(new FeaturesAction(new File[] {file}), isAddition);
 	}
 
-	/* (non-Javadoc)
-	 * @see org.eclipse.equinox.internal.provisional.p2.directorywatcher.DirectoryChangeListener#isInterested(java.io.File)
-	 */
+	private boolean publish(IPublisherAction action, boolean isAddition) {
+		IPublisherResult result = isAddition ? iusToAdd : iusToChange;
+		return action.perform(info, result).isOK();
+	}
+
 	public boolean isInterested(File file) {
 		return true;
 	}
 
-	/* (non-Javadoc)
-	 * @see org.eclipse.equinox.internal.provisional.p2.directorywatcher.IDirectoryChangeListener#getSeenFile(java.io.File)
-	 */
 	public Long getSeenFile(File file) {
-		Long lastSeen = (Long) currentFiles.get(file);
-		if (lastSeen != null)
-			polledSeenFiles.add(file);
-		return lastSeen;
+		return (Long) currentFiles.get(file);
 	}
 
-	/* (non-Javadoc)
-	 * @see org.eclipse.equinox.internal.provisional.p2.directorywatcher.IDirectoryChangeListener#startPoll()
-	 */
 	public void startPoll() {
-		// do nothing
-	}
-
-	/* (non-Javadoc)
-	 * @see org.eclipse.equinox.internal.provisional.p2.directorywatcher.IDirectoryChangeListener#stopPoll()
-	 */
-	public void stopPoll() {
-		final Set removedFiles = new HashSet(currentFiles.keySet());
-		removedFiles.removeAll(polledSeenFiles);
-		polledSeenFiles.clear();
-
-		if (removedFiles.isEmpty() && polledIUsToAdd.isEmpty() && polledArtifactsToAdd.isEmpty())
-			return;
-
-		if (metadataRepository != null)
-			synchronizeMetadataRepository(removedFiles);
-
-		if (artifactRepository != null)
-			synchronizeArtifactRepository(removedFiles);
-
+		filesToRemove = new HashSet();
+		iusToAdd = new PublisherResult();
+		iusToChange = new PublisherResult();
+		// TODO investigate why we do this here?  Suspect it is to clean up the currentFiles collection
+		// for removed entries.  This may be a performance opportunity
+		currentFiles = new HashMap();
 		synchronizeCurrentFiles();
-
-		polledIUsToAdd.clear();
-		polledArtifactsToAdd.clear();
 	}
 
-	private void synchronizeMetadataRepository(final Set removedFiles) {
-		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));
-				return removedFiles.contains(iuFile);
-			}
-		};
-		metadataRepository.removeInstallableUnits(removeQuery, null);
-
-		if (!polledIUsToAdd.isEmpty())
-			metadataRepository.addInstallableUnits((IInstallableUnit[]) polledIUsToAdd.toArray(new IInstallableUnit[polledIUsToAdd.size()]));
+	public void stopPoll() {
+		synchronizeMetadataRepository(filesToRemove);
+		synchronizeArtifactRepository(filesToRemove);
+		filesToRemove.clear();
+		iusToAdd = null;
+		iusToChange = null;
+		currentFiles = null;
 	}
 
-	private void synchronizeArtifactRepository(final Set removedFiles) {
-		final List keys = new ArrayList(Arrays.asList(artifactRepository.getArtifactKeys()));
-		for (Iterator it = keys.iterator(); it.hasNext();) {
-			IArtifactKey key = (IArtifactKey) it.next();
-			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));
-				if (removedFiles.contains(artifactFile))
-					artifactRepository.removeDescriptor(descriptor);
+	/**
+	 * Flush all the pending changes to the metadata repository.
+	 */
+	private void synchronizeMetadataRepository(final Collection removedFiles) {
+		if (metadataRepository == null)
+			return;
+		final Collection changes = iusToChange.getIUs(null, null);
+		// first remove any IUs that have changed or that are associated with removed files
+		if (!removedFiles.isEmpty() || !changes.isEmpty()) {
+			// create a query that will identify all ius related to removed files or ius that have changed
+			Query removeQuery = new Query() {
+				public boolean isMatch(Object candidate) {
+					if (!(candidate instanceof IInstallableUnit))
+						return false;
+					IInstallableUnit iu = (IInstallableUnit) candidate;
+					if (changes.contains(iu))
+						return true;
+					File iuFile = new File(iu.getProperty(FILE_NAME));
+					return removedFiles.contains(iuFile);
+				}
+			};
+			metadataRepository.removeInstallableUnits(removeQuery, null);
+		}
+		// Then add all the new IUs as well as the new copies of the ones that have changed
+		Collection additions = iusToAdd.getIUs(null, null);
+		additions.addAll(changes);
+		if (!additions.isEmpty())
+			metadataRepository.addInstallableUnits((IInstallableUnit[]) additions.toArray(new IInstallableUnit[additions.size()]));
+	}
+
+	/**
+	 * Here the artifacts have all been added to the artifact repo.  Remove the
+	 * descriptors related to any file that has been removed and flush the repo
+	 * to ensure that all the additions and removals have been completed.
+	 */
+	private void synchronizeArtifactRepository(final Collection removedFiles) {
+		if (artifactRepository == null)
+			return;
+		if (!removedFiles.isEmpty()) {
+			final List keys = new ArrayList(Arrays.asList(artifactRepository.getArtifactKeys()));
+			for (Iterator it = keys.iterator(); it.hasNext();) {
+				IArtifactKey key = (IArtifactKey) it.next();
+				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));
+					if (removedFiles.contains(artifactFile))
+						artifactRepository.removeDescriptor(descriptor);
+				}
 			}
 		}
-
-		if (!polledArtifactsToAdd.isEmpty())
-			artifactRepository.addDescriptors((IArtifactDescriptor[]) polledArtifactsToAdd.toArray(new IArtifactDescriptor[polledArtifactsToAdd.size()]));
+		artifactRepository.save();
 	}
 
+	/**
+	 * Prime the list of current files that the listener knows about.  This traverses the 
+	 * repos and looks for the related filename and modified timestamp information.
+	 */
 	private void synchronizeCurrentFiles() {
 		currentFiles.clear();
 		if (metadataRepository != null) {
@@ -415,20 +276,22 @@
 				currentFiles.put(iuFile, iuLastModified);
 			}
 		}
-
-		if (artifactRepository != null) {
-			final List keys = new ArrayList(Arrays.asList(artifactRepository.getArtifactKeys()));
-			for (Iterator it = keys.iterator(); it.hasNext();) {
-				IArtifactKey key = (IArtifactKey) it.next();
-				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));
-					currentFiles.put(artifactFile, artifactLastModified);
-				}
-			}
-		}
+		//
+		//		// TODO  should we be doing this for the artifact repo?  the metadata repo should
+		//		// be the main driver here.
+		//		if (artifactRepository != null) {
+		//			final List keys = new ArrayList(Arrays.asList(artifactRepository.getArtifactKeys()));
+		//			for (Iterator it = keys.iterator(); it.hasNext();) {
+		//				IArtifactKey key = (IArtifactKey) it.next();
+		//				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));
+		//					currentFiles.put(artifactFile, artifactLastModified);
+		//				}
+		//			}
+		//		}
 	}
 
 	public IMetadataRepository getMetadataRepository() {
diff --git a/bundles/org.eclipse.equinox.p2.extensionlocation/META-INF/MANIFEST.MF b/bundles/org.eclipse.equinox.p2.extensionlocation/META-INF/MANIFEST.MF
index 3342328..ca104ad 100644
--- a/bundles/org.eclipse.equinox.p2.extensionlocation/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.equinox.p2.extensionlocation/META-INF/MANIFEST.MF
@@ -10,7 +10,7 @@
  CDC-1.1/Foundation-1.1
 Export-Package: org.eclipse.equinox.internal.p2.extensionlocation;x-friends:="org.eclipse.equinox.p2.reconciler.dropins"
 Import-Package: org.eclipse.equinox.internal.p2.core.helpers,
- org.eclipse.equinox.internal.p2.metadata.generator.features,
+ org.eclipse.equinox.internal.p2.publisher.eclipse,
  org.eclipse.equinox.internal.p2.touchpoint.eclipse,
  org.eclipse.equinox.internal.p2.update,
  org.eclipse.equinox.internal.provisional.p2.artifact.repository,
@@ -19,12 +19,12 @@
  org.eclipse.equinox.internal.provisional.p2.directorywatcher,
  org.eclipse.equinox.internal.provisional.p2.engine,
  org.eclipse.equinox.internal.provisional.p2.metadata,
- org.eclipse.equinox.internal.provisional.p2.metadata.generator,
  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.equinox.p2.publisher.eclipse,
  org.eclipse.osgi.service.datalocation;version="1.1.0",
  org.eclipse.osgi.service.resolver;version="1.2.0",
  org.eclipse.osgi.util,
diff --git a/bundles/org.eclipse.equinox.p2.extensionlocation/src/org/eclipse/equinox/internal/p2/extensionlocation/ExtensionLocationArtifactRepository.java b/bundles/org.eclipse.equinox.p2.extensionlocation/src/org/eclipse/equinox/internal/p2/extensionlocation/ExtensionLocationArtifactRepository.java
index aee3fcd..81117f4 100644
--- a/bundles/org.eclipse.equinox.p2.extensionlocation/src/org/eclipse/equinox/internal/p2/extensionlocation/ExtensionLocationArtifactRepository.java
+++ b/bundles/org.eclipse.equinox.p2.extensionlocation/src/org/eclipse/equinox/internal/p2/extensionlocation/ExtensionLocationArtifactRepository.java
@@ -7,6 +7,7 @@
  * 
  * Contributors:
  *     IBM Corporation - initial API and implementation
+ *     Code 9 - ongoing development
  *******************************************************************************/
 package org.eclipse.equinox.internal.p2.extensionlocation;
 
@@ -18,7 +19,6 @@
 import org.eclipse.core.runtime.*;
 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.*;
 import org.eclipse.equinox.internal.provisional.p2.metadata.IArtifactKey;
 import org.eclipse.equinox.internal.provisional.spi.p2.core.repository.AbstractRepository;
 import org.eclipse.osgi.util.NLS;
@@ -28,9 +28,10 @@
 
 	public static final String TYPE = "org.eclipse.equinox.p2.extensionlocation.artifactRepository"; //$NON-NLS-1$
 	public static final Integer VERSION = new Integer(1);
-	private final IFileArtifactRepository artifactRepository;
-	private boolean initialized = false;
+
+	final IFileArtifactRepository artifactRepository;
 	private File base;
+	private Object state = SiteListener.UNINITIALIZED;
 
 	/*
 	 * Return the location of a local repository based on
@@ -60,15 +61,14 @@
 	}
 
 	public synchronized void ensureInitialized() {
-		if (initialized)
+		if (state == SiteListener.INITIALIZED || state == SiteListener.INITIALIZING)
 			return;
-		File plugins = new File(base, PLUGINS);
-		File features = new File(base, FEATURES);
-		DirectoryWatcher watcher = new DirectoryWatcher(new File[] {plugins, features});
-		DirectoryChangeListener listener = new RepositoryListener(Activator.getContext(), null, artifactRepository);
-		watcher.addListener(listener);
-		watcher.poll();
-		initialized = true;
+		// if the repo has not been synchronized for us already, synchronize it.
+		SiteListener.synchronizeRepositories(null, this, base);
+	}
+
+	void state(Object value) {
+		state = value;
 	}
 
 	public static void validate(URL location, IProgressMonitor monitor) throws ProvisionException {
diff --git a/bundles/org.eclipse.equinox.p2.extensionlocation/src/org/eclipse/equinox/internal/p2/extensionlocation/ExtensionLocationMetadataRepository.java b/bundles/org.eclipse.equinox.p2.extensionlocation/src/org/eclipse/equinox/internal/p2/extensionlocation/ExtensionLocationMetadataRepository.java
index 13c6b48..5cc250e 100644
--- a/bundles/org.eclipse.equinox.p2.extensionlocation/src/org/eclipse/equinox/internal/p2/extensionlocation/ExtensionLocationMetadataRepository.java
+++ b/bundles/org.eclipse.equinox.p2.extensionlocation/src/org/eclipse/equinox/internal/p2/extensionlocation/ExtensionLocationMetadataRepository.java
@@ -7,6 +7,7 @@
  * 
  * Contributors:
  *     IBM Corporation - initial API and implementation
+ *     Code 9 - ongoing development
  *******************************************************************************/
 package org.eclipse.equinox.internal.p2.extensionlocation;
 
@@ -16,7 +17,6 @@
 import java.util.Map;
 import org.eclipse.core.runtime.*;
 import org.eclipse.equinox.internal.provisional.p2.core.ProvisionException;
-import org.eclipse.equinox.internal.provisional.p2.directorywatcher.*;
 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;
@@ -29,9 +29,10 @@
 
 	public static final String TYPE = "org.eclipse.equinox.p2.extensionlocation.metadataRepository"; //$NON-NLS-1$
 	public static final Integer VERSION = new Integer(1);
-	private final IMetadataRepository metadataRepository;
-	private boolean initialized = false;
+
+	final IMetadataRepository metadataRepository;
 	private File base;
+	private Object state = SiteListener.UNINITIALIZED;
 
 	/*
 	 * Return the URL for this repo's nested local repository.
@@ -60,17 +61,14 @@
 	}
 
 	public synchronized void ensureInitialized() {
-		if (initialized)
+		if (state == SiteListener.INITIALIZED || state == SiteListener.INITIALIZING)
 			return;
-		File plugins = new File(base, PLUGINS);
-		File features = new File(base, FEATURES);
-		DirectoryWatcher watcher = new DirectoryWatcher(new File[] {plugins, features});
-		DirectoryChangeListener listener = new RepositoryListener(Activator.getContext(), metadataRepository, null);
-		if (getProperties().get(SiteListener.SITE_POLICY) != null)
-			listener = new SiteListener(getProperties(), location.toExternalForm(), new BundlePoolFilteredListener(listener));
-		watcher.addListener(listener);
-		watcher.poll();
-		initialized = true;
+		// if the repo has not been synchronized for us already, synchronize it.
+		SiteListener.synchronizeRepositories(this, null, base);
+	}
+
+	void state(Object value) {
+		state = value;
 	}
 
 	/* (non-Javadoc)
diff --git a/bundles/org.eclipse.equinox.p2.extensionlocation/src/org/eclipse/equinox/internal/p2/extensionlocation/SiteListener.java b/bundles/org.eclipse.equinox.p2.extensionlocation/src/org/eclipse/equinox/internal/p2/extensionlocation/SiteListener.java
index 0f1fdab..41c85d9 100644
--- a/bundles/org.eclipse.equinox.p2.extensionlocation/src/org/eclipse/equinox/internal/p2/extensionlocation/SiteListener.java
+++ b/bundles/org.eclipse.equinox.p2.extensionlocation/src/org/eclipse/equinox/internal/p2/extensionlocation/SiteListener.java
@@ -7,6 +7,7 @@
  * 
  * Contributors:
  *     IBM Corporation - initial API and implementation
+ *     Code 9 - ongoing development
  *******************************************************************************/
 package org.eclipse.equinox.internal.p2.extensionlocation;
 
@@ -18,24 +19,27 @@
 import org.eclipse.core.runtime.Status;
 import org.eclipse.equinox.internal.p2.core.helpers.LogHelper;
 import org.eclipse.equinox.internal.p2.core.helpers.URLUtil;
-import org.eclipse.equinox.internal.p2.metadata.generator.features.FeatureParser;
+import org.eclipse.equinox.internal.p2.publisher.eclipse.FeatureParser;
 import org.eclipse.equinox.internal.p2.update.Site;
+import org.eclipse.equinox.internal.provisional.p2.core.ProvisionException;
 import org.eclipse.equinox.internal.provisional.p2.directorywatcher.*;
-import org.eclipse.equinox.internal.provisional.p2.directorywatcher.Messages;
-import org.eclipse.equinox.internal.provisional.p2.metadata.generator.*;
-import org.eclipse.osgi.service.resolver.*;
-import org.osgi.framework.ServiceReference;
+import org.eclipse.equinox.p2.publisher.eclipse.*;
+import org.eclipse.osgi.service.resolver.BundleDescription;
 
 /**
  * @since 1.0
  */
-public class SiteListener extends RepositoryListener {
+public class SiteListener extends DirectoryChangeListener {
 
 	public static final String SITE_POLICY = "org.eclipse.update.site.policy"; //$NON-NLS-1$
 	public static final String SITE_LIST = "org.eclipse.update.site.list"; //$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 FEATURE_MANIFEST = "feature.xml"; //$NON-NLS-1$
+	public static final Object UNINITIALIZED = "uninitialized"; //$NON-NLS-1$
+	public static final Object INITIALIZING = "initializing"; //$NON-NLS-1$
+	public static final Object INITIALIZED = "initialized"; //$NON-NLS-1$
+
 	private String policy;
 	private String[] list;
 	private String url;
@@ -55,11 +59,53 @@
 		return false;
 	}
 
+	/**
+	 * Given one repo and a base location, ensure cause the other repo to be loaded and then 
+	 * poll the base location once updating the repositories accordingly.  This method is used to 
+	 * ensure that both the metadata and artifact repos corresponding to one location are 
+	 * synchronized in one go.  It is expected that both repos have been previously created
+	 * so simply loading them here will work and that all their properties etc have been configured
+	 * previously.
+	 * @param metadataRepository
+	 * @param artifactRepository
+	 * @param base
+	 */
+	public static synchronized void synchronizeRepositories(ExtensionLocationMetadataRepository metadataRepository, ExtensionLocationArtifactRepository artifactRepository, File base) {
+		try {
+			if (metadataRepository == null) {
+				ExtensionLocationMetadataRepositoryFactory factory = new ExtensionLocationMetadataRepositoryFactory();
+				metadataRepository = (ExtensionLocationMetadataRepository) factory.load(artifactRepository.getLocation(), null);
+			} else if (artifactRepository == null) {
+				ExtensionLocationArtifactRepositoryFactory factory = new ExtensionLocationArtifactRepositoryFactory();
+				artifactRepository = (ExtensionLocationArtifactRepository) factory.load(metadataRepository.getLocation(), null);
+			}
+		} catch (ProvisionException e) {
+			// TODO need proper error handling here.  What should we do if there is a failure
+			// when loading "the other" repo?
+			e.printStackTrace();
+			return;
+		}
+
+		artifactRepository.state(INITIALIZING);
+		metadataRepository.state(INITIALIZING);
+		File plugins = new File(base, PLUGINS);
+		File features = new File(base, FEATURES);
+		DirectoryWatcher watcher = new DirectoryWatcher(new File[] {plugins, features});
+		//  here we have to sync with the inner repos as the extension location repos are 
+		// read-only wrappers.
+		DirectoryChangeListener listener = new RepositoryListener(metadataRepository.metadataRepository, artifactRepository.artifactRepository);
+		if (metadataRepository.getProperties().get(SiteListener.SITE_POLICY) != null)
+			listener = new SiteListener(metadataRepository.getProperties(), metadataRepository.getLocation().toExternalForm(), new BundlePoolFilteredListener(listener));
+		watcher.addListener(listener);
+		watcher.poll();
+		artifactRepository.state(INITIALIZED);
+		metadataRepository.state(INITIALIZED);
+	}
+
 	/*
 	 * Create a new site listener on the given site.
 	 */
 	public SiteListener(Map properties, String url, DirectoryChangeListener delegate) {
-		super(Activator.getContext(), url, null, true);
 		this.url = url;
 		this.delegate = delegate;
 		this.policy = (String) properties.get(SITE_POLICY);
@@ -77,7 +123,7 @@
 	public boolean isInterested(File file) {
 		// make sure that our delegate and super-class are both interested in 
 		// the file before we consider it
-		if (!delegate.isInterested(file) || !super.isInterested(file))
+		if (!delegate.isInterested(file))
 			return false;
 		if (Site.POLICY_MANAGED_ONLY.equals(policy)) {
 			// we only want plug-ins referenced by features
@@ -209,7 +255,7 @@
 			File featureFile = (File) iter.next();
 			// add the feature path
 			result.add(featureFile.toString());
-			org.eclipse.equinox.internal.provisional.p2.metadata.generator.Feature feature = (org.eclipse.equinox.internal.provisional.p2.metadata.generator.Feature) featureCache.get(featureFile);
+			Feature feature = (Feature) featureCache.get(featureFile);
 			FeatureEntry[] entries = feature.getEntries();
 			for (int inner = 0; inner < entries.length; inner++) {
 				FeatureEntry entry = entries[inner];
@@ -249,32 +295,20 @@
 	 * plug-in id/version to File locations.
 	 */
 	private Map getPlugins(File siteLocation) {
-		ServiceReference reference = Activator.getContext().getServiceReference(PlatformAdmin.class.getName());
-		if (reference == null)
-			throw new IllegalStateException(Messages.platformadmin_not_registered);
-		try {
-			PlatformAdmin platformAdmin = (PlatformAdmin) Activator.getContext().getService(reference);
-			if (platformAdmin == null)
-				throw new IllegalStateException(Messages.platformadmin_not_registered);
-			StateObjectFactory stateObjectFactory = platformAdmin.getFactory();
-			BundleDescriptionFactory factory = new BundleDescriptionFactory(stateObjectFactory, null);
-			File[] plugins = new File(siteLocation, PLUGINS).listFiles();
-			Map result = new HashMap();
-			for (int i = 0; plugins != null && i < plugins.length; i++) {
-				File bundleLocation = plugins[i];
-				if (bundleLocation.isDirectory() || bundleLocation.getName().endsWith(".jar")) {
-					BundleDescription description = factory.getBundleDescription(bundleLocation);
-					if (description != null) {
-						String id = description.getSymbolicName();
-						String version = description.getVersion().toString();
-						result.put(id + '/' + version, bundleLocation);
-					}
+		File[] plugins = new File(siteLocation, PLUGINS).listFiles();
+		Map result = new HashMap();
+		for (int i = 0; plugins != null && i < plugins.length; i++) {
+			File bundleLocation = plugins[i];
+			if (bundleLocation.isDirectory() || bundleLocation.getName().endsWith(".jar")) { //$NON-NLS-1$
+				BundleDescription description = BundlesAction.createBundleDescription(bundleLocation);
+				if (description != null) {
+					String id = description.getSymbolicName();
+					String version = description.getVersion().toString();
+					result.put(id + '/' + version, bundleLocation);
 				}
 			}
-			return result;
-		} finally {
-			Activator.getContext().ungetService(reference);
 		}
+		return result;
 	}
 
 	/* (non-Javadoc)
@@ -318,5 +352,4 @@
 	public void stopPoll() {
 		delegate.stopPoll();
 	}
-
 }
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 6c6ce1f..6bb464a 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
@@ -462,7 +462,7 @@
 		if (directories.isEmpty())
 			return;
 
-		DropinsRepositoryListener listener = new DropinsRepositoryListener(Activator.getContext(), DROPINS);
+		DropinsRepositoryListener listener = new DropinsRepositoryListener(DROPINS);
 		DirectoryWatcher watcher = new DirectoryWatcher((File[]) directories.toArray(new File[directories.size()]));
 		watcher.addListener(listener);
 		watcher.poll();
@@ -634,4 +634,15 @@
 		}
 		return null;
 	}
+
+	// TODO Fix this up to get the services in a better way
+	public static IArtifactRepositoryManager getArtifactRepositoryManager() {
+		return (IArtifactRepositoryManager) ServiceHelper.getService(bundleContext, IArtifactRepositoryManager.class.getName());
+	}
+
+	// TODO Fix this up to get the services in a better way
+	public static IMetadataRepositoryManager getMetadataRepositoryManager() {
+		return (IMetadataRepositoryManager) ServiceHelper.getService(bundleContext, IMetadataRepositoryManager.class.getName());
+	}
+
 }
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
index 8771e00..4f2fba7 100644
--- 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
@@ -7,6 +7,7 @@
  * 
  * Contributors:
  *     IBM Corporation - initial API and implementation
+ *     Code 9 - ongoing development
  *******************************************************************************/
 package org.eclipse.equinox.internal.p2.reconciler.dropins;
 
@@ -26,11 +27,8 @@
 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 PLUGINS = "plugins"; //$NON-NLS-1$
 	private static final String FEATURES = "features"; //$NON-NLS-1$
 	private static final String JAR = ".jar"; //$NON-NLS-1$
@@ -40,13 +38,11 @@
 	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, null, true);
-		this.context = context;
+	public DropinsRepositoryListener(String repositoryName) {
+		super(repositoryName, true);
 	}
 
 	public boolean isInterested(File file) {
@@ -217,19 +213,13 @@
 	}
 
 	private void removeMetadataRepository(String urlString) {
-		ServiceReference reference = context.getServiceReference(IMetadataRepositoryManager.class.getName());
-		IMetadataRepositoryManager manager = null;
-		if (reference != null)
-			manager = (IMetadataRepositoryManager) context.getService(reference);
+		IMetadataRepositoryManager manager = Activator.getMetadataRepositoryManager();
 		if (manager == null)
 			throw new IllegalStateException(Messages.metadata_repo_manager_not_registered);
-
 		try {
 			manager.removeRepository(new URL(urlString));
 		} catch (MalformedURLException e) {
 			LogHelper.log(new Status(IStatus.ERROR, Activator.ID, "Error occurred while creating URL from: " + urlString, e)); //$NON-NLS-1$
-		} finally {
-			context.ungetService(reference);
 		}
 	}
 
@@ -250,19 +240,13 @@
 	}
 
 	public void removeArtifactRepository(String urlString) {
-		ServiceReference reference = context.getServiceReference(IArtifactRepositoryManager.class.getName());
-		IArtifactRepositoryManager manager = null;
-		if (reference != null)
-			manager = (IArtifactRepositoryManager) context.getService(reference);
+		IArtifactRepositoryManager manager = Activator.getArtifactRepositoryManager();
 		if (manager == null)
 			throw new IllegalStateException(Messages.artifact_repo_manager_not_registered);
-
 		try {
 			manager.removeRepository(new URL(urlString));
 		} catch (MalformedURLException e) {
 			LogHelper.log(new Status(IStatus.ERROR, Activator.ID, "Error occurred while creating URL from: " + urlString, e)); //$NON-NLS-1$
-		} finally {
-			context.ungetService(reference);
 		}
 	}
 
diff --git a/bundles/org.eclipse.equinox.p2.tests/.classpath b/bundles/org.eclipse.equinox.p2.tests/.classpath
index ce73933..2fbb7a2 100644
--- a/bundles/org.eclipse.equinox.p2.tests/.classpath
+++ b/bundles/org.eclipse.equinox.p2.tests/.classpath
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <classpath>
-	<classpathentry kind="src" path="src"/>
 	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.4"/>
 	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+	<classpathentry kind="src" path="src"/>
 	<classpathentry kind="output" path="bin"/>
 </classpath>
diff --git a/bundles/org.eclipse.equinox.p2.tests/.settings/org.eclipse.jdt.core.prefs b/bundles/org.eclipse.equinox.p2.tests/.settings/org.eclipse.jdt.core.prefs
index 20b2cd0..b4fdaeb 100644
--- a/bundles/org.eclipse.equinox.p2.tests/.settings/org.eclipse.jdt.core.prefs
+++ b/bundles/org.eclipse.equinox.p2.tests/.settings/org.eclipse.jdt.core.prefs
@@ -1,4 +1,4 @@
-#Fri Apr 25 12:15:58 EDT 2008
+#Tue Aug 19 22:58:53 EDT 2008
 eclipse.preferences.version=1
 instance/org.eclipse.core.net/org.eclipse.core.net.hasMigrated=true
 org.eclipse.jdt.core.builder.cleanOutputFolder=clean
@@ -157,7 +157,6 @@
 org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
 org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true
 org.eclipse.jdt.core.formatter.indentation.size=4
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation=insert
 org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
 org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert
 org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
diff --git a/bundles/org.eclipse.equinox.p2.tests/META-INF/MANIFEST.MF b/bundles/org.eclipse.equinox.p2.tests/META-INF/MANIFEST.MF
index 4af2c5f..2edc3ec 100644
--- a/bundles/org.eclipse.equinox.p2.tests/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.equinox.p2.tests/META-INF/MANIFEST.MF
@@ -15,6 +15,7 @@
  org.eclipse.equinox.internal.p2.metadata.repository,
  org.eclipse.equinox.internal.p2.metadata.repository.io,
  org.eclipse.equinox.internal.p2.persistence,
+ org.eclipse.equinox.internal.p2.publisher.eclipse,
  org.eclipse.equinox.internal.p2.resolution,
  org.eclipse.equinox.internal.p2.touchpoint.eclipse,
  org.eclipse.equinox.internal.p2.updatesite,
@@ -37,6 +38,7 @@
  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.equinox.spi.p2.publisher,
  org.eclipse.osgi.service.environment;version="1.0.0",
  org.eclipse.osgi.service.resolver;version="1.1.0",
  org.eclipse.osgi.service.urlconversion;version="1.0.0",
diff --git a/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/artifact/repository/SimpleArtifactRepositoryTest.java b/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/artifact/repository/SimpleArtifactRepositoryTest.java
index a0eac7f..cb4d5a6 100644
--- a/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/artifact/repository/SimpleArtifactRepositoryTest.java
+++ b/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/artifact/repository/SimpleArtifactRepositoryTest.java
@@ -7,6 +7,7 @@
  *
  * Contributors:
  * 	compeople AG (Stefan Liebig) - initial API and implementation
+ *     Code 9 - ongoing development
  *******************************************************************************/
 package org.eclipse.equinox.p2.tests.artifact.repository;
 
@@ -18,13 +19,13 @@
 import junit.framework.TestCase;
 import org.eclipse.equinox.internal.p2.artifact.repository.simple.SimpleArtifactRepository;
 import org.eclipse.equinox.internal.p2.core.helpers.ServiceHelper;
-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.artifact.repository.*;
 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.metadata.generator.EclipseInstallGeneratorInfoProvider;
-import org.eclipse.equinox.internal.provisional.p2.metadata.generator.Generator;
+import org.eclipse.equinox.internal.provisional.p2.metadata.IArtifactKey;
 import org.eclipse.equinox.p2.tests.TestActivator;
+import org.eclipse.equinox.spi.p2.publisher.PublisherHelper;
+import org.osgi.framework.Version;
 
 public class SimpleArtifactRepositoryTest extends TestCase {
 	//artifact repository to remove on tear down
@@ -87,13 +88,11 @@
 		Map properties = new HashMap();
 		properties.put(IRepository.PROP_COMPRESSED, "true");
 		IArtifactRepository repo = artifactRepositoryManager.createRepository(repositoryURL, "artifact name", IArtifactRepositoryManager.TYPE_SIMPLE_REPOSITORY, properties);
-		EclipseInstallGeneratorInfoProvider provider = new EclipseInstallGeneratorInfoProvider();
-		provider.setArtifactRepository(repo);
-		provider.initialize(repositoryFile);
-		provider.setRootVersion("3.3");
-		provider.setRootId("sdk");
-		provider.setFlavor("tooling");
-		new Generator(provider).generate();
+
+		IArtifactKey key = PublisherHelper.createBinaryArtifactKey("testKeyId", new Version("1.2.3"));
+		IArtifactDescriptor descriptor = PublisherHelper.createArtifactDescriptor(key, null);
+		repo.addDescriptor(descriptor);
+
 		File files[] = repositoryFile.listFiles();
 		boolean jarFilePresent = false;
 		boolean artifactFilePresent = false;
@@ -105,6 +104,8 @@
 				artifactFilePresent = false;
 			}
 		}
+		delete(repositoryFile);
+
 		if (!jarFilePresent)
 			fail("Repository should create JAR for artifact.xml");
 		if (artifactFilePresent)
@@ -120,13 +121,11 @@
 		Map properties = new HashMap();
 		properties.put(IRepository.PROP_COMPRESSED, "false");
 		IArtifactRepository repo = artifactRepositoryManager.createRepository(repositoryURL, "artifact name", IArtifactRepositoryManager.TYPE_SIMPLE_REPOSITORY, properties);
-		EclipseInstallGeneratorInfoProvider provider = new EclipseInstallGeneratorInfoProvider();
-		provider.setArtifactRepository(repo);
-		provider.initialize(repositoryFile);
-		provider.setRootVersion("3.3");
-		provider.setRootId("sdk");
-		provider.setFlavor("tooling");
-		new Generator(provider).generate();
+
+		IArtifactKey key = PublisherHelper.createBinaryArtifactKey("testKeyId", new Version("1.2.3"));
+		IArtifactDescriptor descriptor = PublisherHelper.createArtifactDescriptor(key, null);
+		repo.addDescriptor(descriptor);
+
 		File files[] = repositoryFile.listFiles();
 		boolean jarFilePresent = false;
 		boolean artifactFilePresent = false;
@@ -138,6 +137,8 @@
 				artifactFilePresent = true;
 			}
 		}
+		delete(repositoryFile);
+
 		if (jarFilePresent)
 			fail("Repository should not create JAR for artifact.xml");
 		if (!artifactFilePresent)
diff --git a/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/directorywatcher/TestRepositoryWatcher.java b/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/directorywatcher/TestRepositoryWatcher.java
index 321612b..8ce3f70 100644
--- a/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/directorywatcher/TestRepositoryWatcher.java
+++ b/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/directorywatcher/TestRepositoryWatcher.java
@@ -36,7 +36,7 @@
 	 * Create and return a new test directory watcher class which will listen on the given folder.
 	 */
 	public static TestRepositoryWatcher createWatcher(File folder) {
-		RepositoryListener listener = new RepositoryListener(TestActivator.getContext(), AbstractProvisioningTest.getUniqueString(), null, false);
+		RepositoryListener listener = new RepositoryListener(AbstractProvisioningTest.getUniqueString(), false);
 		Dictionary props = new Hashtable();
 		props.put(DirectoryWatcher.DIR, folder.getAbsolutePath());
 		props.put(DirectoryWatcher.POLL, "500");
diff --git a/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/metadata/repository/JarURLRepositoryTest.java b/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/metadata/repository/JarURLRepositoryTest.java
index 4e3b996..3701411 100644
--- a/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/metadata/repository/JarURLRepositoryTest.java
+++ b/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/metadata/repository/JarURLRepositoryTest.java
@@ -7,24 +7,29 @@
  *
  * Contributors:
  *     IBM Corporation - initial API and implementation
+ *     Code 9 - ongoing development
  *******************************************************************************/
 package org.eclipse.equinox.p2.tests.metadata.repository;
 
 import java.io.File;
 import java.net.MalformedURLException;
 import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
 import junit.framework.TestCase;
-import org.eclipse.core.runtime.FileLocator;
 import org.eclipse.equinox.internal.p2.core.helpers.FileUtils;
 import org.eclipse.equinox.internal.provisional.p2.core.ProvisionException;
-import org.eclipse.equinox.internal.provisional.p2.metadata.generator.EclipseInstallGeneratorInfoProvider;
-import org.eclipse.equinox.internal.provisional.p2.metadata.generator.Generator;
+import org.eclipse.equinox.internal.provisional.p2.core.repository.IRepository;
+import org.eclipse.equinox.internal.provisional.p2.metadata.IInstallableUnit;
+import org.eclipse.equinox.internal.provisional.p2.metadata.MetadataFactory;
+import org.eclipse.equinox.internal.provisional.p2.metadata.MetadataFactory.InstallableUnitDescription;
 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.p2.tests.TestActivator;
 import org.osgi.framework.ServiceReference;
+import org.osgi.framework.Version;
 
 public class JarURLRepositoryTest extends TestCase {
 
@@ -40,44 +45,32 @@
 		this("");
 	}
 
-	private static boolean deleteDirectory(File directory) {
-		if (directory.exists() && directory.isDirectory()) {
-			File[] files = directory.listFiles();
-			for (int i = 0; i < files.length; i++) {
-				if (files[i].isDirectory()) {
-					deleteDirectory(files[i]);
-				} else {
-					files[i].delete();
-				}
-			}
-		}
-		return directory.delete();
-	}
-
 	protected void setUp() throws Exception {
 		managerRef = TestActivator.getContext().getServiceReference(IMetadataRepositoryManager.class.getName());
 		manager = (IMetadataRepositoryManager) TestActivator.getContext().getService(managerRef);
 
-		EclipseInstallGeneratorInfoProvider provider = new EclipseInstallGeneratorInfoProvider();
-		URL base = TestActivator.getContext().getBundle().getEntry("/testData/generator/eclipse3.3");
-		provider.initialize(new File(FileLocator.toFileURL(base).getPath()));
 		String tempDir = System.getProperty("java.io.tmpdir");
 		File testRepo = new File(tempDir, "testRepo");
-		deleteDirectory(testRepo);
+		FileUtils.deleteAll(testRepo);
 		testRepo.mkdir();
-		provider.setFlavor("jartest");
-		IMetadataRepository repository = manager.createRepository(testRepo.toURL(), "testRepo", IMetadataRepositoryManager.TYPE_SIMPLE_REPOSITORY, null);
-		provider.setMetadataRepository(repository);
-		new Generator(provider).generate();
-		testRepoJar = new File(tempDir, "testRepo.jar");
-		FileUtils.zip(new File[] {testRepo}, null, testRepoJar, FileUtils.createDynamicPathComputer(1));
+		Map properties = new HashMap();
+		properties.put(IRepository.PROP_COMPRESSED, "true");
+		IMetadataRepository repo = manager.createRepository(testRepo.toURL(), "TestRepo", IMetadataRepositoryManager.TYPE_SIMPLE_REPOSITORY, properties);
+
+		InstallableUnitDescription descriptor = new MetadataFactory.InstallableUnitDescription();
+		descriptor.setId("testIuId");
+		descriptor.setVersion(new Version("3.2.1"));
+		IInstallableUnit iu = MetadataFactory.createInstallableUnit(descriptor);
+		repo.addInstallableUnits(new IInstallableUnit[] {iu});
+
+		testRepoJar = new File(testRepo, "content.jar");
 		assertTrue(testRepoJar.exists());
 		testRepoJar.deleteOnExit();
-		deleteDirectory(testRepo);
 	}
 
 	protected void tearDown() throws Exception {
 		manager = null;
+		FileUtils.deleteAll(testRepoJar.getParentFile());
 		TestActivator.getContext().ungetService(managerRef);
 	}
 
diff --git a/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/metadata/repository/LocalMetadataRepositoryTest.java b/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/metadata/repository/LocalMetadataRepositoryTest.java
index 24e1407..df79929 100644
--- a/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/metadata/repository/LocalMetadataRepositoryTest.java
+++ b/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/metadata/repository/LocalMetadataRepositoryTest.java
@@ -7,6 +7,7 @@
  * 
  * Contributors:
  *     IBM Corporation - initial API and implementation
+ *     Code 9 - ongoing development
  *******************************************************************************/
 package org.eclipse.equinox.p2.tests.metadata.repository;
 
@@ -19,12 +20,14 @@
 import org.eclipse.equinox.internal.provisional.p2.core.eventbus.*;
 import org.eclipse.equinox.internal.provisional.p2.core.repository.IRepository;
 import org.eclipse.equinox.internal.provisional.p2.core.repository.RepositoryEvent;
-import org.eclipse.equinox.internal.provisional.p2.metadata.generator.EclipseInstallGeneratorInfoProvider;
-import org.eclipse.equinox.internal.provisional.p2.metadata.generator.Generator;
+import org.eclipse.equinox.internal.provisional.p2.metadata.IInstallableUnit;
+import org.eclipse.equinox.internal.provisional.p2.metadata.MetadataFactory;
+import org.eclipse.equinox.internal.provisional.p2.metadata.MetadataFactory.InstallableUnitDescription;
 import org.eclipse.equinox.internal.provisional.p2.metadata.repository.IMetadataRepository;
 import org.eclipse.equinox.internal.provisional.p2.metadata.repository.IMetadataRepositoryManager;
 import org.eclipse.equinox.p2.tests.AbstractProvisioningTest;
 import org.eclipse.equinox.p2.tests.TestActivator;
+import org.osgi.framework.Version;
 
 /**
  * Test API of the local metadata repository implementation.
@@ -57,14 +60,13 @@
 		Map properties = new HashMap();
 		properties.put(IRepository.PROP_COMPRESSED, "true");
 		IMetadataRepository repo = manager.createRepository(repoLocation.toURL(), "TestRepo", IMetadataRepositoryManager.TYPE_SIMPLE_REPOSITORY, properties);
-		EclipseInstallGeneratorInfoProvider provider = new EclipseInstallGeneratorInfoProvider();
-		provider.setMetadataRepository(repo);
-		provider.initialize(repoLocation);
-		provider.setRootVersion("3.3");
-		provider.setRootId("sdk");
-		provider.setFlavor("tooling");
-		// Generate the repository
-		new Generator(provider).generate();
+
+		InstallableUnitDescription descriptor = new MetadataFactory.InstallableUnitDescription();
+		descriptor.setId("testIuId");
+		descriptor.setVersion(new Version("3.2.1"));
+		IInstallableUnit iu = MetadataFactory.createInstallableUnit(descriptor);
+		repo.addInstallableUnits(new IInstallableUnit[] {iu});
+
 		File[] files = repoLocation.listFiles();
 		boolean jarFilePresent = false;
 		boolean xmlFilePresent = false;
@@ -126,14 +128,13 @@
 		Map properties = new HashMap();
 		properties.put(IRepository.PROP_COMPRESSED, "false");
 		IMetadataRepository repo = manager.createRepository(repoLocation.toURL(), "TestRepo", IMetadataRepositoryManager.TYPE_SIMPLE_REPOSITORY, properties);
-		EclipseInstallGeneratorInfoProvider provider = new EclipseInstallGeneratorInfoProvider();
-		provider.setMetadataRepository(repo);
-		provider.initialize(repoLocation);
-		provider.setRootVersion("3.3");
-		provider.setRootId("sdk");
-		provider.setFlavor("tooling");
-		// Generate the repository
-		new Generator(provider).generate();
+
+		InstallableUnitDescription descriptor = new MetadataFactory.InstallableUnitDescription();
+		descriptor.setId("testIuId");
+		descriptor.setVersion(new Version("3.2.1"));
+		IInstallableUnit iu = MetadataFactory.createInstallableUnit(descriptor);
+		repo.addInstallableUnits(new IInstallableUnit[] {iu});
+
 		File[] files = repoLocation.listFiles();
 		boolean jarFilePresent = false;
 		// none of the files in the repository should be the content.xml.jar
diff --git a/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/updatesite/UpdateSiteTest.java b/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/updatesite/UpdateSiteTest.java
index ba9f4d7..7b767d5 100644
--- a/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/updatesite/UpdateSiteTest.java
+++ b/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/updatesite/UpdateSiteTest.java
@@ -10,7 +10,8 @@
  *******************************************************************************/
 package org.eclipse.equinox.p2.tests.updatesite;
 
-import java.io.*;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
 import java.net.MalformedURLException;
 import java.net.URL;
 import junit.framework.Test;
@@ -456,27 +457,9 @@
 		IArtifactKey[] keys = artifactRepo.getArtifactKeys();
 		for (int i = 0; i < keys.length; i++) {
 			if (keys[i].getId().equals("Plugin240121")) {
-				FileOutputStream fos = null;
-				try {
-					File tmp;
-					try {
-						tmp = File.createTempFile("p2.test", "test");
-						tmp.deleteOnExit();
-						fos = new FileOutputStream(tmp);
-					} catch (IOException e1) {
-						fail("Can't create temp file");
-					}
-					IStatus status = artifactRepo.getArtifact(artifactRepo.getArtifactDescriptors(keys[i])[0], fos, new NullProgressMonitor());
-					if (!status.isOK())
-						fail("Can't get the expected artifact:" + keys[i]);
-				} finally {
-					if (fos != null)
-						try {
-							fos.close();
-						} catch (IOException e) {
-							//ignore
-						}
-				}
+				IStatus status = artifactRepo.getArtifact(artifactRepo.getArtifactDescriptors(keys[i])[0], new ByteArrayOutputStream(500), new NullProgressMonitor());
+				if (!status.isOK())
+					fail("Can't get the expected artifact:" + keys[i]);
 			}
 		}
 	}
diff --git a/bundles/org.eclipse.equinox.p2.touchpoint.eclipse/META-INF/MANIFEST.MF b/bundles/org.eclipse.equinox.p2.touchpoint.eclipse/META-INF/MANIFEST.MF
index 5ea562a..00a219c 100644
--- a/bundles/org.eclipse.equinox.p2.touchpoint.eclipse/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.equinox.p2.touchpoint.eclipse/META-INF/MANIFEST.MF
@@ -17,11 +17,12 @@
  org.eclipse.equinox.internal.provisional.p2.core.repository,
  org.eclipse.equinox.internal.provisional.p2.engine,
  org.eclipse.equinox.internal.provisional.p2.metadata,
- org.eclipse.equinox.internal.provisional.p2.metadata.generator;resolution:=optional,
  org.eclipse.equinox.internal.provisional.p2.metadata.query,
  org.eclipse.equinox.internal.provisional.p2.query,
  org.eclipse.equinox.internal.provisional.spi.p2.artifact.repository,
  org.eclipse.equinox.internal.simpleconfigurator.manipulator,
+ org.eclipse.equinox.p2.publisher.eclipse;resolution:=optional,
+ org.eclipse.equinox.spi.p2.publisher;resolution:=optional,
  org.eclipse.osgi.service.datalocation;version="1.0.0",
  org.eclipse.osgi.service.environment;version="1.0.0",
  org.eclipse.osgi.service.resolver;version="1.2.0";resolution:=optional,
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 dda2c4f..611e902 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
@@ -4,7 +4,9 @@
  * 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
+ * Contributors: 
+ *     IBM Corporation - initial API and implementation
+ *     Code 9 - ongoing development
  ******************************************************************************/
 package org.eclipse.equinox.internal.p2.touchpoint.eclipse;
 
@@ -20,6 +22,9 @@
 import org.eclipse.equinox.internal.provisional.p2.core.ProvisionException;
 import org.eclipse.equinox.internal.provisional.p2.engine.*;
 import org.eclipse.equinox.internal.provisional.p2.metadata.*;
+import org.eclipse.equinox.p2.publisher.eclipse.BundlesAction;
+import org.eclipse.equinox.spi.p2.publisher.PublisherHelper;
+import org.eclipse.osgi.service.resolver.BundleDescription;
 import org.eclipse.osgi.util.NLS;
 import org.osgi.framework.Version;
 
@@ -103,7 +108,7 @@
 
 		Class c = null;
 		try {
-			c = Class.forName("org.eclipse.equinox.internal.provisional.p2.metadata.generator.MetadataGeneratorHelper"); //$NON-NLS-1$
+			c = Class.forName("org.eclipse.equinox.spi.p2.publisher.PublisherHelper"); //$NON-NLS-1$
 			if (c != null)
 				c = Class.forName("org.eclipse.osgi.service.resolver.PlatformAdmin"); //$NON-NLS-1$
 		} catch (ClassNotFoundException e) {
@@ -124,13 +129,18 @@
 				LogHelper.log(Util.createError(NLS.bind(Messages.artifact_file_not_found, artifactKey.toString())));
 				return null;
 			}
-			return MetadataGeneratorUtils.createBundleIU(artifactKey, bundleFile);
+			return createBundleIU(artifactKey, bundleFile);
 		}
 
 		// should not occur
 		throw new IllegalStateException(Messages.unexpected_prepareiu_error);
 	}
 
+	private IInstallableUnit createBundleIU(IArtifactKey artifactKey, File bundleFile) {
+		BundleDescription bundleDescription = BundlesAction.createBundleDescription(bundleFile);
+		return PublisherHelper.createBundleIU(bundleDescription, (Map) bundleDescription.getUserObject(), bundleFile.isDirectory(), artifactKey);
+	}
+
 	public static IStatus loadManipulator(Manipulator manipulator) {
 		try {
 			manipulator.load();
diff --git a/bundles/org.eclipse.equinox.p2.touchpoint.eclipse/src/org/eclipse/equinox/internal/p2/touchpoint/eclipse/MetadataGeneratorUtils.java b/bundles/org.eclipse.equinox.p2.touchpoint.eclipse/src/org/eclipse/equinox/internal/p2/touchpoint/eclipse/MetadataGeneratorUtils.java
deleted file mode 100644
index e5fe603..0000000
--- a/bundles/org.eclipse.equinox.p2.touchpoint.eclipse/src/org/eclipse/equinox/internal/p2/touchpoint/eclipse/MetadataGeneratorUtils.java
+++ /dev/null
@@ -1,50 +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.touchpoint.eclipse;
-
-import java.io.File;
-import java.util.Map;
-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.BundleDescriptionFactory;
-import org.eclipse.equinox.internal.provisional.p2.metadata.generator.MetadataGeneratorHelper;
-import org.eclipse.osgi.service.resolver.*;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.ServiceReference;
-
-public class MetadataGeneratorUtils {
-
-	public static IInstallableUnit createBundleIU(IArtifactKey artifactKey, File bundleFile) {
-		BundleDescriptionFactory factory = initializeBundleDescriptionFactory(Activator.getContext());
-		BundleDescription bundleDescription = factory.getBundleDescription(bundleFile);
-		if (bundleDescription == null)
-			return null;
-		return MetadataGeneratorHelper.createBundleIU(bundleDescription, (Map) bundleDescription.getUserObject(), bundleFile.isDirectory(), artifactKey);
-	}
-
-	private static BundleDescriptionFactory initializeBundleDescriptionFactory(BundleContext context) {
-
-		ServiceReference reference = context.getServiceReference(PlatformAdmin.class.getName());
-		if (reference == null)
-			throw new IllegalStateException(Messages.platformadmin_not_registered);
-		PlatformAdmin platformAdmin = (PlatformAdmin) context.getService(reference);
-		if (platformAdmin == null)
-			throw new IllegalStateException(Messages.platformadmin_not_registered);
-
-		try {
-			StateObjectFactory stateObjectFactory = platformAdmin.getFactory();
-			return new BundleDescriptionFactory(stateObjectFactory, null);
-		} finally {
-			context.ungetService(reference);
-		}
-	}
-
-}
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 83fea97..1fbcf5a 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
@@ -8,6 +8,7 @@
  * Contributors:
  *     IBM Corporation - initial API and implementation
  *     Red Hat Incorporated - fix for bug 225145
+ *     Code 9 - ongoing development
  *******************************************************************************/
 package org.eclipse.equinox.internal.p2.touchpoint.eclipse;
 
@@ -260,9 +261,15 @@
 		if (name == null)
 			name = "eclipse"; //$NON-NLS-1$
 
+		if (os.equals(org.eclipse.osgi.service.environment.Constants.OS_WIN32)) {
+			IPath path = new Path(name);
+			if ("exe".equals(path.getFileExtension())) //$NON-NLS-1$
+				return name;
+			return name + ".exe";
+		}
 		if (os.equals(org.eclipse.osgi.service.environment.Constants.OS_MACOSX)) {
 			IPath path = new Path(name);
-			if (path.segment(0).endsWith(".app")) //$NON-NLS-1$
+			if ("app".equals(path.getFileExtension())) //$NON-NLS-1$
 				return name;
 			StringBuffer buffer = new StringBuffer();
 			buffer.append(name.substring(0, 1).toUpperCase());
diff --git a/bundles/org.eclipse.equinox.p2.updatesite/META-INF/MANIFEST.MF b/bundles/org.eclipse.equinox.p2.updatesite/META-INF/MANIFEST.MF
index ce312f6..29694dc 100644
--- a/bundles/org.eclipse.equinox.p2.updatesite/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.equinox.p2.updatesite/META-INF/MANIFEST.MF
@@ -8,19 +8,22 @@
 Bundle-Activator: org.eclipse.equinox.internal.p2.updatesite.Activator
 Eclipse-LazyStart: true
 Import-Package: org.eclipse.equinox.internal.p2.core.helpers,
- org.eclipse.equinox.internal.p2.metadata.generator.features,
+ org.eclipse.equinox.internal.p2.publisher.eclipse,
  org.eclipse.equinox.internal.provisional.p2.artifact.repository,
  org.eclipse.equinox.internal.provisional.p2.core,
  org.eclipse.equinox.internal.provisional.p2.core.eventbus,
  org.eclipse.equinox.internal.provisional.p2.core.repository,
  org.eclipse.equinox.internal.provisional.p2.metadata,
- org.eclipse.equinox.internal.provisional.p2.metadata.generator,
  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.equinox.p2.publisher,
+ org.eclipse.equinox.p2.publisher.actions,
+ org.eclipse.equinox.p2.publisher.eclipse,
  org.eclipse.equinox.security.storage,
+ org.eclipse.equinox.spi.p2.publisher,
  org.eclipse.osgi.service.resolver;version="1.2.0",
  org.eclipse.osgi.signedcontent;version="1.0.0",
  org.eclipse.osgi.util;version="1.1.0",
diff --git a/bundles/org.eclipse.equinox.p2.updatesite/src/org/eclipse/equinox/internal/p2/updatesite/DefaultSiteParser.java b/bundles/org.eclipse.equinox.p2.updatesite/src/org/eclipse/equinox/internal/p2/updatesite/DefaultSiteParser.java
new file mode 100644
index 0000000..659c047
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.updatesite/src/org/eclipse/equinox/internal/p2/updatesite/DefaultSiteParser.java
@@ -0,0 +1,859 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 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.updatesite;
+
+import java.io.*;
+import java.util.*;
+import javax.xml.parsers.*;
+import org.eclipse.core.runtime.*;
+import org.eclipse.equinox.internal.p2.core.helpers.LogHelper;
+import org.eclipse.equinox.internal.p2.core.helpers.Tracing;
+import org.eclipse.equinox.p2.publisher.eclipse.URLEntry;
+import org.eclipse.osgi.util.NLS;
+import org.w3c.dom.*;
+import org.xml.sax.*;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * Parses a site.xml file.
+ * This class was initially copied from org.eclipse.update.core.model.DefaultSiteParser.
+ */
+public class DefaultSiteParser extends DefaultHandler {
+
+	private static final String ARCHIVE = "archive"; //$NON-NLS-1$
+	private static final String CATEGORY = "category"; //$NON-NLS-1$
+	private static final String CATEGORY_DEF = "category-def"; //$NON-NLS-1$
+
+	private static final String ASSOCIATE_SITES_URL = "associateSitesURL"; //$NON-NLS-1$
+	private static final String ASSOCIATE_SITE = "associateSite"; //$NON-NLS-1$
+	private static final String DEFAULT_INFO_URL = "index.html"; //$NON-NLS-1$
+	private static final String DESCRIPTION = "description"; //$NON-NLS-1$
+	private static final String FEATURE = "feature"; //$NON-NLS-1$
+	private static final String FEATURES = "features/"; //$NON-NLS-1$
+	private static final String MIRROR = "mirror"; //$NON-NLS-1$
+	private final static SAXParserFactory parserFactory = SAXParserFactory.newInstance();
+	private static final String PLUGIN_ID = Activator.ID;
+	private static final String SITE = "site"; //$NON-NLS-1$
+
+	private static final int STATE_ARCHIVE = 3;
+	private static final int STATE_CATEGORY = 4;
+	private static final int STATE_CATEGORY_DEF = 5;
+	private static final int STATE_DESCRIPTION_CATEGORY_DEF = 7;
+	private static final int STATE_DESCRIPTION_SITE = 6;
+	private static final int STATE_FEATURE = 2;
+	private static final int STATE_IGNORED_ELEMENT = -1;
+	private static final int STATE_INITIAL = 0;
+	private static final int STATE_SITE = 1;
+
+	private int currentState;
+
+	private boolean DESCRIPTION_SITE_ALREADY_SEEN = false;
+	// Current object stack (used to hold the current object we are
+	// populating in this plugin descriptor
+	Stack objectStack = new Stack();
+
+	private SAXParser parser;
+
+	// Current State Information
+	Stack stateStack = new Stack();
+
+	// List of string keys for translated strings
+	private final List messageKeys = new ArrayList(4);
+
+	private MultiStatus status;
+
+	/*
+	 * 
+	 */
+	private static void debug(String s) {
+		Tracing.debug("DefaultSiteParser: " + s); //$NON-NLS-1$
+	}
+
+	private static URLEntry[] getAssociateSites(String associateSitesURL) {
+
+		try {
+			DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
+			DocumentBuilder builder = domFactory.newDocumentBuilder();
+			Document document = builder.parse(associateSitesURL);
+			if (document == null)
+				return null;
+			NodeList mirrorNodes = document.getElementsByTagName(ASSOCIATE_SITE);
+			URLEntry[] mirrors = new URLEntry[mirrorNodes.getLength()];
+			for (int i = 0; i < mirrorNodes.getLength(); i++) {
+				Element mirrorNode = (Element) mirrorNodes.item(i);
+				mirrors[i] = new URLEntry();
+				String infoURL = mirrorNode.getAttribute("url"); //$NON-NLS-1$
+				String label = mirrorNode.getAttribute("label"); //$NON-NLS-1$
+				mirrors[i].setURL(infoURL);
+				mirrors[i].setAnnotation(label);
+
+				if (Tracing.DEBUG_GENERATOR_PARSING)
+					debug("Processed mirror: url:" + infoURL + " label:" + label); //$NON-NLS-1$ //$NON-NLS-2$
+			}
+			return mirrors;
+		} catch (Exception e) {
+			// log if absolute url
+			if (associateSitesURL != null && (associateSitesURL.startsWith("http://") //$NON-NLS-1$
+					|| associateSitesURL.startsWith("https://") //$NON-NLS-1$
+					|| associateSitesURL.startsWith("file://") //$NON-NLS-1$
+					|| associateSitesURL.startsWith("ftp://") //$NON-NLS-1$
+			|| associateSitesURL.startsWith("jar://"))) //$NON-NLS-1$
+				log(Messages.DefaultSiteParser_mirrors, e);
+			return null;
+		}
+	}
+
+	static URLEntry[] getMirrors(String mirrorsURL) {
+
+		try {
+			String countryCode = Locale.getDefault().getCountry().toLowerCase();
+			int timeZone = (new GregorianCalendar()).get(Calendar.ZONE_OFFSET) / (60 * 60 * 1000);
+
+			if (mirrorsURL.indexOf("?") != -1) { //$NON-NLS-1$
+				mirrorsURL = mirrorsURL + "&"; //$NON-NLS-1$
+			} else {
+				mirrorsURL = mirrorsURL + "?"; //$NON-NLS-1$
+			}
+			mirrorsURL = mirrorsURL + "countryCode=" + countryCode + "&timeZone=" + timeZone + "&responseType=xml"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+
+			DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
+			DocumentBuilder builder = domFactory.newDocumentBuilder();
+			Document document = builder.parse(mirrorsURL);
+			if (document == null)
+				return null;
+			NodeList mirrorNodes = document.getElementsByTagName(MIRROR);
+			URLEntry[] mirrors = new URLEntry[mirrorNodes.getLength()];
+			for (int i = 0; i < mirrorNodes.getLength(); i++) {
+				Element mirrorNode = (Element) mirrorNodes.item(i);
+				mirrors[i] = new URLEntry();
+				String infoURL = mirrorNode.getAttribute("url"); //$NON-NLS-1$
+				String label = mirrorNode.getAttribute("label"); //$NON-NLS-1$
+				mirrors[i].setURL(infoURL);
+				mirrors[i].setAnnotation(label);
+
+				if (Tracing.DEBUG_GENERATOR_PARSING)
+					debug("Processed mirror: url:" + infoURL + " label:" + label); //$NON-NLS-1$ //$NON-NLS-2$
+			}
+			return mirrors;
+		} catch (Exception e) {
+			// log if absolute url
+			if (mirrorsURL != null && (mirrorsURL.startsWith("http://") //$NON-NLS-1$
+					|| mirrorsURL.startsWith("https://") //$NON-NLS-1$
+					|| mirrorsURL.startsWith("file://") //$NON-NLS-1$
+					|| mirrorsURL.startsWith("ftp://") //$NON-NLS-1$
+			|| mirrorsURL.startsWith("jar://"))) //$NON-NLS-1$
+				LogHelper.log(new Status(IStatus.ERROR, Activator.ID, Messages.DefaultSiteParser_mirrors, e));
+			return null;
+		}
+	}
+
+	static void log(Exception e) {
+		LogHelper.log(new Status(IStatus.ERROR, Activator.ID, "Internal Error", e)); //$NON-NLS-1$
+	}
+
+	static void log(IStatus error) {
+		LogHelper.log(error);
+	}
+
+	static void log(String message) {
+		LogHelper.log(new Status(IStatus.WARNING, Activator.ID, message, null));
+	}
+
+	static void log(String message, Exception e) {
+		LogHelper.log(new Status(IStatus.WARNING, Activator.ID, message, e));
+	}
+
+	/**
+	 * Constructs a site parser.
+	 */
+	public DefaultSiteParser() {
+		super();
+		stateStack = new Stack();
+		objectStack = new Stack();
+		status = null;
+		DESCRIPTION_SITE_ALREADY_SEEN = false;
+		try {
+			parserFactory.setNamespaceAware(true);
+			this.parser = parserFactory.newSAXParser();
+		} catch (ParserConfigurationException e) {
+			log(e);
+		} catch (SAXException e) {
+			log(e);
+		}
+
+		if (Tracing.DEBUG_GENERATOR_PARSING)
+			debug("Created"); //$NON-NLS-1$
+	}
+
+	/**
+	 * Handle character text
+	 * @see DefaultHandler#characters(char[], int, int)
+	 * @since 2.0
+	 */
+	public void characters(char[] ch, int start, int length) {
+		String text = new String(ch, start, length);
+		//only push if description
+		int state = ((Integer) stateStack.peek()).intValue();
+		if (state == STATE_DESCRIPTION_SITE || state == STATE_DESCRIPTION_CATEGORY_DEF)
+			objectStack.push(text);
+
+	}
+
+	/**
+	 * Handle end of element tags
+	 * @see DefaultHandler#endElement(String, String, String)
+	 * @since 2.0
+	 */
+	public void endElement(String uri, String localName, String qName) {
+
+		String text = null;
+		URLEntry info = null;
+
+		int state = ((Integer) stateStack.peek()).intValue();
+		switch (state) {
+			case STATE_IGNORED_ELEMENT :
+			case STATE_ARCHIVE :
+			case STATE_CATEGORY :
+				stateStack.pop();
+				break;
+
+			case STATE_INITIAL :
+				internalError(Messages.DefaultSiteParser_ParsingStackBackToInitialState);
+				break;
+
+			case STATE_SITE :
+				stateStack.pop();
+				if (objectStack.peek() instanceof String) {
+					text = (String) objectStack.pop();
+					SiteModel site = (SiteModel) objectStack.peek();
+					site.getDescription().setAnnotation(text);
+				}
+				//do not pop the object
+				break;
+
+			case STATE_FEATURE :
+				stateStack.pop();
+				objectStack.pop();
+				break;
+
+			case STATE_CATEGORY_DEF :
+				stateStack.pop();
+				if (objectStack.peek() instanceof String) {
+					text = (String) objectStack.pop();
+					SiteCategory category = (SiteCategory) objectStack.peek();
+					category.setDescription(text);
+				}
+				objectStack.pop();
+				break;
+
+			case STATE_DESCRIPTION_SITE :
+				stateStack.pop();
+				text = ""; //$NON-NLS-1$
+				while (objectStack.peek() instanceof String) {
+					// add text, preserving at most one space between text fragments
+					String newText = (String) objectStack.pop();
+					if (trailingSpace(newText) && !leadingSpace(text)) {
+						text = " " + text; //$NON-NLS-1$
+					}
+					text = newText.trim() + text;
+					if (leadingSpace(newText) && !leadingSpace(text)) {
+						text = " " + text; //$NON-NLS-1$
+					}
+				}
+				text = text.trim();
+
+				info = (URLEntry) objectStack.pop();
+				if (text != null)
+					info.setAnnotation(text);
+
+				SiteModel siteModel = (SiteModel) objectStack.peek();
+				// override description.
+				// do not raise error as previous description may be default one
+				// when parsing site tag
+				if (DESCRIPTION_SITE_ALREADY_SEEN)
+					debug(NLS.bind(Messages.DefaultSiteParser_ElementAlreadySet, (new String[] {getState(state)})));
+				siteModel.setDescription(info);
+				DESCRIPTION_SITE_ALREADY_SEEN = true;
+				break;
+
+			case STATE_DESCRIPTION_CATEGORY_DEF :
+				stateStack.pop();
+				text = ""; //$NON-NLS-1$
+				while (objectStack.peek() instanceof String) {
+					// add text, preserving at most one space between text fragments
+					String newText = (String) objectStack.pop();
+					if (trailingSpace(newText) && !leadingSpace(text)) {
+						text = " " + text; //$NON-NLS-1$
+					}
+					text = newText.trim() + text;
+					if (leadingSpace(newText) && !leadingSpace(text)) {
+						text = " " + text; //$NON-NLS-1$
+					}
+				}
+				text = text.trim();
+
+				info = (URLEntry) objectStack.pop();
+				if (text != null)
+					info.setAnnotation(text);
+
+				SiteCategory category = (SiteCategory) objectStack.peek();
+				if (category.getDescription() != null)
+					internalError(NLS.bind(Messages.DefaultSiteParser_ElementAlreadySet, (new String[] {getState(state), category.getLabel()})));
+				else
+					category.setDescription(info.getAnnotation());
+				break;
+
+			default :
+				internalError(NLS.bind(Messages.DefaultSiteParser_UnknownEndState, (new String[] {getState(state)})));
+				break;
+		}
+
+		if (Tracing.DEBUG_GENERATOR_PARSING)
+			debug("End Element:" + uri + ":" + localName + ":" + qName);//$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+	}
+
+	/*
+	 * Handles an error state specified by the status.  The collection of all logged status
+	 * objects can be accessed using <code>getStatus()</code>.
+	 *
+	 * @param error a status detailing the error condition
+	 */
+	private void error(IStatus error) {
+
+		if (status == null) {
+			status = new MultiStatus(PLUGIN_ID, 0, Messages.DefaultSiteParser_ErrorParsingSite, null);
+		}
+
+		status.add(error);
+		if (Tracing.DEBUG_GENERATOR_PARSING)
+			LogHelper.log(error);
+	}
+
+	/**
+	 * Handle errors
+	 * @see DefaultHandler#error(SAXParseException)
+	 * @since 2.0
+	 */
+	public void error(SAXParseException ex) {
+		logStatus(ex);
+	}
+
+	/**
+	 * Handle fatal errors
+	 * @see DefaultHandler#fatalError(SAXParseException)
+	 * @exception SAXException
+	 * @since 2.0
+	 */
+	public void fatalError(SAXParseException ex) throws SAXException {
+		logStatus(ex);
+		throw ex;
+	}
+
+	/*
+	 * return the state as String
+	 */
+	private String getState(int state) {
+
+		switch (state) {
+			case STATE_IGNORED_ELEMENT :
+				return "Ignored"; //$NON-NLS-1$
+
+			case STATE_INITIAL :
+				return "Initial"; //$NON-NLS-1$
+
+			case STATE_SITE :
+				return "Site"; //$NON-NLS-1$
+
+			case STATE_FEATURE :
+				return "Feature"; //$NON-NLS-1$
+
+			case STATE_ARCHIVE :
+				return "Archive"; //$NON-NLS-1$
+
+			case STATE_CATEGORY :
+				return "Category"; //$NON-NLS-1$
+
+			case STATE_CATEGORY_DEF :
+				return "Category Def"; //$NON-NLS-1$
+
+			case STATE_DESCRIPTION_CATEGORY_DEF :
+				return "Description / Category Def"; //$NON-NLS-1$
+
+			case STATE_DESCRIPTION_SITE :
+				return "Description / Site"; //$NON-NLS-1$
+
+			default :
+				return Messages.DefaultSiteParser_UnknownState;
+		}
+	}
+
+	/**
+	 * Returns all status objects accumulated by the parser.
+	 *
+	 * @return multi-status containing accumulated status, or <code>null</code>.
+	 * @since 2.0
+	 */
+	public MultiStatus getStatus() {
+		return status;
+	}
+
+	private void handleCategoryDefState(String elementName, Attributes attributes) {
+		if (elementName.equals(FEATURE)) {
+			stateStack.push(new Integer(STATE_FEATURE));
+			processFeature(attributes);
+		} else if (elementName.equals(ARCHIVE)) {
+			stateStack.push(new Integer(STATE_ARCHIVE));
+			processArchive(attributes);
+		} else if (elementName.equals(CATEGORY_DEF)) {
+			stateStack.push(new Integer(STATE_CATEGORY_DEF));
+			processCategoryDef(attributes);
+		} else if (elementName.equals(DESCRIPTION)) {
+			stateStack.push(new Integer(STATE_DESCRIPTION_CATEGORY_DEF));
+			processInfo(attributes);
+		} else
+			internalErrorUnknownTag(NLS.bind(Messages.DefaultSiteParser_UnknownElement, (new String[] {elementName, getState(currentState)})));
+	}
+
+	private void handleCategoryState(String elementName, Attributes attributes) {
+		if (elementName.equals(DESCRIPTION)) {
+			stateStack.push(new Integer(STATE_DESCRIPTION_SITE));
+			processInfo(attributes);
+		} else if (elementName.equals(FEATURE)) {
+			stateStack.push(new Integer(STATE_FEATURE));
+			processFeature(attributes);
+		} else if (elementName.equals(ARCHIVE)) {
+			stateStack.push(new Integer(STATE_ARCHIVE));
+			processArchive(attributes);
+		} else if (elementName.equals(CATEGORY_DEF)) {
+			stateStack.push(new Integer(STATE_CATEGORY_DEF));
+			processCategoryDef(attributes);
+		} else if (elementName.equals(CATEGORY)) {
+			stateStack.push(new Integer(STATE_CATEGORY));
+			processCategory(attributes);
+		} else
+			internalErrorUnknownTag(NLS.bind(Messages.DefaultSiteParser_UnknownElement, (new String[] {elementName, getState(currentState)})));
+	}
+
+	private void handleFeatureState(String elementName, Attributes attributes) {
+		if (elementName.equals(DESCRIPTION)) {
+			stateStack.push(new Integer(STATE_DESCRIPTION_SITE));
+			processInfo(attributes);
+		} else if (elementName.equals(FEATURE)) {
+			stateStack.push(new Integer(STATE_FEATURE));
+			processFeature(attributes);
+		} else if (elementName.equals(ARCHIVE)) {
+			stateStack.push(new Integer(STATE_ARCHIVE));
+			processArchive(attributes);
+		} else if (elementName.equals(CATEGORY_DEF)) {
+			stateStack.push(new Integer(STATE_CATEGORY_DEF));
+			processCategoryDef(attributes);
+		} else if (elementName.equals(CATEGORY)) {
+			stateStack.push(new Integer(STATE_CATEGORY));
+			processCategory(attributes);
+		} else
+			internalErrorUnknownTag(NLS.bind(Messages.DefaultSiteParser_UnknownElement, (new String[] {elementName, getState(currentState)})));
+	}
+
+	private void handleInitialState(String elementName, Attributes attributes) throws SAXException {
+		if (elementName.equals(SITE)) {
+			stateStack.push(new Integer(STATE_SITE));
+			processSite(attributes);
+		} else {
+			internalErrorUnknownTag(NLS.bind(Messages.DefaultSiteParser_UnknownElement, (new String[] {elementName, getState(currentState)})));
+			// what we received was not a site.xml, no need to continue
+			throw new SAXException(Messages.DefaultSiteParser_InvalidXMLStream);
+		}
+
+	}
+
+	private void handleSiteState(String elementName, Attributes attributes) {
+		if (elementName.equals(DESCRIPTION)) {
+			stateStack.push(new Integer(STATE_DESCRIPTION_SITE));
+			processInfo(attributes);
+		} else if (elementName.equals(FEATURE)) {
+			stateStack.push(new Integer(STATE_FEATURE));
+			processFeature(attributes);
+		} else if (elementName.equals(ARCHIVE)) {
+			stateStack.push(new Integer(STATE_ARCHIVE));
+			processArchive(attributes);
+		} else if (elementName.equals(CATEGORY_DEF)) {
+			stateStack.push(new Integer(STATE_CATEGORY_DEF));
+			processCategoryDef(attributes);
+		} else
+			internalErrorUnknownTag(NLS.bind(Messages.DefaultSiteParser_UnknownElement, (new String[] {elementName, getState(currentState)})));
+	}
+
+	/*
+	 * 
+	 */
+	private void internalError(String message) {
+		error(new Status(IStatus.ERROR, PLUGIN_ID, IStatus.OK, message, null));
+	}
+
+	/*
+	 * 
+	 */
+	private void internalErrorUnknownTag(String msg) {
+		stateStack.push(new Integer(STATE_IGNORED_ELEMENT));
+		internalError(msg);
+	}
+
+	private boolean leadingSpace(String str) {
+		if (str.length() <= 0) {
+			return false;
+		}
+		return Character.isWhitespace(str.charAt(0));
+	}
+
+	/*
+	 * 
+	 */
+	private void logStatus(SAXParseException ex) {
+		String name = ex.getSystemId();
+		if (name == null)
+			name = ""; //$NON-NLS-1$
+		else
+			name = name.substring(1 + name.lastIndexOf("/")); //$NON-NLS-1$
+
+		String msg;
+		if (name.equals("")) //$NON-NLS-1$
+			msg = NLS.bind(Messages.DefaultSiteParser_ErrorParsing, (new String[] {ex.getMessage()}));
+		else {
+			String[] values = new String[] {name, Integer.toString(ex.getLineNumber()), Integer.toString(ex.getColumnNumber()), ex.getMessage()};
+			msg = NLS.bind(Messages.DefaultSiteParser_ErrorlineColumnMessage, values);
+		}
+		error(new Status(IStatus.ERROR, PLUGIN_ID, msg, ex));
+	}
+
+	/**
+	 * Parses the specified input steam and constructs a site model.
+	 * The input stream is not closed as part of this operation.
+	 * 
+	 * @param in input stream
+	 * @return site model
+	 * @exception SAXException
+	 * @exception IOException
+	 * @since 2.0
+	 */
+	public SiteModel parse(InputStream in) throws SAXException, IOException {
+		stateStack.push(new Integer(STATE_INITIAL));
+		currentState = ((Integer) stateStack.peek()).intValue();
+		parser.parse(new InputSource(in), this);
+		if (objectStack.isEmpty())
+			throw new SAXException(Messages.DefaultSiteParser_NoSiteTag);
+		if (objectStack.peek() instanceof SiteModel) {
+			SiteModel site = (SiteModel) objectStack.pop();
+			site.setMessageKeys(messageKeys);
+			return site;
+		}
+		String stack = ""; //$NON-NLS-1$
+		Iterator iter = objectStack.iterator();
+		while (iter.hasNext()) {
+			stack = stack + iter.next().toString() + "\r\n"; //$NON-NLS-1$
+		}
+		throw new SAXException(NLS.bind(Messages.DefaultSiteParser_WrongParsingStack, (new String[] {stack})));
+	}
+
+	/* 
+	 * process archive info
+	 */
+	private void processArchive(Attributes attributes) {
+		URLEntry archive = new URLEntry();
+		String id = attributes.getValue("path"); //$NON-NLS-1$
+		if (id == null || id.trim().equals("")) { //$NON-NLS-1$
+			internalError(NLS.bind(Messages.DefaultSiteParser_Missing, (new String[] {"path", getState(currentState)}))); //$NON-NLS-1$
+		}
+
+		archive.setAnnotation(id);
+
+		String url = attributes.getValue("url"); //$NON-NLS-1$
+		if (url == null || url.trim().equals("")) { //$NON-NLS-1$
+			internalError(NLS.bind(Messages.DefaultSiteParser_Missing, (new String[] {"archive", getState(currentState)}))); //$NON-NLS-1$
+		} else {
+			archive.setURL(url);
+
+			SiteModel site = (SiteModel) objectStack.peek();
+			site.addArchive(archive);
+		}
+		if (Tracing.DEBUG_GENERATOR_PARSING)
+			debug("End processing Archive: path:" + id + " url:" + url);//$NON-NLS-1$ //$NON-NLS-2$
+
+	}
+
+	/* 
+	 * process the Category  info
+	 */
+	private void processCategory(Attributes attributes) {
+		String category = attributes.getValue("name"); //$NON-NLS-1$
+		SiteFeature feature = (SiteFeature) objectStack.peek();
+		feature.addCategoryName(category);
+
+		if (Tracing.DEBUG_GENERATOR_PARSING)
+			debug("End processing Category: name:" + category); //$NON-NLS-1$
+	}
+
+	/* 
+	 * process category def info
+	 */
+	private void processCategoryDef(Attributes attributes) {
+		SiteCategory category = new SiteCategory();
+		String name = attributes.getValue("name"); //$NON-NLS-1$
+		String label = attributes.getValue("label"); //$NON-NLS-1$
+		checkTranslated(label);
+		category.setName(name);
+		category.setLabel(label);
+
+		SiteModel site = (SiteModel) objectStack.peek();
+		site.addCategory(category);
+		objectStack.push(category);
+
+		if (Tracing.DEBUG_GENERATOR_PARSING)
+			debug("End processing CategoryDef: name:" + name + " label:" + label); //$NON-NLS-1$ //$NON-NLS-2$
+	}
+
+	/* 
+	 * process feature info
+	 */
+	private void processFeature(Attributes attributes) {
+		SiteFeature feature = new SiteFeature();
+
+		// feature location on the site
+		String urlInfo = attributes.getValue("url"); //$NON-NLS-1$
+		// identifier and version
+		String id = attributes.getValue("id"); //$NON-NLS-1$
+		String ver = attributes.getValue("version"); //$NON-NLS-1$
+
+		boolean noURL = (urlInfo == null || urlInfo.trim().equals("")); //$NON-NLS-1$
+		boolean noId = (id == null || id.trim().equals("")); //$NON-NLS-1$
+		boolean noVersion = (ver == null || ver.trim().equals("")); //$NON-NLS-1$
+
+		// We need to have id and version, or the url, or both.
+		if (noURL) {
+			if (noId || noVersion)
+				internalError(NLS.bind(Messages.DefaultSiteParser_Missing, (new String[] {"url", getState(currentState)}))); //$NON-NLS-1$
+			else
+				// default url
+				urlInfo = FEATURES + id + '_' + ver; // 
+		}
+
+		feature.setURLString(urlInfo);
+
+		String type = attributes.getValue("type"); //$NON-NLS-1$
+		feature.setType(type);
+
+		// if one is null, and not the other
+		if (noId ^ noVersion) {
+			String[] values = new String[] {id, ver, getState(currentState)};
+			log(NLS.bind(Messages.DefaultFeatureParser_IdOrVersionInvalid, values));
+		} else {
+			feature.setFeatureIdentifier(id);
+			feature.setFeatureVersion(ver);
+		}
+
+		// get label if it exists
+		String label = attributes.getValue("label"); //$NON-NLS-1$
+		if (label != null) {
+			if ("".equals(label.trim())) //$NON-NLS-1$
+				label = null;
+			checkTranslated(label);
+		}
+		feature.setLabel(label);
+
+		// OS
+		String os = attributes.getValue("os"); //$NON-NLS-1$
+		feature.setOS(os);
+
+		// WS
+		String ws = attributes.getValue("ws"); //$NON-NLS-1$
+		feature.setWS(ws);
+
+		// NL
+		String nl = attributes.getValue("nl"); //$NON-NLS-1$
+		feature.setNL(nl);
+
+		// arch
+		String arch = attributes.getValue("arch"); //$NON-NLS-1$
+		feature.setArch(arch);
+
+		//patch
+		String patch = attributes.getValue("patch"); //$NON-NLS-1$
+		feature.setPatch(patch);
+
+		SiteModel site = (SiteModel) objectStack.peek();
+		site.addFeature(feature);
+		feature.setSiteModel(site);
+
+		objectStack.push(feature);
+
+		if (Tracing.DEBUG_GENERATOR_PARSING)
+			debug("End Processing DefaultFeature Tag: url:" + urlInfo + " type:" + type); //$NON-NLS-1$ //$NON-NLS-2$
+
+	}
+
+	/* 
+	 * process URL info with element text
+	 */
+	private void processInfo(Attributes attributes) {
+		URLEntry inf = new URLEntry();
+		String infoURL = attributes.getValue("url"); //$NON-NLS-1$
+		inf.setURL(infoURL);
+
+		if (Tracing.DEBUG_GENERATOR_PARSING)
+			debug("Processed Info: url:" + infoURL); //$NON-NLS-1$
+
+		objectStack.push(inf);
+	}
+
+	/* 
+	 * process site info
+	 */
+	private void processSite(Attributes attributes) {
+		// create site map
+		SiteModel site = new SiteModel();
+
+		// if URL is specified, it replaces the URL of the site
+		// used to calculate the location of features and archives
+		String siteURL = attributes.getValue("url"); //$NON-NLS-1$
+		if (siteURL != null && !("".equals(siteURL.trim()))) { //$NON-NLS-1$
+			if (!siteURL.endsWith("/") && !siteURL.endsWith(File.separator)) { //$NON-NLS-1$
+				siteURL += "/"; //$NON-NLS-1$
+			}
+			site.setLocationURLString(siteURL);
+		}
+
+		// provide default description URL
+		// If <description> is specified, for the site,  it takes precedence		
+		URLEntry description = new URLEntry();
+		description.setURL(DEFAULT_INFO_URL);
+		site.setDescription(description);
+
+		// verify we can parse the site ...if the site has
+		// a different type throw an exception to force reparsing
+		// with the matching parser
+		String type = attributes.getValue("type"); //$NON-NLS-1$
+		site.setType(type);
+
+		// get mirrors, if any
+		String mirrorsURL = attributes.getValue("mirrorsURL"); //$NON-NLS-1$
+		if (mirrorsURL != null && mirrorsURL.trim().length() > 0) {
+			//			URLEntry[] mirrors = getMirrors(mirrorsURL);
+			//			if (mirrors != null)
+			//				site.setMirrors(mirrors);
+			//			else
+
+			//Since we are parsing the site at p2 generation time and the 
+			//mirrors may change, there is no point doing the mirror expansion now
+			site.setMirrorsURLString(mirrorsURL);
+		}
+
+		String pack200 = attributes.getValue("pack200"); //$NON-NLS-1$
+		if (pack200 != null && new Boolean(pack200).booleanValue()) {
+			site.setSupportsPack200(true);
+		}
+
+		String digestURL = attributes.getValue("digestURL"); //$NON-NLS-1$
+		if (digestURL != null)
+			site.setDigestURLString(digestURL);
+
+		// TODO: Digest locales
+		//			if ((attributes.getValue("availableLocales") != null) && (!attributes.getValue("availableLocales").trim().equals(""))) { //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$
+		//				StringTokenizer locals = new StringTokenizer(attributes.getValue("availableLocales"), ","); //$NON-NLS-1$//$NON-NLS-2$
+		//				String[] availableLocals = new String[locals.countTokens()];
+		//				int i = 0;
+		//				while (locals.hasMoreTokens()) {
+		//					availableLocals[i++] = locals.nextToken();
+		//				}
+		//								extendedSite.setAvailableLocals(availableLocals);
+		//			}
+		//		}
+		//
+		if (attributes.getValue(ASSOCIATE_SITES_URL) != null)
+			site.setAssociateSites(getAssociateSites(attributes.getValue(ASSOCIATE_SITES_URL)));
+
+		objectStack.push(site);
+
+		if (Tracing.DEBUG_GENERATOR_PARSING)
+			debug("End process Site tag: siteURL:" + siteURL + " type:" + type);//$NON-NLS-1$ //$NON-NLS-2$
+
+	}
+
+	/**
+	 * Handle start of element tags
+	 * @see DefaultHandler#startElement(String, String, String, Attributes)
+	 * @since 2.0
+	 */
+	public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
+
+		if (Tracing.DEBUG_GENERATOR_PARSING) {
+			debug("State: " + currentState); //$NON-NLS-1$
+			debug("Start Element: uri:" + uri + " local Name:" + localName + " qName:" + qName);//$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+		}
+
+		switch (currentState) {
+			case STATE_IGNORED_ELEMENT :
+				internalErrorUnknownTag(NLS.bind(Messages.DefaultSiteParser_UnknownElement, (new String[] {localName, getState(currentState)})));
+				break;
+			case STATE_INITIAL :
+				handleInitialState(localName, attributes);
+				break;
+
+			case STATE_SITE :
+				handleSiteState(localName, attributes);
+				break;
+
+			case STATE_FEATURE :
+				handleFeatureState(localName, attributes);
+				break;
+
+			case STATE_ARCHIVE :
+				handleSiteState(localName, attributes);
+				break;
+
+			case STATE_CATEGORY :
+				handleCategoryState(localName, attributes);
+				break;
+
+			case STATE_CATEGORY_DEF :
+				handleCategoryDefState(localName, attributes);
+				break;
+
+			case STATE_DESCRIPTION_SITE :
+				handleSiteState(localName, attributes);
+				break;
+
+			case STATE_DESCRIPTION_CATEGORY_DEF :
+				handleSiteState(localName, attributes);
+				break;
+
+			default :
+				internalErrorUnknownTag(NLS.bind(Messages.DefaultSiteParser_UnknownStartState, (new String[] {getState(currentState)})));
+				break;
+		}
+		int newState = ((Integer) stateStack.peek()).intValue();
+		if (newState != STATE_IGNORED_ELEMENT)
+			currentState = newState;
+
+	}
+
+	private boolean trailingSpace(String str) {
+		if (str.length() <= 0) {
+			return false;
+		}
+		return Character.isWhitespace(str.charAt(str.length() - 1));
+	}
+
+	// Add translatable strings from the site.xml
+	// to the list of message keys.
+	private void checkTranslated(String value) {
+		if (value != null && value.length() > 1 && value.startsWith("%")) //$NON-NLS-1$
+			messageKeys.add(value.substring(1));
+	}
+}
diff --git a/bundles/org.eclipse.equinox.p2.updatesite/src/org/eclipse/equinox/internal/p2/updatesite/DigestParser.java b/bundles/org.eclipse.equinox.p2.updatesite/src/org/eclipse/equinox/internal/p2/updatesite/DigestParser.java
new file mode 100644
index 0000000..cbe9fea
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.updatesite/src/org/eclipse/equinox/internal/p2/updatesite/DigestParser.java
@@ -0,0 +1,111 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 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.updatesite;
+
+import org.eclipse.equinox.internal.p2.publisher.eclipse.FeatureParser;
+
+import java.io.*;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import javax.xml.parsers.*;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.equinox.internal.p2.core.helpers.LogHelper;
+import org.eclipse.equinox.p2.publisher.eclipse.Feature;
+import org.eclipse.osgi.util.NLS;
+import org.xml.sax.*;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * Default feature parser.
+ * Parses the feature manifest file as defined by the platform.
+ * 
+ * @since 3.0
+ */
+public class DigestParser extends DefaultHandler {
+
+	private final static SAXParserFactory parserFactory = SAXParserFactory.newInstance();
+	private SAXParser parser;
+	private final List features = new ArrayList();
+	private final FeatureParser featureHandler = new FeatureParser(false);
+
+	public DigestParser() {
+		super();
+		try {
+			parserFactory.setNamespaceAware(true);
+			this.parser = parserFactory.newSAXParser();
+		} catch (ParserConfigurationException e) {
+			System.out.println(e);
+		} catch (SAXException e) {
+			System.out.println(e);
+		}
+	}
+
+	public void characters(char[] ch, int start, int length) throws SAXException {
+		featureHandler.characters(ch, start, length);
+	}
+
+	public void endElement(String uri, String localName, String qName) throws SAXException {
+		if ("digest".equals(localName)) { //$NON-NLS-1$
+			return;
+		}
+		if ("feature".equals(localName)) { //$NON-NLS-1$
+			Feature feature = featureHandler.getResult();
+			features.add(feature);
+		} else
+			featureHandler.endElement(uri, localName, qName);
+	}
+
+	public Feature[] parse(File location) {
+		if (!location.exists())
+			return null;
+
+		JarFile jar = null;
+		InputStream is = null;
+		try {
+			jar = new JarFile(location);
+			JarEntry entry = jar.getJarEntry("digest.xml"); //$NON-NLS-1$
+			if (entry == null)
+				return null;
+			is = new BufferedInputStream(jar.getInputStream(entry));
+			parser.parse(new InputSource(is), this);
+			return (Feature[]) features.toArray(new Feature[features.size()]);
+		} catch (IOException e) {
+			LogHelper.log(new Status(IStatus.ERROR, Activator.ID, NLS.bind(Messages.ErrorReadingDigest, location), e));
+		} catch (SAXException e) {
+			LogHelper.log(new Status(IStatus.ERROR, Activator.ID, NLS.bind(Messages.ErrorReadingDigest, location), e));
+		} finally {
+			try {
+				if (is != null)
+					is.close();
+			} catch (IOException e1) {
+				//
+			}
+			try {
+				if (jar != null)
+					jar.close();
+			} catch (IOException e) {
+				//
+			}
+		}
+		return null;
+	}
+
+	public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
+		if ("digest".equals(localName)) { //$NON-NLS-1$
+			return;
+		}
+		featureHandler.startElement(uri, localName, qName, attributes);
+	}
+
+}
diff --git a/bundles/org.eclipse.equinox.p2.updatesite/src/org/eclipse/equinox/internal/p2/updatesite/LocalUpdateSiteAction.java b/bundles/org.eclipse.equinox.p2.updatesite/src/org/eclipse/equinox/internal/p2/updatesite/LocalUpdateSiteAction.java
new file mode 100644
index 0000000..dc1ae33
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.updatesite/src/org/eclipse/equinox/internal/p2/updatesite/LocalUpdateSiteAction.java
@@ -0,0 +1,84 @@
+/*******************************************************************************
+ * Copyright (c) 2008 Code 9 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: 
+ *   Code 9 - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.equinox.internal.p2.updatesite;
+
+import org.eclipse.equinox.p2.publisher.actions.MergeResultsAction;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.util.ArrayList;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.equinox.p2.publisher.*;
+import org.eclipse.equinox.p2.publisher.eclipse.BundlesAction;
+import org.eclipse.equinox.p2.publisher.eclipse.FeaturesAction;
+
+/**
+ * A publishing action that processes a local (File-based) update site and generates
+ * metadata and artifacts for the features, bundles and site index (categories etc).
+ */
+public class LocalUpdateSiteAction implements IPublisherAction {
+	protected String source;
+	private UpdateSite updateSite;
+
+	protected LocalUpdateSiteAction() {
+	}
+
+	public LocalUpdateSiteAction(String source) {
+		this.source = source;
+	}
+
+	public LocalUpdateSiteAction(UpdateSite updateSite) {
+		this.updateSite = updateSite;
+	}
+
+	public IStatus perform(IPublisherInfo info, IPublisherResult results) {
+		IPublisherAction[] actions = createActions();
+		for (int i = 0; i < actions.length; i++)
+			actions[i].perform(info, results);
+		return Status.OK_STATUS;
+	}
+
+	protected IPublisherAction[] createActions() {
+		createAdvice();
+		ArrayList result = new ArrayList();
+		// create an action that just publishes the raw bundles and features
+		IPublisherAction action = new MergeResultsAction(new IPublisherAction[] {createFeaturesAction(), createBundlesAction()}, IPublisherResult.MERGE_ALL_NON_ROOT);
+		result.add(action);
+		result.add(createSiteXMLAction());
+		return (IPublisherAction[]) result.toArray(new IPublisherAction[result.size()]);
+	}
+
+	private IPublisherAction createSiteXMLAction() {
+		if (updateSite != null)
+			return new SiteXMLAction(updateSite);
+		if (source != null) {
+			try {
+				return new SiteXMLAction(new File(source, "site.xml").toURL()); //$NON-NLS-1$
+			} catch (MalformedURLException e) {
+				// never happens
+				return null;
+			}
+		}
+		return null;
+	}
+
+	private void createAdvice() {
+	}
+
+	protected IPublisherAction createFeaturesAction() {
+		return new FeaturesAction(new File[] {new File(source, "features")}); //$NON-NLS-1$
+	}
+
+	protected IPublisherAction createBundlesAction() {
+		return new BundlesAction(new File[] {new File(source, "plugins")}); //$NON-NLS-1$
+	}
+
+}
diff --git a/bundles/org.eclipse.equinox.p2.updatesite/src/org/eclipse/equinox/internal/p2/updatesite/Messages.java b/bundles/org.eclipse.equinox.p2.updatesite/src/org/eclipse/equinox/internal/p2/updatesite/Messages.java
index a32485c..2ba0c6a 100644
--- a/bundles/org.eclipse.equinox.p2.updatesite/src/org/eclipse/equinox/internal/p2/updatesite/Messages.java
+++ b/bundles/org.eclipse.equinox.p2.updatesite/src/org/eclipse/equinox/internal/p2/updatesite/Messages.java
@@ -32,6 +32,23 @@
 
 	public static String repoMan_internalError;
 
+	public static String DefaultFeatureParser_IdOrVersionInvalid;
+	public static String DefaultSiteParser_NoSiteTag;
+	public static String DefaultSiteParser_WrongParsingStack;
+	public static String DefaultSiteParser_UnknownElement;
+	public static String DefaultSiteParser_UnknownStartState;
+	public static String DefaultSiteParser_Missing;
+	public static String DefaultSiteParser_ParsingStackBackToInitialState;
+	public static String DefaultSiteParser_ElementAlreadySet;
+	public static String DefaultSiteParser_CategoryAlreadySet;
+	public static String DefaultSiteParser_UnknownEndState;
+	public static String DefaultSiteParser_ErrorParsing;
+	public static String DefaultSiteParser_ErrorlineColumnMessage;
+	public static String DefaultSiteParser_ErrorParsingSite;
+	public static String DefaultSiteParser_UnknownState;
+	public static String DefaultSiteParser_InvalidXMLStream;
+	public static String DefaultSiteParser_mirrors;
+
 	static {
 		// initialize resource bundle
 		NLS.initializeMessages(BUNDLE_NAME, Messages.class);
diff --git a/bundles/org.eclipse.equinox.p2.updatesite/src/org/eclipse/equinox/internal/p2/updatesite/RemoteFeaturesAction.java b/bundles/org.eclipse.equinox.p2.updatesite/src/org/eclipse/equinox/internal/p2/updatesite/RemoteFeaturesAction.java
new file mode 100644
index 0000000..3d1fc52
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.updatesite/src/org/eclipse/equinox/internal/p2/updatesite/RemoteFeaturesAction.java
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * Copyright (c) 2008 Code 9 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: 
+ *   Code 9 - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.equinox.internal.p2.updatesite;
+
+import java.util.Dictionary;
+import java.util.Properties;
+import org.eclipse.equinox.internal.provisional.p2.metadata.IArtifactKey;
+import org.eclipse.equinox.internal.provisional.p2.metadata.IInstallableUnit;
+import org.eclipse.equinox.p2.publisher.IPublisherInfo;
+import org.eclipse.equinox.p2.publisher.IPublisherResult;
+import org.eclipse.equinox.p2.publisher.eclipse.*;
+import org.eclipse.equinox.spi.p2.publisher.PublisherHelper;
+import org.eclipse.osgi.service.resolver.BundleDescription;
+
+public class RemoteFeaturesAction extends FeaturesAction {
+
+	public RemoteFeaturesAction(Feature[] features) {
+		super(features);
+	}
+
+	protected void generateFeatureIUs(Feature[] features, IPublisherResult result, IPublisherInfo info) {
+		Properties extraProperties = new Properties();
+		extraProperties.put(IInstallableUnit.PROP_PARTIAL_IU, Boolean.TRUE.toString());
+		for (int i = 0; i < features.length; i++) {
+			Feature feature = features[i];
+			FeatureEntry[] featureEntries = feature.getEntries();
+			for (int j = 0; j < featureEntries.length; j++) {
+				FeatureEntry entry = featureEntries[j];
+				if (entry.isPlugin() && !entry.isRequires()) {
+					Dictionary mockManifest = new Properties();
+					mockManifest.put("Manifest-Version", "1.0"); //$NON-NLS-1$ //$NON-NLS-2$
+					mockManifest.put("Bundle-ManifestVersion", "2"); //$NON-NLS-1$ //$NON-NLS-2$
+					mockManifest.put("Bundle-SymbolicName", entry.getId()); //$NON-NLS-1$
+					mockManifest.put("Bundle-Version", entry.getVersion()); //$NON-NLS-1$
+					BundleDescription bundleDescription = BundlesAction.createBundleDescription(mockManifest, null);
+					IArtifactKey key = BundlesAction.createBundleArtifactKey(entry.getId(), entry.getVersion());
+					IInstallableUnit[] bundleIUs = PublisherHelper.createEclipseIU(bundleDescription, null, entry.isUnpack(), key, extraProperties);
+					for (int n = 0; n < bundleIUs.length; n++)
+						result.addIU(bundleIUs[n], IPublisherResult.ROOT);
+				}
+			}
+			IInstallableUnit featureIU = createFeatureJarIU(feature, null, null);
+			IInstallableUnit groupIU = createGroupIU(feature, featureIU, null);
+			result.addIU(featureIU, IPublisherResult.ROOT);
+			result.addIU(groupIU, IPublisherResult.ROOT);
+		}
+	}
+}
diff --git a/bundles/org.eclipse.equinox.p2.updatesite/src/org/eclipse/equinox/internal/p2/updatesite/RemoteUpdateSiteAction.java b/bundles/org.eclipse.equinox.p2.updatesite/src/org/eclipse/equinox/internal/p2/updatesite/RemoteUpdateSiteAction.java
new file mode 100644
index 0000000..d53c747
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.updatesite/src/org/eclipse/equinox/internal/p2/updatesite/RemoteUpdateSiteAction.java
@@ -0,0 +1,70 @@
+/*******************************************************************************
+ * Copyright (c) 2008 Code 9 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: 
+ *   Code 9 - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.equinox.internal.p2.updatesite;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.equinox.internal.provisional.p2.core.ProvisionException;
+import org.eclipse.equinox.p2.publisher.*;
+
+/**
+ * A publishing action that processes a remote (URL-based) update site and generates
+ * metadata and artifacts for the features, bundles and site index (categories etc).  The 
+ * IUs generated for the bundles are "partial" as the bundles themselves are not downloaded.
+ */
+public class RemoteUpdateSiteAction implements IPublisherAction {
+	protected String source;
+	private UpdateSite updateSite;
+
+	public RemoteUpdateSiteAction(UpdateSite updateSite) {
+		this.updateSite = updateSite;
+	}
+
+	public RemoteUpdateSiteAction(String source) {
+		this.source = source;
+	}
+
+	public IStatus perform(IPublisherInfo info, IPublisherResult results) {
+		IPublisherAction[] actions = createActions();
+		for (int i = 0; i < actions.length; i++)
+			actions[i].perform(info, results);
+		return Status.OK_STATUS;
+	}
+
+	protected IPublisherAction[] createActions() {
+		try {
+			ArrayList result = new ArrayList();
+			result.add(new RemoteFeaturesAction(updateSite.loadFeatures()));
+			result.add(createSiteXMLAction());
+			return (IPublisherAction[]) result.toArray(new IPublisherAction[result.size()]);
+		} catch (ProvisionException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		}
+		return new IPublisherAction[0];
+	}
+
+	private IPublisherAction createSiteXMLAction() {
+		if (updateSite != null)
+			return new SiteXMLAction(updateSite);
+		if (source != null) {
+			try {
+				return new SiteXMLAction(new URL(source + "/site.xml")); //$NON-NLS-1$
+			} catch (MalformedURLException e) {
+				// never happens
+				return null;
+			}
+		}
+		return null;
+	}
+}
diff --git a/bundles/org.eclipse.equinox.p2.updatesite/src/org/eclipse/equinox/internal/p2/updatesite/SiteCategory.java b/bundles/org.eclipse.equinox.p2.updatesite/src/org/eclipse/equinox/internal/p2/updatesite/SiteCategory.java
new file mode 100644
index 0000000..d78efa1
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.updatesite/src/org/eclipse/equinox/internal/p2/updatesite/SiteCategory.java
@@ -0,0 +1,199 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2007 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.updatesite;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Comparator;
+import java.util.Map;
+
+/**
+ * A category in an update site.
+ * 
+ * Based on org.eclipse.update.core.model.CategoryModel.
+ */
+public class SiteCategory {
+
+	private static Comparator comp;
+	private String description;
+	private String label;
+	private String name;
+	private Map localizations;
+
+	/**
+	 * Returns a comparator for category models.
+	 * 
+	 * @return comparator
+	 * @since 2.0
+	 */
+	public static Comparator getComparator() {
+		if (comp == null) {
+			comp = new Comparator() {
+				/*
+				 * @see Comparator#compare(Object,Object)
+				 * Returns 0 if versions are equal.
+				 * Returns -1 if object1 is after than object2.
+				 * Returns +1 if object1 is before than object2.
+				 */
+				public int compare(Object o1, Object o2) {
+
+					SiteCategory cat1 = (SiteCategory) o1;
+					SiteCategory cat2 = (SiteCategory) o2;
+
+					if (cat1.equals(cat2))
+						return 0;
+					return cat1.getName().compareTo(cat2.getName());
+				}
+			};
+		}
+		return comp;
+	}
+
+	/**
+	 * Creates an uninitialized model object.
+	 * 
+	 * @since 2.0
+	 */
+	public SiteCategory() {
+		super();
+	}
+
+	/**
+	 * Compare two category models for equality.
+	 * 
+	 * @see Object#equals(Object)
+	 * @since 2.0
+	 */
+	public boolean equals(Object obj) {
+		boolean result = false;
+		if (obj instanceof SiteCategory) {
+			SiteCategory otherCategory = (SiteCategory) obj;
+			result = getName().equalsIgnoreCase(otherCategory.getName());
+		}
+		return result;
+	}
+
+	/**
+	 * Retrieve the detailed category description
+	 * 
+	 * @return category description, or <code>null</code>.
+	 * @since 2.0
+	 */
+	public String getDescription() {
+		return description;
+	}
+
+	/**
+	 * Retrieve the non-localized displayable label for the category.
+	 * 
+	 * @return non-localized displayable label, or <code>null</code>.
+	 * @since 2.0
+	 */
+	public String getLabel() {
+		return label;
+	}
+
+	/**
+	 * Gets the localizations for the site as a map from locale
+	 * to the set of translated properties for that locale.
+	 * 
+	 * @return a map from locale to property set
+	 * @since 3.4
+	 */
+	public Map getLocalizations() {
+		return this.localizations;
+	}
+
+	/**
+	 * Retrieve the name of the category.
+	 * 
+	 * @return category name, or <code>null</code>.
+	 * @since 2.0
+	 */
+	public String getName() {
+		return name;
+	}
+
+	/**
+	 * Compute hash code for category model.
+	 * 
+	 * @see Object#hashCode()
+	 * @since 2.0
+	 */
+	public int hashCode() {
+		return getName().hashCode();
+	}
+
+	/**
+	 * Resolve the model object.
+	 * Any URL strings in the model are resolved relative to the 
+	 * base URL argument. Any translatable strings in the model that are
+	 * specified as translation keys are localized using the supplied 
+	 * resource bundle.
+	 * 
+	 * @param base URL
+	 * @param bundleURL resource bundle URL
+	 * @exception MalformedURLException
+	 * @since 2.0
+	 */
+	public void resolve(URL base, URL bundleURL) throws MalformedURLException {
+		// resolve local elements
+		//		localizedLabel = resolveNLString(bundleURL, label);
+
+		// delegate to references
+		//		resolveReference(getDescriptionModel(), base, bundleURL);
+	}
+
+	/**
+	 * Sets the category description.
+	 * Throws a runtime exception if this object is marked read-only.
+	 * 
+	 * @param description category description
+	 * @since 2.0
+	 */
+	public void setDescription(String description) {
+		this.description = description;
+	}
+
+	/**
+	 * Sets the category displayable label.
+	 * Throws a runtime exception if this object is marked read-only.
+	 * 
+	 * @param label displayable label, or resource key
+	 * @since 2.0
+	 */
+	public void setLabel(String label) {
+		this.label = label;
+	}
+
+	/**
+	 * Sets the localizations for the site as a map from locale
+	 * to the set of translated properties for that locale.
+	 * 
+	 * @param localizations as a map from locale to property set
+	 * @since 3.4
+	 */
+	public void setLocalizations(Map localizations) {
+		this.localizations = localizations;
+	}
+
+	/**
+	 * Sets the category name.
+	 * Throws a runtime exception if this object is marked read-only.
+	 * 
+	 * @param name category name
+	 * @since 2.0
+	 */
+	public void setName(String name) {
+		this.name = name;
+	}
+
+}
diff --git a/bundles/org.eclipse.equinox.p2.updatesite/src/org/eclipse/equinox/internal/p2/updatesite/SiteFeature.java b/bundles/org.eclipse.equinox.p2.updatesite/src/org/eclipse/equinox/internal/p2/updatesite/SiteFeature.java
new file mode 100644
index 0000000..626e4b1
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.updatesite/src/org/eclipse/equinox/internal/p2/updatesite/SiteFeature.java
@@ -0,0 +1,408 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2007 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
+ *     James D Miles (IBM Corp.) - bug 191783, NullPointerException in FeatureDownloader
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.updatesite;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.*;
+
+/**
+ * A reference to a feature in an update site.xml file.
+ * 
+ * Based on org.eclipse.update.core.model.FeatureReferenceModel.
+ */
+public class SiteFeature {
+
+	private String arch;
+	// performance
+	private URL base;
+	private List /* of String*/categoryNames;
+	private String featureId;
+	private String featureVersion;
+	private String label;
+	private String nl;
+
+	private String os;
+	private String patch;
+	private final boolean resolved = false;
+	private SiteModel site;
+	private String type;
+	private URL url;
+	private String urlString;
+	private String ws;
+
+	/*
+	 * Compares two URL for equality
+	 * Return false if one of them is null
+	 */
+	public static boolean sameURL(URL url1, URL url2) {
+
+		if (url1 == null || url2 == null)
+			return false;
+		if (url1 == url2)
+			return true;
+		if (url1.equals(url2))
+			return true;
+
+		// check if URL are file: URL as we may
+		// have 2 URL pointing to the same featureReference
+		// but with different representation
+		// (i.e. file:/C;/ and file:C:/)
+		if (!"file".equalsIgnoreCase(url1.getProtocol())) //$NON-NLS-1$
+			return false;
+		if (!"file".equalsIgnoreCase(url2.getProtocol())) //$NON-NLS-1$
+			return false;
+
+		File file1 = new File(url1.getFile());
+		File file2 = new File(url2.getFile());
+
+		if (file1 == null)
+			return false;
+
+		return (file1.equals(file2));
+	}
+
+	/**
+	 * Creates an uninitialized feature reference model object.
+	 */
+	public SiteFeature() {
+		super();
+	}
+
+	/**
+	 * Adds the name of a category this feature belongs to.
+	 * Throws a runtime exception if this object is marked read-only.
+	 * 
+	 * @param categoryName category name
+	 */
+	public void addCategoryName(String categoryName) {
+		if (this.categoryNames == null)
+			this.categoryNames = new ArrayList();
+		if (!this.categoryNames.contains(categoryName))
+			this.categoryNames.add(categoryName);
+	}
+
+	private void delayedResolve() {
+
+		// PERF: delay resolution
+		if (resolved)
+			return;
+
+		// resolve local elements
+		try {
+			url = new URL(base, urlString);
+		} catch (MalformedURLException e) {
+			//			UpdateCore.warn("", e); //$NON-NLS-1$
+		}
+	}
+
+	/**
+	 * Compares 2 feature reference models for equality
+	 *  
+	 * @param object feature reference model to compare with
+	 * @return <code>true</code> if the two models are equal, 
+	 * <code>false</code> otherwise
+	 */
+	public boolean equals(Object object) {
+
+		if (object == null)
+			return false;
+		if (getURL() == null)
+			return false;
+
+		if (!(object instanceof SiteFeature))
+			return false;
+
+		SiteFeature f = (SiteFeature) object;
+
+		return sameURL(getURL(), f.getURL());
+	}
+
+	/**
+	 * Returns the names of categories the referenced feature belongs to.
+	 * 
+	 * @return an array of names, or an empty array.
+	 */
+	public String[] getCategoryNames() {
+		if (categoryNames == null)
+			return new String[0];
+
+		return (String[]) categoryNames.toArray(new String[0]);
+	}
+
+	/**
+	 * Returns the feature identifier as a string
+	 * 
+	 * @return feature identifier
+	 */
+	public String getFeatureIdentifier() {
+		return featureId;
+	}
+
+	/**
+	 * Returns the feature version as a string
+	 * 
+	 * @return feature version 
+	 */
+	public String getFeatureVersion() {
+		return featureVersion;
+	}
+
+	/**
+	 * Retrieve the displayable label for the feature reference. If the model
+	 * object has been resolved, the label is localized.
+	 *
+	 * @return displayable label, or <code>null</code>.
+	 */
+	public String getLabel() {
+		return label;
+	}
+
+	/**
+	 * Retrieve the non-localized displayable label for the feature reference.
+	 *
+	 * @return non-localized displayable label, or <code>null</code>.
+	 */
+	public String getLabelNonLocalized() {
+		return label;
+	}
+
+	/**
+	 * Get optional locale specification as a comma-separated string.
+	 *
+	 * @return the locale specification string, or <code>null</code>.
+	 */
+	public String getNL() {
+		return nl;
+	}
+
+	/**
+	 * Get optional operating system specification as a comma-separated string.
+	 *
+	 * @return the operating system specification string, or <code>null</code>.
+	 */
+	public String getOS() {
+		return os;
+	}
+
+	/**
+	 * Get optional system architecture specification as a comma-separated string.
+	 *
+	 * @return the system architecture specification string, or <code>null</code>.
+	 */
+	public String getOSArch() {
+		return arch;
+	}
+
+	/**
+	 * Returns the patch mode.
+	 */
+	public String getPatch() {
+		return patch;
+	}
+
+	/**
+	 * Returns the site model for the reference.
+	 * 
+	 * @return site model
+	 * @since 2.0
+	 */
+	public SiteModel getSiteModel() {
+		return site;
+	}
+
+	/**
+	 * Returns the referenced feature type.
+	 * 
+	 * @return feature type, or <code>null</code> representing the default
+	 * feature type for the site
+	 */
+	public String getType() {
+		return type;
+	}
+
+	/**
+	 * Returns the resolved URL for the feature reference.
+	 * 
+	 * @return url string
+	 */
+	public URL getURL() {
+		delayedResolve();
+		return url;
+	}
+
+	/**
+	 * Returns the unresolved URL string for the reference.
+	 *
+	 * @return url string
+	 */
+	public String getURLString() {
+		return urlString;
+	}
+
+	/**
+	 * Get optional windowing system specification as a comma-separated string.
+	 *
+	 * @return the windowing system specification string, or <code>null</code>.
+	 */
+	public String getWS() {
+		return ws;
+	}
+
+	/**
+	 * Resolve the model object.
+	 * Any URL strings in the model are resolved relative to the 
+	 * base URL argument. Any translatable strings in the model that are
+	 * specified as translation keys are localized using the supplied 
+	 * resource bundle.
+	 * 
+	 * @param resolveBase URL
+	 * @param bundleURL resource bundle URL
+	 * @exception MalformedURLException
+	 */
+	public void resolve(URL resolveBase, URL bundleURL) throws MalformedURLException {
+		this.base = resolveBase;
+	}
+
+	/**
+	 * Sets the system architecture specification.
+	 * Throws a runtime exception if this object is marked read-only.
+	 *
+	 * @param arch system architecture specification as a comma-separated list
+	 */
+	public void setArch(String arch) {
+		this.arch = arch;
+	}
+
+	/**
+	 * Sets the names of categories this feature belongs to.
+	 * Throws a runtime exception if this object is marked read-only.
+	 * 
+	 * @param categoryNames an array of category names
+	 */
+	public void setCategoryNames(String[] categoryNames) {
+		if (categoryNames == null)
+			this.categoryNames = null;
+		else
+			this.categoryNames = new ArrayList(Arrays.asList(categoryNames));
+	}
+
+	/**
+	 * Sets the feature identifier.
+	 * Throws a runtime exception if this object is marked read-only.
+	 * 
+	 * @param featureId feature identifier
+	 */
+	public void setFeatureIdentifier(String featureId) {
+		this.featureId = featureId;
+	}
+
+	/**
+	 * Sets the feature version.
+	 * Throws a runtime exception if this object is marked read-only.
+	 * 
+	 * @param featureVersion feature version
+	 */
+	public void setFeatureVersion(String featureVersion) {
+		this.featureVersion = featureVersion;
+	}
+
+	/**
+	 * Sets the label.
+	 * @param label The label to set
+	 */
+	public void setLabel(String label) {
+		this.label = label;
+	}
+
+	/**
+	 * Sets the locale specification.
+	 * Throws a runtime exception if this object is marked read-only.
+	 *
+	 * @param nl locale specification as a comma-separated list
+	 */
+	public void setNL(String nl) {
+		this.nl = nl;
+	}
+
+	/**
+	 * Sets the operating system specification.
+	 * Throws a runtime exception if this object is marked read-only.
+	 *
+	 * @param os operating system specification as a comma-separated list
+	 */
+	public void setOS(String os) {
+		this.os = os;
+	}
+
+	/**
+	 * Sets the patch mode.
+	 */
+	public void setPatch(String patch) {
+		this.patch = patch;
+	}
+
+	/**
+	 * Sets the site for the referenced.
+	 * Throws a runtime exception if this object is marked read-only.
+	 * 
+	 * @param site site for the reference
+	 */
+	public void setSiteModel(SiteModel site) {
+		this.site = site;
+	}
+
+	/**
+	 * Sets the referenced feature type.
+	 * Throws a runtime exception if this object is marked read-only.
+	 * 
+	 * @param type referenced feature type
+	 */
+	public void setType(String type) {
+		this.type = type;
+	}
+
+	/**
+	 * Sets the unresolved URL for the feature reference.
+	 * Throws a runtime exception if this object is marked read-only.
+	 * 
+	 * @param urlString unresolved URL string
+	 */
+	public void setURLString(String urlString) {
+		this.urlString = urlString;
+		this.url = null;
+	}
+
+	/**
+	 * Sets the windowing system specification.
+	 * Throws a runtime exception if this object is marked read-only.
+	 *
+	 * @param ws windowing system specification as a comma-separated list
+	 */
+	public void setWS(String ws) {
+		this.ws = ws;
+	}
+
+	/**
+	 * @see Object#toString()
+	 */
+	public String toString() {
+		StringBuffer buffer = new StringBuffer();
+		buffer.append(getClass().toString() + " :"); //$NON-NLS-1$
+		buffer.append(" at "); //$NON-NLS-1$
+		if (url != null)
+			buffer.append(url.toExternalForm());
+		return buffer.toString();
+	}
+
+}
diff --git a/bundles/org.eclipse.equinox.p2.updatesite/src/org/eclipse/equinox/internal/p2/updatesite/SiteModel.java b/bundles/org.eclipse.equinox.p2.updatesite/src/org/eclipse/equinox/internal/p2/updatesite/SiteModel.java
new file mode 100644
index 0000000..3db8a96
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.updatesite/src/org/eclipse/equinox/internal/p2/updatesite/SiteModel.java
@@ -0,0 +1,399 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 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.updatesite;
+
+import org.eclipse.equinox.p2.publisher.eclipse.URLEntry;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.*;
+
+/**
+ * A model of an update site.
+ * 
+ * Copied from org.eclipse.update.core.model.SiteModel.
+ */
+public class SiteModel {
+
+	private List /*of ArchiveReferenceModel*/archiveReferences;
+	/**
+	 * Map of String (category id) -> SiteCategory
+	 */
+	private Map categories;
+	private URLEntry description;
+	/**
+	 * Map of String (feature id) -> SiteFeature
+	 */
+	private List features;
+	private URL locationURL;
+	private String locationURLString;
+	private List /* of URLEntry */mirrors;
+	private String mirrorsURLString;
+	private boolean supportsPack200;
+	private String type;
+	private URLEntry[] associateSites;
+	private String digestURLString;
+	private List messageKeys;
+	private Map localizations;
+
+	/**
+	 * Creates an uninitialized site model object.
+	 * 
+	 * @since 2.0
+	 */
+	public SiteModel() {
+		super();
+	}
+
+	/**
+	 * Adds an archive reference model to site.
+	 * Throws a runtime exception if this object is marked read-only.
+	 * 
+	 * @param archiveReference archive reference model
+	 * @since 2.0
+	 */
+	public void addArchive(URLEntry archiveReference) {
+		if (this.archiveReferences == null)
+			this.archiveReferences = new ArrayList();
+		if (!this.archiveReferences.contains(archiveReference))
+			this.archiveReferences.add(archiveReference);
+	}
+
+	/**
+	 * Adds a category to the site.
+	 * 
+	 * @param category category model
+	 */
+	public void addCategory(SiteCategory category) {
+		if (categories == null)
+			categories = new HashMap();
+		if (!categories.containsKey(category.getName())) {
+			categories.put(category.getName(), category);
+			if (localizations != null && !localizations.isEmpty())
+				category.setLocalizations(localizations);
+		}
+	}
+
+	/**
+	 * Adds a feature reference model to site.
+	 * 
+	 * @param featureReference feature reference model
+	 */
+	public void addFeature(SiteFeature featureReference) {
+		if (this.features == null)
+			this.features = new ArrayList();
+		this.features.add(featureReference);
+	}
+
+	/**
+	 * Adds a mirror site.
+	 * 
+	 * @param mirror mirror model 
+	 * @since 3.1
+	 */
+	public void addMirror(URLEntry mirror) {
+		if (this.mirrors == null)
+			this.mirrors = new ArrayList();
+		if (!this.mirrors.contains(mirror))
+			this.mirrors.add(mirror);
+	}
+
+	private void doSetMirrorSiteEntryModels(URLEntry[] newMirrors) {
+		if (newMirrors == null || newMirrors.length == 0)
+			this.mirrors = null;
+		else
+			this.mirrors = new ArrayList(Arrays.asList(newMirrors));
+	}
+
+	/**
+	 * Returns an array of plug-in and non-plug-in archive reference models
+	 * on this site
+	 * 
+	 * @return an array of archive reference models, or an empty array if there are
+	 * no archives known to this site.
+	 * @since 2.0
+	 */
+	public URLEntry[] getArchives() {
+		if (archiveReferences == null || archiveReferences.size() == 0)
+			return new URLEntry[0];
+
+		return (URLEntry[]) archiveReferences.toArray(new URLEntry[0]);
+	}
+
+	public URLEntry[] getAssociatedSites() {
+		return associateSites;
+	}
+
+	/**
+	 * Returns an array of category models for this site.
+	 * 
+	 * @return array of site category models, or an empty array.
+	 * @since 2.0
+	 */
+	public SiteCategory[] getCategories() {
+		if (categories == null || categories.size() == 0)
+			return new SiteCategory[0];
+		return (SiteCategory[]) categories.values().toArray(new SiteCategory[0]);
+	}
+
+	/**
+	 * Returns the category with the given name.
+	 * @return the category with the given name, or <code>null</code>
+	 */
+	public SiteCategory getCategory(String name) {
+		return (SiteCategory) (categories == null ? null : categories.get(name));
+	}
+
+	/**
+	 * Returns the site description.
+	 * 
+	 * @return site description, or <code>null</code>.
+	 */
+	public URLEntry getDescription() {
+		return description;
+	}
+
+	/**
+	 * Returns an array of feature reference models on this site.
+	 * 
+	 * @return an array of feature reference models, or an empty array.
+	 */
+	public SiteFeature[] getFeatures() {
+		if (features == null || features.size() == 0)
+			return new SiteFeature[0];
+		return (SiteFeature[]) features.toArray(new SiteFeature[0]);
+	}
+
+	/**
+	 * Gets the localizations for the site as a map from locale
+	 * to the set of translated properties for that locale.
+	 * 
+	 * @return a map from locale to property set
+	 * @since 3.4
+	 */
+	public Map getLocalizations() {
+		return this.localizations;
+	}
+
+	/**
+	 * Returns the resolved URL for the site.
+	 * 
+	 * @return url, or <code>null</code>
+	 */
+	public URL getLocationURL() {
+		if (locationURL == null && locationURLString != null) {
+			try {
+				locationURL = new URL(locationURLString);
+			} catch (MalformedURLException e) {
+				//ignore and return null
+			}
+		}
+		return locationURL;
+	}
+
+	/**
+	 * Returns the unresolved URL string for the site.
+	 *
+	 * @return url string, or <code>null</code>
+	 */
+	public String getLocationURLString() {
+		return locationURLString;
+	}
+
+	/**
+	 * Return the keys for translatable strings
+	 *
+	 * @return the list of keys for translatable strings; may be null
+	 * @since 3.4
+	 */
+	public List getMessageKeys() {
+		return messageKeys;
+	}
+
+	/**
+	 * Return an array of update site mirrors
+	 * 
+	 * @return an array of mirror entries, or an empty array.
+	 * @since 3.1
+	 */
+	public URLEntry[] getMirrors() {
+		//delayedResolve(); no delay;
+		if (mirrors == null || mirrors.size() == 0)
+			// see if we can get mirrors from the provided url
+			if (mirrorsURLString != null)
+				doSetMirrorSiteEntryModels(DefaultSiteParser.getMirrors(mirrorsURLString));
+
+		if (mirrors == null || mirrors.size() == 0)
+			return new URLEntry[0];
+		return (URLEntry[]) mirrors.toArray(new URLEntry[0]);
+	}
+
+	/**
+	 * Returns the URL from which the list of mirrors of this site can be retrieved.
+	 * 
+	 * @since org.eclipse.equinox.p2.metadata.generator 1.0
+	 */
+	public String getMirrorsURL() {
+		return mirrorsURLString;
+	}
+
+	/** 
+	 * Returns the site type.
+	 * 
+	 * @return site type, or <code>null</code>.
+	 * @since 2.0
+	 */
+	public String getType() {
+		return type;
+	}
+
+	public boolean isPack200Supported() {
+		return supportsPack200;
+	}
+
+	/**
+	 * Resolve the model object.
+	 * Any URL strings in the model are resolved relative to the 
+	 * base URL argument. Any translatable strings in the model that are
+	 * specified as translation keys are localized using the supplied 
+	 * resource bundle.
+	 * 
+	 * @param base URL
+	 * @param bundleURL resource bundle URL
+	 * @exception MalformedURLException
+	 * @since 2.0
+	 */
+	public void resolve(URL base, URL bundleURL) throws MalformedURLException {
+
+		// Archives and feature are relative to location URL
+		// if the Site element has a URL tag: see spec	
+		//		locationURL = resolveURL(base, bundleURL, getLocationURLString());
+		//		if (locationURL == null)
+		//			locationURL = base;
+		//		resolveListReference(getFeatureReferenceModels(), locationURL, bundleURL);
+		//		resolveListReference(getArchiveReferenceModels(), locationURL, bundleURL);
+		//
+		//		resolveReference(getDescriptionModel(), base, bundleURL);
+		//		resolveListReference(getCategoryModels(), base, bundleURL);
+		//
+		//		URL url = resolveURL(base, bundleURL, mirrorsURLString);
+		//		if (url != null)
+		//			mirrorsURLString = url.toString();
+		//
+		//		if ((this instanceof ExtendedSite) && ((ExtendedSite) this).isDigestExist()) {
+		//			ExtendedSite extendedSite = (ExtendedSite) this;
+		//			extendedSite.setLiteFeatures(UpdateManagerUtils.getLightFeatures(extendedSite));
+		//		}
+	}
+
+	/**
+	 * Sets the site description.
+	 * 
+	 * @param description site description
+	 * @since 2.0
+	 */
+	public void setDescription(URLEntry description) {
+		this.description = description;
+	}
+
+	/**
+	 * Sets the localizations for the site as a map from locale
+	 * to the set of translated properties for that locale.
+	 * 
+	 * @param localizations as a map from locale to property set
+	 * @since 3.4
+	 */
+	public void setLocalizations(Map localizations) {
+		this.localizations = localizations;
+		if (localizations != null && !localizations.isEmpty() && //
+				categories != null && !categories.isEmpty()) {
+			for (Iterator catIter = categories.entrySet().iterator(); catIter.hasNext();) {
+				Map.Entry entry = (Map.Entry) catIter.next();
+				SiteCategory category = (SiteCategory) entry.getValue();
+				category.setLocalizations(localizations);
+			}
+		}
+	}
+
+	/**
+	 * Sets the unresolved URL for the site.
+	 * 
+	 * @param locationURLString url for the site (as a string)
+	 * @since 2.0
+	 */
+	public void setLocationURLString(String locationURLString) {
+		this.locationURLString = locationURLString;
+	}
+
+	/**
+	 * Sets keys for translatable strings
+	 * 
+	 * @param keys for translatable strings
+	 * @since 3.4
+	 */
+	public void setMessageKeys(List keys) {
+		this.messageKeys = keys;
+	}
+
+	/**
+	 * Sets additional mirror sites
+	 * 
+	 * @param mirrors additional update site mirrors
+	 * @since 3.1
+	 */
+	public void setMirrors(URLEntry[] mirrors) {
+		doSetMirrorSiteEntryModels(mirrors);
+	}
+
+	/**
+	 * Sets the mirrors url. Mirror sites will then be obtained from this mirror url later.
+	 * This method is complementary to setMirrorsiteEntryModels(), and only one of these 
+	 * methods should be called.
+	 * 
+	 * @param mirrorsURL additional update site mirrors
+	 * @since 3.1
+	 */
+	public void setMirrorsURLString(String mirrorsURL) {
+		this.mirrorsURLString = mirrorsURL;
+	}
+
+	public void setSupportsPack200(boolean value) {
+		this.supportsPack200 = value;
+	}
+
+	/**
+	 * Sets the site type.
+	 * Throws a runtime exception if this object is marked read-only.
+	 * 
+	 * @param type site type
+	 * @since 2.0
+	 */
+	public void setType(String type) {
+		this.type = type;
+	}
+
+	/**
+	 * Sets the associated sites for this update site.
+	 * 
+	 * @param associateSites the associated sites
+	 */
+	public void setAssociateSites(URLEntry[] associateSites) {
+		this.associateSites = associateSites;
+	}
+
+	public void setDigestURLString(String digestURLString) {
+		this.digestURLString = digestURLString;
+	}
+
+	public String getDigestURLString() {
+		return digestURLString;
+	}
+}
diff --git a/bundles/org.eclipse.equinox.p2.updatesite/src/org/eclipse/equinox/internal/p2/updatesite/SiteXMLAction.java b/bundles/org.eclipse.equinox.p2.updatesite/src/org/eclipse/equinox/internal/p2/updatesite/SiteXMLAction.java
new file mode 100644
index 0000000..4cd7247
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.updatesite/src/org/eclipse/equinox/internal/p2/updatesite/SiteXMLAction.java
@@ -0,0 +1,252 @@
+/*******************************************************************************
+ * Copyright (c) 2008 Code 9 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: 
+ *   Code 9 - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.equinox.internal.p2.updatesite;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.*;
+import org.eclipse.core.runtime.*;
+import org.eclipse.equinox.internal.p2.core.helpers.LogHelper;
+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.metadata.*;
+import org.eclipse.equinox.internal.provisional.p2.metadata.MetadataFactory.InstallableUnitDescription;
+import org.eclipse.equinox.internal.provisional.p2.metadata.repository.IMetadataRepository;
+import org.eclipse.equinox.p2.publisher.*;
+import org.eclipse.equinox.p2.publisher.eclipse.FeaturesAction;
+import org.eclipse.equinox.p2.publisher.eclipse.URLEntry;
+import org.eclipse.equinox.spi.p2.publisher.LocalizationHelper;
+import org.eclipse.equinox.spi.p2.publisher.PublisherHelper;
+import org.eclipse.osgi.service.resolver.VersionRange;
+import org.osgi.framework.Version;
+
+/**
+ * Action which processes a site.xml and generates categories.  The categorization process
+ * relies on IUs for the various features to have already been generated.
+ */
+public class SiteXMLAction extends AbstractPublisherAction {
+
+	private static final String PROTOCOL_FILE = "file"; //$NON-NLS-1$
+	private UpdateSite updateSite;
+	private SiteCategory defaultCategory;
+	private HashSet defaultCategorySet;
+
+	public SiteXMLAction(URL location) {
+		try {
+			updateSite = UpdateSite.load(location, new NullProgressMonitor());
+		} catch (ProvisionException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		}
+		initialize();
+	}
+
+	public SiteXMLAction(UpdateSite updateSite) {
+		this.updateSite = updateSite;
+		initialize();
+	}
+
+	private void initialize() {
+		defaultCategory = new SiteCategory();
+		defaultCategory.setDescription("Default category for otherwise uncategorized features"); //$NON-NLS-1$
+		defaultCategory.setLabel("Uncategorized"); //$NON-NLS-1$
+		defaultCategory.setName("Default"); //$NON-NLS-1$
+		defaultCategorySet = new HashSet(1);
+		defaultCategorySet.add(defaultCategory);
+	}
+
+	public IStatus perform(IPublisherInfo info, IPublisherResult results) {
+		generateCategories(info, results);
+		return Status.OK_STATUS;
+	}
+
+	private void generateCategories(IPublisherInfo info, IPublisherResult results) {
+		Map categoriesToFeatureIUs = new HashMap();
+		Map featuresToCategories = getFeatureToCategoryMappings(info);
+		for (Iterator i = featuresToCategories.keySet().iterator(); i.hasNext();) {
+			SiteFeature feature = (SiteFeature) i.next();
+			IInstallableUnit iu = getFeatureIU(feature, results);
+			Set categories = (Set) featuresToCategories.get(feature);
+			// if there are no categories for this feature then add it to the default category.
+			if (categories == null || categories.isEmpty())
+				categories = defaultCategorySet;
+			for (Iterator it = categories.iterator(); it.hasNext();) {
+				SiteCategory category = (SiteCategory) it.next();
+				Set featureIUs = (Set) categoriesToFeatureIUs.get(category);
+				if (featureIUs == null) {
+					featureIUs = new HashSet();
+					categoriesToFeatureIUs.put(category, featureIUs);
+				}
+				featureIUs.add(iu);
+			}
+		}
+		generateCategoryIUs(categoriesToFeatureIUs, results);
+	}
+
+	private IInstallableUnit getFeatureIU(SiteFeature feature, IPublisherResult results) {
+		String id = FeaturesAction.getTransformedId(feature.getFeatureIdentifier(), false, true);
+		Version version = new Version(feature.getFeatureVersion());
+		// TODO look elsewhere as well.  Perhaps in the metadata repos and some advice.
+		Collection ius = results.getIUs(id, null);
+		for (Iterator i = ius.iterator(); i.hasNext();) {
+			IInstallableUnit iu = (IInstallableUnit) i.next();
+			if (iu.getVersion().equals(version))
+				return iu;
+		}
+		return null;
+	}
+
+	/**
+	 * Computes the mapping of features to categories as defined in the site.xml,
+	 * if available. Returns an empty map if there is not site.xml, or no categories.
+	 * @return A map of SiteFeature -> Set<SiteCategory>.
+	 */
+	protected Map getFeatureToCategoryMappings(IPublisherInfo info) {
+		HashMap mappings = new HashMap();
+		if (updateSite == null)
+			return mappings;
+		SiteModel site = updateSite.getSite();
+		if (site == null)
+			return mappings;
+
+		//copy mirror information from update site to p2 repositories
+		String mirrors = site.getMirrorsURL();
+		if (mirrors != null) {
+			//remove site.xml file reference
+			int index = mirrors.indexOf("site.xml"); //$NON-NLS-1$
+			if (index != -1)
+				mirrors = mirrors.substring(0, index) + mirrors.substring(index + "site.xml".length()); //$NON-NLS-1$
+			info.getMetadataRepository().setProperty(IRepository.PROP_MIRRORS_URL, mirrors);
+			info.getArtifactRepository().setProperty(IRepository.PROP_MIRRORS_URL, mirrors);
+		}
+
+		//publish associate sites as repository references
+		URLEntry[] associatedSites = site.getAssociatedSites();
+		if (associatedSites != null)
+			for (int i = 0; i < associatedSites.length; i++)
+				generateSiteReference(associatedSites[i].getURL(), null, info.getMetadataRepository());
+
+		if (PROTOCOL_FILE.equals(updateSite.getLocation().getProtocol())) {
+			File siteFile = new File(updateSite.getLocation().getFile());
+			if (siteFile.exists()) {
+				File siteParent = siteFile.getParentFile();
+
+				List messageKeys = site.getMessageKeys();
+				if (siteParent.isDirectory()) {
+					String[] keyStrings = (String[]) messageKeys.toArray(new String[messageKeys.size()]);
+					site.setLocalizations(LocalizationHelper.getDirPropertyLocalizations(siteParent, "site", null, keyStrings)); //$NON-NLS-1$
+				} else if (siteFile.getName().endsWith(".jar")) { //$NON-NLS-1$
+					String[] keyStrings = (String[]) messageKeys.toArray(new String[messageKeys.size()]);
+					site.setLocalizations(LocalizationHelper.getJarPropertyLocalizations(siteParent, "site", null, keyStrings)); //$NON-NLS-1$
+				}
+			}
+		}
+
+		SiteFeature[] features = site.getFeatures();
+		for (int i = 0; i < features.length; i++) {
+			//add a mapping for each category this feature belongs to
+			String[] categoryNames = features[i].getCategoryNames();
+			Set categories = new HashSet();
+			mappings.put(features[i], categories);
+			for (int j = 0; j < categoryNames.length; j++) {
+				SiteCategory category = site.getCategory(categoryNames[j]);
+				if (category != null)
+					categories.add(category);
+			}
+		}
+		return mappings;
+	}
+
+	/**
+	 * Generates and publishes a reference to an update site location
+	 * @param location The update site location
+	 * @param featureId the identifier of the feature where the error occurred, or null
+	 * @param metadataRepo The repo into which the references are added
+	 */
+	private void generateSiteReference(String location, String featureId, IMetadataRepository metadataRepo) {
+		try {
+			URL associateLocation = new URL(location);
+			metadataRepo.addReference(associateLocation, IRepository.TYPE_METADATA, IRepository.ENABLED);
+			metadataRepo.addReference(associateLocation, IRepository.TYPE_ARTIFACT, IRepository.ENABLED);
+		} catch (MalformedURLException e) {
+			String message = "Invalid site reference: " + location; //$NON-NLS-1$
+			if (featureId != null)
+				message = message + " in feature: " + featureId; //$NON-NLS-1$
+			LogHelper.log(new Status(IStatus.ERROR, Activator.ID, message));
+		}
+	}
+
+	/**
+	 * Generates IUs corresponding to update site categories.
+	 * @param categoriesToFeatures Map of SiteCategory ->Set (Feature IUs in that category).
+	 * @param result The generator result being built
+	 */
+	protected void generateCategoryIUs(Map categoriesToFeatures, IPublisherResult result) {
+		for (Iterator it = categoriesToFeatures.keySet().iterator(); it.hasNext();) {
+			SiteCategory category = (SiteCategory) it.next();
+			result.addIU(createCategoryIU(category, (Set) categoriesToFeatures.get(category), null), IPublisherResult.NON_ROOT);
+		}
+	}
+
+	/**
+	 * Creates an IU corresponding to an update site category
+	 * @param category The category descriptor
+	 * @param featureIUs The IUs of the features that belong to the category
+	 * @param parentCategory The parent category, or <code>null</code>
+	 * @return an IU representing the category
+	 */
+	public static IInstallableUnit createCategoryIU(SiteCategory category, Set featureIUs, IInstallableUnit parentCategory) {
+		InstallableUnitDescription cat = new MetadataFactory.InstallableUnitDescription();
+		cat.setSingleton(true);
+		String categoryId = category.getName();
+		cat.setId(categoryId);
+		cat.setVersion(Version.emptyVersion);
+		cat.setProperty(IInstallableUnit.PROP_NAME, category.getLabel());
+		cat.setProperty(IInstallableUnit.PROP_DESCRIPTION, category.getDescription());
+
+		ArrayList reqsConfigurationUnits = new ArrayList(featureIUs.size());
+		for (Iterator iterator = featureIUs.iterator(); iterator.hasNext();) {
+			IInstallableUnit iu = (IInstallableUnit) iterator.next();
+			VersionRange range = new VersionRange(iu.getVersion(), true, iu.getVersion(), true);
+			reqsConfigurationUnits.add(MetadataFactory.createRequiredCapability(IInstallableUnit.NAMESPACE_IU_ID, iu.getId(), range, iu.getFilter(), false, false));
+		}
+		//note that update sites don't currently support nested categories, but it may be useful to add in the future
+		if (parentCategory != null) {
+			reqsConfigurationUnits.add(MetadataFactory.createRequiredCapability(IInstallableUnit.NAMESPACE_IU_ID, parentCategory.getId(), VersionRange.emptyRange, parentCategory.getFilter(), false, false));
+		}
+		cat.setRequiredCapabilities((RequiredCapability[]) reqsConfigurationUnits.toArray(new RequiredCapability[reqsConfigurationUnits.size()]));
+
+		// Create set of provided capabilities
+		ArrayList providedCapabilities = new ArrayList();
+		providedCapabilities.add(PublisherHelper.createSelfCapability(categoryId, Version.emptyVersion));
+
+		Map localizations = category.getLocalizations();
+		if (localizations != null) {
+			for (Iterator iter = localizations.keySet().iterator(); iter.hasNext();) {
+				Locale locale = (Locale) iter.next();
+				Properties translatedStrings = (Properties) localizations.get(locale);
+				Enumeration propertyKeys = translatedStrings.propertyNames();
+				while (propertyKeys.hasMoreElements()) {
+					String nextKey = (String) propertyKeys.nextElement();
+					cat.setProperty(locale.toString() + '.' + nextKey, translatedStrings.getProperty(nextKey));
+				}
+				providedCapabilities.add(PublisherHelper.makeTranslationCapability(categoryId, locale));
+			}
+		}
+
+		cat.setCapabilities((ProvidedCapability[]) providedCapabilities.toArray(new ProvidedCapability[providedCapabilities.size()]));
+
+		cat.setArtifacts(new IArtifactKey[0]);
+		cat.setProperty(IInstallableUnit.PROP_TYPE_CATEGORY, "true"); //$NON-NLS-1$
+		return MetadataFactory.createInstallableUnit(cat);
+	}
+
+}
diff --git a/bundles/org.eclipse.equinox.p2.updatesite/src/org/eclipse/equinox/internal/p2/updatesite/UpdateSite.java b/bundles/org.eclipse.equinox.p2.updatesite/src/org/eclipse/equinox/internal/p2/updatesite/UpdateSite.java
index d36aa15..0eda749 100644
--- a/bundles/org.eclipse.equinox.p2.updatesite/src/org/eclipse/equinox/internal/p2/updatesite/UpdateSite.java
+++ b/bundles/org.eclipse.equinox.p2.updatesite/src/org/eclipse/equinox/internal/p2/updatesite/UpdateSite.java
@@ -19,9 +19,9 @@
 import org.eclipse.core.runtime.*;
 import org.eclipse.equinox.internal.p2.core.helpers.LogHelper;
 import org.eclipse.equinox.internal.p2.core.helpers.URLUtil;
-import org.eclipse.equinox.internal.p2.metadata.generator.features.*;
+import org.eclipse.equinox.internal.p2.publisher.eclipse.FeatureParser;
 import org.eclipse.equinox.internal.provisional.p2.core.ProvisionException;
-import org.eclipse.equinox.internal.provisional.p2.metadata.generator.*;
+import org.eclipse.equinox.p2.publisher.eclipse.*;
 import org.eclipse.osgi.util.NLS;
 import org.xml.sax.SAXException;
 
diff --git a/bundles/org.eclipse.equinox.p2.updatesite/src/org/eclipse/equinox/internal/p2/updatesite/artifact/UpdateSiteArtifactRepository.java b/bundles/org.eclipse.equinox.p2.updatesite/src/org/eclipse/equinox/internal/p2/updatesite/artifact/UpdateSiteArtifactRepository.java
deleted file mode 100644
index 6b66e66..0000000
--- a/bundles/org.eclipse.equinox.p2.updatesite/src/org/eclipse/equinox/internal/p2/updatesite/artifact/UpdateSiteArtifactRepository.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.updatesite.artifact;
-
-import java.io.File;
-import java.io.OutputStream;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.*;
-import org.eclipse.core.runtime.*;
-import org.eclipse.equinox.internal.p2.updatesite.*;
-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.core.repository.IRepository;
-import org.eclipse.equinox.internal.provisional.p2.metadata.IArtifactKey;
-import org.eclipse.equinox.internal.provisional.p2.metadata.generator.*;
-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 UpdateSiteArtifactRepository extends AbstractRepository implements IArtifactRepository {
-
-	public static final String TYPE = "org.eclipse.equinox.p2.updatesite.artifactRepository"; //$NON-NLS-1$
-	public static final Integer VERSION = new Integer(1);
-	private static final String PROP_ARTIFACT_REFERENCE = "artifact.reference"; //$NON-NLS-1$
-	private static final String PROP_FORCE_THREADING = "eclipse.p2.force.threading"; //$NON-NLS-1$
-	private static final String PROP_SITE_CHECKSUM = "site.checksum"; //$NON-NLS-1$
-	private static final String PROTOCOL_FILE = "file"; //$NON-NLS-1$
-
-	private final IArtifactRepository artifactRepository;
-
-	public UpdateSiteArtifactRepository(URL location, IProgressMonitor monitor) throws ProvisionException {
-		super(Activator.getRepositoryName(location), TYPE, VERSION.toString(), location, null, null, null);
-
-		// todo progress monitoring
-		// loading validates before we create repositories
-		UpdateSite updateSite = UpdateSite.load(location, null);
-
-		BundleContext context = Activator.getBundleContext();
-		URL localRepositoryURL = null;
-		try {
-			String stateDirName = Integer.toString(location.toExternalForm().hashCode());
-			File bundleData = context.getDataFile(null);
-			File stateDir = new File(bundleData, stateDirName);
-			localRepositoryURL = stateDir.toURL();
-		} catch (MalformedURLException e) {
-			throw new ProvisionException(new Status(IStatus.ERROR, Activator.ID, Messages.ErrorCreatingRepository, e));
-		}
-		artifactRepository = initializeArtifactRepository(context, localRepositoryURL, "update site implementation - " + location.toExternalForm(), updateSite); //$NON-NLS-1$
-
-		String savedChecksum = (String) artifactRepository.getProperties().get(PROP_SITE_CHECKSUM);
-		if (savedChecksum != null && savedChecksum.equals(updateSite.getChecksum()))
-			return;
-
-		if (!location.getProtocol().equals(PROTOCOL_FILE))
-			artifactRepository.setProperty(PROP_FORCE_THREADING, "true"); //$NON-NLS-1$
-		artifactRepository.removeAll();
-		generateArtifacts(updateSite);
-		artifactRepository.setProperty(PROP_SITE_CHECKSUM, updateSite.getChecksum());
-	}
-
-	private void generateArtifacts(UpdateSite updateSite) throws ProvisionException {
-		Feature[] features = updateSite.loadFeatures();
-
-		Set allSiteArtifacts = new HashSet();
-		for (int i = 0; i < features.length; i++) {
-			Feature feature = features[i];
-			IArtifactKey featureKey = MetadataGeneratorHelper.createFeatureArtifactKey(feature.getId(), feature.getVersion());
-			ArtifactDescriptor featureArtifactDescriptor = new ArtifactDescriptor(featureKey);
-			URL featureURL = updateSite.getFeatureURL(feature.getId(), feature.getVersion());
-			featureArtifactDescriptor.setRepositoryProperty(PROP_ARTIFACT_REFERENCE, featureURL.toExternalForm());
-			featureArtifactDescriptor.setProperty(IArtifactDescriptor.DOWNLOAD_CONTENTTYPE, IArtifactDescriptor.TYPE_ZIP);
-			allSiteArtifacts.add(featureArtifactDescriptor);
-
-			FeatureEntry[] featureEntries = feature.getEntries();
-			for (int j = 0; j < featureEntries.length; j++) {
-				FeatureEntry entry = featureEntries[j];
-				if (entry.isPlugin() && !entry.isRequires()) {
-					IArtifactKey key = MetadataGeneratorHelper.createBundleArtifactKey(entry.getId(), entry.getVersion());
-					ArtifactDescriptor artifactDescriptor = new ArtifactDescriptor(key);
-					URL pluginURL = updateSite.getPluginURL(entry);
-					artifactDescriptor.setRepositoryProperty(PROP_ARTIFACT_REFERENCE, pluginURL.toExternalForm());
-					artifactDescriptor.setProperty(IArtifactDescriptor.DOWNLOAD_CONTENTTYPE, IArtifactDescriptor.TYPE_ZIP);
-					allSiteArtifacts.add(artifactDescriptor);
-				}
-			}
-		}
-
-		IArtifactDescriptor[] descriptors = (IArtifactDescriptor[]) allSiteArtifacts.toArray(new IArtifactDescriptor[allSiteArtifacts.size()]);
-		artifactRepository.addDescriptors(descriptors);
-	}
-
-	public static void validate(URL url, IProgressMonitor monitor) throws ProvisionException {
-		UpdateSite.validate(url, monitor);
-	}
-
-	private IArtifactRepository initializeArtifactRepository(BundleContext context, URL stateDirURL, String repositoryName, UpdateSite updateSite) {
-		SimpleArtifactRepositoryFactory factory = new SimpleArtifactRepositoryFactory();
-		try {
-			return factory.load(stateDirURL, null);
-		} catch (ProvisionException e) {
-			//fall through and create a new repository
-		}
-		Map props = new HashMap(5);
-		String mirrors = updateSite.getMirrorsURL();
-		if (mirrors != null) {
-			props.put(IRepository.PROP_MIRRORS_URL, mirrors);
-			//set the mirror base URL relative to the real remote repository rather than our local cache
-			props.put(IRepository.PROP_MIRRORS_BASE_URL, getLocation().toExternalForm());
-		}
-		return factory.create(stateDirURL, repositoryName, null, props);
-	}
-
-	public Map getProperties() {
-		return artifactRepository.getProperties();
-	}
-
-	public String setProperty(String key, String value) {
-		return artifactRepository.setProperty(key, value);
-	}
-
-	public void addDescriptor(IArtifactDescriptor descriptor) {
-		artifactRepository.addDescriptor(descriptor);
-	}
-
-	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 void removeAll() {
-		artifactRepository.removeAll();
-	}
-
-	public void removeDescriptor(IArtifactDescriptor descriptor) {
-		artifactRepository.removeDescriptor(descriptor);
-	}
-
-	public void removeDescriptor(IArtifactKey key) {
-		artifactRepository.removeDescriptor(key);
-	}
-
-	public void addDescriptors(IArtifactDescriptor[] descriptors) {
-		artifactRepository.addDescriptors(descriptors);
-	}
-}
diff --git a/bundles/org.eclipse.equinox.p2.updatesite/src/org/eclipse/equinox/internal/p2/updatesite/artifact/UpdateSiteArtifactRepositoryFactory.java b/bundles/org.eclipse.equinox.p2.updatesite/src/org/eclipse/equinox/internal/p2/updatesite/artifact/UpdateSiteArtifactRepositoryFactory.java
index ef3ecd1..3a21558 100644
--- a/bundles/org.eclipse.equinox.p2.updatesite/src/org/eclipse/equinox/internal/p2/updatesite/artifact/UpdateSiteArtifactRepositoryFactory.java
+++ b/bundles/org.eclipse.equinox.p2.updatesite/src/org/eclipse/equinox/internal/p2/updatesite/artifact/UpdateSiteArtifactRepositoryFactory.java
@@ -7,15 +7,27 @@
  *
  * Contributors:
  *     IBM Corporation - initial API and implementation
+ *     Code 9 - ongoing development
  *******************************************************************************/
 package org.eclipse.equinox.internal.p2.updatesite.artifact;
 
+import org.eclipse.equinox.spi.p2.publisher.PublisherHelper;
+
+import org.eclipse.equinox.p2.publisher.eclipse.Feature;
+import org.eclipse.equinox.p2.publisher.eclipse.FeatureEntry;
+
+import org.eclipse.equinox.p2.publisher.eclipse.FeaturesAction;
+
 import java.net.URL;
-import java.util.Map;
+import java.util.*;
 import org.eclipse.core.runtime.IProgressMonitor;
-import org.eclipse.equinox.internal.provisional.p2.artifact.repository.IArtifactRepository;
+import org.eclipse.equinox.internal.p2.updatesite.UpdateSite;
+import org.eclipse.equinox.internal.p2.updatesite.metadata.UpdateSiteMetadataRepositoryFactory;
+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.metadata.IArtifactKey;
 import org.eclipse.equinox.internal.provisional.spi.p2.artifact.repository.IArtifactRepositoryFactory;
+import org.eclipse.equinox.internal.provisional.spi.p2.artifact.repository.SimpleArtifactRepositoryFactory;
 
 public class UpdateSiteArtifactRepositoryFactory implements IArtifactRepositoryFactory {
 
@@ -26,10 +38,70 @@
 		return null;
 	}
 
+	private static final String PROP_ARTIFACT_REFERENCE = "artifact.reference"; //$NON-NLS-1$
+	private static final String PROP_FORCE_THREADING = "eclipse.p2.force.threading"; //$NON-NLS-1$
+	private static final String PROP_SITE_CHECKSUM = "site.checksum"; //$NON-NLS-1$
+	private static final String PROTOCOL_FILE = "file"; //$NON-NLS-1$
+
 	/* (non-Javadoc)
 	 * @see org.eclipse.equinox.internal.provisional.spi.p2.artifact.repository.IArtifactRepositoryFactory#load(java.net.URL, org.eclipse.core.runtime.IProgressMonitor)
 	 */
 	public IArtifactRepository load(URL location, IProgressMonitor monitor) throws ProvisionException {
-		return new UpdateSiteArtifactRepository(location, monitor);
+		IArtifactRepository repository = loadRepository(location, monitor);
+		initializeRepository(repository, location, monitor);
+		return repository;
+	}
+
+	public IArtifactRepository loadRepository(URL location, IProgressMonitor monitor) throws ProvisionException {
+		URL localRepositoryURL = UpdateSiteMetadataRepositoryFactory.getLocalRepositoryLocation(location);
+		SimpleArtifactRepositoryFactory factory = new SimpleArtifactRepositoryFactory();
+		try {
+			return factory.load(localRepositoryURL, null);
+		} catch (ProvisionException e) {
+			//fall through and create a new repository
+		}
+		String repositoryName = "update site: " + location.toExternalForm(); //$NON-NLS-1$
+		return factory.create(localRepositoryURL, repositoryName, null, null);
+	}
+
+	public void initializeRepository(IArtifactRepository repository, URL location, IProgressMonitor monitor) throws ProvisionException {
+		UpdateSite updateSite = UpdateSite.load(location, null);
+		String savedChecksum = (String) repository.getProperties().get(PROP_SITE_CHECKSUM);
+		if (savedChecksum != null && savedChecksum.equals(updateSite.getChecksum()))
+			return;
+
+		if (!location.getProtocol().equals(PROTOCOL_FILE))
+			repository.setProperty(PROP_FORCE_THREADING, "true"); //$NON-NLS-1$
+		repository.setProperty(PROP_SITE_CHECKSUM, updateSite.getChecksum());
+		repository.removeAll();
+		generateMetadata(updateSite, repository);
+	}
+
+	private void generateMetadata(UpdateSite updateSite, IArtifactRepository repository) throws ProvisionException {
+		Feature[] features = updateSite.loadFeatures();
+		Set allSiteArtifacts = new HashSet();
+		for (int i = 0; i < features.length; i++) {
+			Feature feature = features[i];
+			IArtifactKey featureKey = FeaturesAction.createFeatureArtifactKey(feature.getId(), feature.getVersion());
+			ArtifactDescriptor featureArtifactDescriptor = new ArtifactDescriptor(featureKey);
+			URL featureURL = updateSite.getFeatureURL(feature.getId(), feature.getVersion());
+			featureArtifactDescriptor.setRepositoryProperty(PROP_ARTIFACT_REFERENCE, featureURL.toExternalForm());
+			allSiteArtifacts.add(featureArtifactDescriptor);
+
+			FeatureEntry[] featureEntries = feature.getEntries();
+			for (int j = 0; j < featureEntries.length; j++) {
+				FeatureEntry entry = featureEntries[j];
+				if (entry.isPlugin() && !entry.isRequires()) {
+					IArtifactKey key = PublisherHelper.createBundleArtifactKey(entry.getId(), entry.getVersion());
+					ArtifactDescriptor artifactDescriptor = new ArtifactDescriptor(key);
+					URL pluginURL = updateSite.getPluginURL(entry);
+					artifactDescriptor.setRepositoryProperty(PROP_ARTIFACT_REFERENCE, pluginURL.toExternalForm());
+					allSiteArtifacts.add(artifactDescriptor);
+				}
+			}
+		}
+
+		IArtifactDescriptor[] descriptors = (IArtifactDescriptor[]) allSiteArtifacts.toArray(new IArtifactDescriptor[allSiteArtifacts.size()]);
+		repository.addDescriptors(descriptors);
 	}
 }
diff --git a/bundles/org.eclipse.equinox.p2.updatesite/src/org/eclipse/equinox/internal/p2/updatesite/messages.properties b/bundles/org.eclipse.equinox.p2.updatesite/src/org/eclipse/equinox/internal/p2/updatesite/messages.properties
index e3d734a..c6def23 100644
--- a/bundles/org.eclipse.equinox.p2.updatesite/src/org/eclipse/equinox/internal/p2/updatesite/messages.properties
+++ b/bundles/org.eclipse.equinox.p2.updatesite/src/org/eclipse/equinox/internal/p2/updatesite/messages.properties
@@ -18,3 +18,20 @@
 io_failedRead=Unable to read repository at {0}.
 repoMan_internalError=Internal error.
 repo_loading = Loading the repository {0}
+
+DefaultFeatureParser_IdOrVersionInvalid= Error parsing feature stream. The unique identifier or the version is null or empty for the State: \"{2}\": unique identifier=\"{0}\" version=\"{1}\".
+DefaultSiteParser_NoSiteTag= Error parsing site stream. Unable to find root element \"site\" in the stream.
+DefaultSiteParser_WrongParsingStack= Internal Error parsing site stream. Unexpected Parsing Stack: \"{0}\"
+DefaultSiteParser_UnknownElement= Error parsing site stream. Unknown element \"{0}\" in parsing state \"{1}\". Check the validity of the XML file.
+DefaultSiteParser_UnknownStartState= Internal Error parsing site stream. Unknown start state \"{0}\".
+DefaultSiteParser_Missing= Error parsing site stream. The \"{0}\" tag of the element \"{1}\" is null or empty. Value is required.
+DefaultSiteParser_ParsingStackBackToInitialState= Internal Error parsing site stream. Parsing stack back to Initial State.
+DefaultSiteParser_ElementAlreadySet= Error parsing site stream. Element: \"{0}\" already set for the Site.
+DefaultSiteParser_CategoryAlreadySet= Error parsing site stream. Element: \"{0}\": \"{1}\" already set for the Site.
+DefaultSiteParser_UnknownEndState= Internal Error parsing site stream. Unknown end state \"{0}\".
+DefaultSiteParser_ErrorParsing= Error Parsing site stream. Error: \"{0}\"
+DefaultSiteParser_ErrorlineColumnMessage= Error Parsing site stream. Element \"{0}\" line: \"{1}\" column:\"{2}\". Error: \"{3}\".
+DefaultSiteParser_ErrorParsingSite= Error Parsing site stream.
+DefaultSiteParser_UnknownState= Unknown State \"{0}\".
+DefaultSiteParser_InvalidXMLStream= The XML stream is not a valid default \"site.xml\" file. The root tag is not site.
+DefaultSiteParser_mirrors = Error processing update site mirror.
\ No newline at end of file
diff --git a/bundles/org.eclipse.equinox.p2.updatesite/src/org/eclipse/equinox/internal/p2/updatesite/metadata/UpdateSiteMetadataRepository.java b/bundles/org.eclipse.equinox.p2.updatesite/src/org/eclipse/equinox/internal/p2/updatesite/metadata/UpdateSiteMetadataRepository.java
deleted file mode 100644
index ff8b9b2..0000000
--- a/bundles/org.eclipse.equinox.p2.updatesite/src/org/eclipse/equinox/internal/p2/updatesite/metadata/UpdateSiteMetadataRepository.java
+++ /dev/null
@@ -1,274 +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
- *     Ray Braithwood (ray@genuitec.com) - fix for bug 220605
- *******************************************************************************/
-package org.eclipse.equinox.internal.p2.updatesite.metadata;
-
-import java.io.File;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.*;
-import org.eclipse.core.runtime.*;
-import org.eclipse.equinox.internal.p2.core.helpers.LogHelper;
-import org.eclipse.equinox.internal.p2.core.helpers.ServiceHelper;
-import org.eclipse.equinox.internal.p2.metadata.generator.features.*;
-import org.eclipse.equinox.internal.p2.updatesite.*;
-import org.eclipse.equinox.internal.p2.updatesite.Messages;
-import org.eclipse.equinox.internal.provisional.p2.core.ProvisionException;
-import org.eclipse.equinox.internal.provisional.p2.core.eventbus.IProvisioningEventBus;
-import org.eclipse.equinox.internal.provisional.p2.core.repository.IRepository;
-import org.eclipse.equinox.internal.provisional.p2.core.repository.RepositoryEvent;
-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.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.metadata.repository.AbstractMetadataRepository;
-import org.eclipse.equinox.internal.provisional.spi.p2.metadata.repository.SimpleMetadataRepositoryFactory;
-import org.eclipse.osgi.service.resolver.*;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.ServiceReference;
-
-public class UpdateSiteMetadataRepository extends AbstractMetadataRepository {
-
-	public static final String TYPE = "org.eclipse.equinox.p2.updatesite.metadataRepository"; //$NON-NLS-1$
-	public static final Integer VERSION = new Integer(1);
-	private final IMetadataRepository metadataRepository;
-	private static final String FEATURE_VERSION_SEPARATOR = "_"; //$NON-NLS-1$
-	private static final String PROP_SITE_CHECKSUM = "site.checksum"; //$NON-NLS-1$
-
-	public UpdateSiteMetadataRepository(URL location, IProgressMonitor monitor) throws ProvisionException {
-		super(Activator.getRepositoryName(location), TYPE, VERSION.toString(), location, null, null, null);
-		// todo progress monitoring
-		// loading validates before we create repositories
-		UpdateSite updateSite = UpdateSite.load(location, null);
-		broadcastAssociateSites(updateSite);
-
-		BundleContext context = Activator.getBundleContext();
-		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, Messages.ErrorCreatingRepository, e));
-		}
-
-		metadataRepository = initializeMetadataRepository(context, localRepositoryURL, "update site implementation - " + location.toExternalForm(), updateSite); //$NON-NLS-1$
-
-		String savedChecksum = (String) metadataRepository.getProperties().get(PROP_SITE_CHECKSUM);
-		if (savedChecksum != null && savedChecksum.equals(updateSite.getChecksum()))
-			return;
-		metadataRepository.removeAll();
-		generateMetadata(updateSite);
-		metadataRepository.setProperty(PROP_SITE_CHECKSUM, updateSite.getChecksum());
-	}
-
-	/**
-	 * Broadcast events for any associated sites for this repository so repository
-	 * managers are aware of them.
-	 */
-	private void broadcastAssociateSites(UpdateSite baseSite) {
-		if (baseSite == null)
-			return;
-		URLEntry[] sites = baseSite.getSite().getAssociatedSites();
-		if (sites == null || sites.length == 0)
-			return;
-
-		IProvisioningEventBus bus = (IProvisioningEventBus) ServiceHelper.getService(Activator.getBundleContext(), IProvisioningEventBus.SERVICE_NAME);
-		if (bus == null)
-			return;
-		for (int i = 0; i < sites.length; i++) {
-			try {
-				URL siteLocation = new URL(sites[i].getURL());
-				bus.publishEvent(new RepositoryEvent(siteLocation, IRepository.TYPE_METADATA, RepositoryEvent.DISCOVERED, true));
-				bus.publishEvent(new RepositoryEvent(siteLocation, IRepository.TYPE_ARTIFACT, RepositoryEvent.DISCOVERED, true));
-			} catch (MalformedURLException e) {
-				LogHelper.log(new Status(IStatus.WARNING, Activator.ID, "Site has invalid associate site: " + baseSite.getLocation(), e)); //$NON-NLS-1$
-			}
-		}
-
-	}
-
-	private void generateMetadata(UpdateSite updateSite) throws ProvisionException {
-		SiteModel siteModel = updateSite.getSite();
-
-		// we load the features here to ensure that all site features are fully populated with
-		// id and version information before looking at category information
-		Feature[] features = updateSite.loadFeatures();
-
-		SiteCategory[] siteCategories = siteModel.getCategories();
-		Map categoryNameToFeatureIUs = new HashMap();
-		for (int i = 0; i < siteCategories.length; i++)
-			categoryNameToFeatureIUs.put(siteCategories[i].getName(), new HashSet());
-
-		SiteFeature[] siteFeatures = siteModel.getFeatures();
-		Map featureKeyToCategoryNames = new HashMap();
-		for (int i = 0; i < siteFeatures.length; i++) {
-			SiteFeature siteFeature = siteFeatures[i];
-			String featureKey = siteFeature.getFeatureIdentifier() + FEATURE_VERSION_SEPARATOR + siteFeature.getFeatureVersion();
-			featureKeyToCategoryNames.put(featureKey, siteFeature.getCategoryNames());
-		}
-
-		Properties extraProperties = new Properties();
-		extraProperties.put(IInstallableUnit.PROP_PARTIAL_IU, Boolean.TRUE.toString());
-		Set allSiteIUs = new HashSet();
-		BundleDescriptionFactory bundleDesciptionFactory = initializeBundleDescriptionFactory(Activator.getBundleContext());
-
-		for (int i = 0; i < features.length; i++) {
-			Feature feature = features[i];
-			FeatureEntry[] featureEntries = feature.getEntries();
-			for (int j = 0; j < featureEntries.length; j++) {
-				FeatureEntry entry = featureEntries[j];
-				if (entry.isPlugin() && !entry.isRequires()) {
-					Dictionary mockManifest = new Properties();
-					mockManifest.put("Manifest-Version", "1.0"); //$NON-NLS-1$ //$NON-NLS-2$
-					mockManifest.put("Bundle-ManifestVersion", "2"); //$NON-NLS-1$ //$NON-NLS-2$
-					mockManifest.put("Bundle-SymbolicName", entry.getId()); //$NON-NLS-1$
-					mockManifest.put("Bundle-Version", entry.getVersion()); //$NON-NLS-1$
-					BundleDescription bundleDescription = bundleDesciptionFactory.getBundleDescription(mockManifest, null);
-					IArtifactKey key = MetadataGeneratorHelper.createBundleArtifactKey(entry.getId(), entry.getVersion());
-					IInstallableUnit[] bundleIUs = MetadataGeneratorHelper.createEclipseIU(bundleDescription, null, entry.isUnpack(), key, extraProperties);
-					for (int n = 0; n < bundleIUs.length; n++) {
-						allSiteIUs.add(bundleIUs[n]);
-					}
-				}
-			}
-
-			IInstallableUnit featureIU = MetadataGeneratorHelper.createFeatureJarIU(feature, true);
-			IInstallableUnit groupIU = MetadataGeneratorHelper.createGroupIU(feature, featureIU);
-
-			String featureKey = feature.getId() + FEATURE_VERSION_SEPARATOR + feature.getVersion();
-			String[] categoryNames = (String[]) featureKeyToCategoryNames.get(featureKey);
-			if (categoryNames != null) {
-				for (int j = 0; j < categoryNames.length; j++) {
-					Set featureIUList = (Set) categoryNameToFeatureIUs.get(categoryNames[j]);
-					if (featureIUList != null) {
-						featureIUList.add(groupIU);
-					}
-				}
-			}
-			allSiteIUs.add(featureIU);
-			allSiteIUs.add(groupIU);
-			publishSites(feature);
-		}
-
-		for (int i = 0; i < siteCategories.length; i++) {
-			SiteCategory category = siteCategories[i];
-			Set featureIUs = (Set) categoryNameToFeatureIUs.get(category.getName());
-			IInstallableUnit categoryIU = MetadataGeneratorHelper.createCategoryIU(category, featureIUs, null);
-			allSiteIUs.add(categoryIU);
-		}
-
-		IInstallableUnit[] ius = (IInstallableUnit[]) allSiteIUs.toArray(new IInstallableUnit[allSiteIUs.size()]);
-		metadataRepository.addInstallableUnits(ius);
-	}
-
-	/*(non-Javadoc)
-	 * @see IMetadataRepositoryFactory#validate(URL, IProgressMonitor)
-	 */
-	public static void validate(URL url, IProgressMonitor monitor) throws ProvisionException {
-		UpdateSite.validate(url, monitor);
-	}
-
-	private IMetadataRepository initializeMetadataRepository(BundleContext context, URL stateDirURL, String repositoryName, UpdateSite updateSite) {
-		SimpleMetadataRepositoryFactory factory = new SimpleMetadataRepositoryFactory();
-		try {
-			return factory.load(stateDirURL, null);
-		} catch (ProvisionException e) {
-			//fall through and create a new repository
-		}
-		Map props = new HashMap(5);
-		String mirrors = updateSite.getMirrorsURL();
-		if (mirrors != null) {
-			props.put(IRepository.PROP_MIRRORS_URL, mirrors);
-			//set the mirror base URL relative to the real remote repository rather than our local cache
-			props.put(IRepository.PROP_MIRRORS_BASE_URL, getLocation().toExternalForm());
-		}
-		return factory.create(stateDirURL, repositoryName, null, props);
-	}
-
-	private BundleDescriptionFactory initializeBundleDescriptionFactory(BundleContext context) {
-		ServiceReference reference = context.getServiceReference(PlatformAdmin.class.getName());
-		if (reference == null)
-			throw new IllegalStateException(Messages.PlatformAdminNotRegistered);
-		PlatformAdmin platformAdmin = (PlatformAdmin) context.getService(reference);
-		if (platformAdmin == null)
-			throw new IllegalStateException(Messages.PlatformAdminNotRegistered);
-
-		try {
-			StateObjectFactory stateObjectFactory = platformAdmin.getFactory();
-			return new BundleDescriptionFactory(stateObjectFactory, null);
-		} finally {
-			context.ungetService(reference);
-		}
-	}
-
-	public Map getProperties() {
-		return metadataRepository.getProperties();
-	}
-
-	public String setProperty(String key, String value) {
-		return metadataRepository.setProperty(key, value);
-	}
-
-	public Collector query(Query query, Collector collector, IProgressMonitor monitor) {
-		return metadataRepository.query(query, collector, monitor);
-	}
-
-	public void removeAll() {
-		metadataRepository.removeAll();
-	}
-
-	public void addInstallableUnits(IInstallableUnit[] installableUnits) {
-		metadataRepository.addInstallableUnits(installableUnits);
-	}
-
-	public boolean removeInstallableUnits(Query query, IProgressMonitor monitor) {
-		return metadataRepository.removeInstallableUnits(query, monitor);
-	}
-
-	public void initialize(RepositoryState state) {
-		//nothing to do
-	}
-
-	/**
-	 * Broadcast events for any discovery sites associated with the feature
-	 * so the repository managers add them to their list of known repositories.
-	 */
-	private void publishSites(Feature feature) {
-		IProvisioningEventBus bus = (IProvisioningEventBus) ServiceHelper.getService(Activator.getBundleContext(), IProvisioningEventBus.SERVICE_NAME);
-		if (bus == null)
-			return;
-		URLEntry[] discoverySites = feature.getDiscoverySites();
-		for (int i = 0; i < discoverySites.length; i++)
-			publishSite(feature, bus, discoverySites[i].getURL(), false);
-		String updateSite = feature.getUpdateSiteURL();
-		if (updateSite != null)
-			publishSite(feature, bus, updateSite, true);
-	}
-
-	/**
-	 * Broadcast a discovery event for the given repository location.
-	 */
-	private void publishSite(Feature feature, IProvisioningEventBus bus, String locationString, boolean isEnabled) {
-		try {
-			URL siteLocation = new URL(locationString);
-			bus.publishEvent(new RepositoryEvent(siteLocation, IRepository.TYPE_METADATA, RepositoryEvent.DISCOVERED, isEnabled));
-			bus.publishEvent(new RepositoryEvent(siteLocation, IRepository.TYPE_ARTIFACT, RepositoryEvent.DISCOVERED, isEnabled));
-		} catch (MalformedURLException e) {
-			LogHelper.log(new Status(IStatus.WARNING, Activator.ID, "Feature references invalid site: " + feature.getId(), e)); //$NON-NLS-1$
-		}
-	}
-
-}
diff --git a/bundles/org.eclipse.equinox.p2.updatesite/src/org/eclipse/equinox/internal/p2/updatesite/metadata/UpdateSiteMetadataRepositoryFactory.java b/bundles/org.eclipse.equinox.p2.updatesite/src/org/eclipse/equinox/internal/p2/updatesite/metadata/UpdateSiteMetadataRepositoryFactory.java
index 2be0d50..5a7f78d 100644
--- a/bundles/org.eclipse.equinox.p2.updatesite/src/org/eclipse/equinox/internal/p2/updatesite/metadata/UpdateSiteMetadataRepositoryFactory.java
+++ b/bundles/org.eclipse.equinox.p2.updatesite/src/org/eclipse/equinox/internal/p2/updatesite/metadata/UpdateSiteMetadataRepositoryFactory.java
@@ -8,17 +8,37 @@
  * Contributors:
  *     IBM Corporation - initial API and implementation
  *     Ray Braithwood (ray@genuitec.com) - fix for bug 220605
+ *     Code 9 - ongoing development
  *******************************************************************************/
 package org.eclipse.equinox.internal.p2.updatesite.metadata;
 
+import java.io.File;
+import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.Map;
 import org.eclipse.core.runtime.*;
+import org.eclipse.equinox.internal.p2.updatesite.*;
 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;
+import org.eclipse.equinox.internal.provisional.spi.p2.metadata.repository.SimpleMetadataRepositoryFactory;
+import org.eclipse.equinox.p2.publisher.*;
 
 public class UpdateSiteMetadataRepositoryFactory implements IMetadataRepositoryFactory {
+	private static final String PROP_SITE_CHECKSUM = "site.checksum"; //$NON-NLS-1$
+
+	public static URL getLocalRepositoryLocation(URL location) throws ProvisionException {
+		URL localRepositoryURL = null;
+		try {
+			String stateDirName = Integer.toString(location.toExternalForm().hashCode());
+			File bundleData = Activator.getBundleContext().getDataFile(null);
+			File stateDir = new File(bundleData, stateDirName);
+			localRepositoryURL = stateDir.toURL();
+		} catch (MalformedURLException e) {
+			throw new ProvisionException(new Status(IStatus.ERROR, Activator.ID, Messages.ErrorCreatingRepository, e));
+		}
+		return localRepositoryURL;
+	}
 
 	/* (non-Javadoc)
 	 * @see org.eclipse.equinox.internal.provisional.spi.p2.metadata.repository.IMetadataRepositoryFactory#create(java.net.URL, java.lang.String, java.lang.String, java.util.Map)
@@ -27,24 +47,49 @@
 		return null;
 	}
 
-	/*
-	 * (non-Javadoc)
-	 * @see org.eclipse.equinox.internal.provisional.spi.p2.metadata.repository.IMetadataRepositoryFactory#validate(java.net.URL, org.eclipse.core.runtime.IProgressMonitor)
-	 */
-
 	public IStatus validate(URL location, IProgressMonitor monitor) {
 		try {
-			UpdateSiteMetadataRepository.validate(location, monitor);
+			UpdateSite.validate(location, monitor);
 		} catch (ProvisionException e) {
 			return e.getStatus();
 		}
 		return Status.OK_STATUS;
 	}
 
-	/* (non-Javadoc)
-	 * @see org.eclipse.equinox.internal.provisional.spi.p2.metadata.repository.IMetadataRepositoryFactory#load(java.net.URL, org.eclipse.core.runtime.IProgressMonitor)
-	 */
 	public IMetadataRepository load(URL location, IProgressMonitor monitor) throws ProvisionException {
-		return new UpdateSiteMetadataRepository(location, monitor);
+		IMetadataRepository repository = loadRepository(location, monitor);
+		initializeRepository(repository, location, monitor);
+		return repository;
 	}
+
+	public IMetadataRepository loadRepository(URL location, IProgressMonitor monitor) throws ProvisionException {
+		URL localRepositoryURL = getLocalRepositoryLocation(location);
+		SimpleMetadataRepositoryFactory factory = new SimpleMetadataRepositoryFactory();
+		try {
+			return factory.load(localRepositoryURL, null);
+		} catch (ProvisionException e) {
+			//fall through and create a new repository
+		}
+		String repositoryName = "update site: " + location.toExternalForm(); //$NON-NLS-1$
+		return factory.create(localRepositoryURL, repositoryName, null, null);
+	}
+
+	public void initializeRepository(IMetadataRepository repository, URL location, IProgressMonitor monitor) throws ProvisionException {
+		UpdateSite updateSite = UpdateSite.load(location, null);
+		String savedChecksum = (String) repository.getProperties().get(PROP_SITE_CHECKSUM);
+		if (savedChecksum != null && savedChecksum.equals(updateSite.getChecksum()))
+			return;
+		repository.setProperty(PROP_SITE_CHECKSUM, updateSite.getChecksum());
+		repository.removeAll();
+		generateMetadata(updateSite, repository);
+	}
+
+	private void generateMetadata(UpdateSite updateSite, IMetadataRepository repository) {
+		PublisherInfo info = new PublisherInfo();
+		info.setMetadataRepository(repository);
+		IPublisherAction[] actions = new IPublisherAction[] {new RemoteUpdateSiteAction(updateSite)};
+		Publisher publisher = new Publisher(info);
+		IStatus result = publisher.publish(actions);
+	}
+
 }