Bug 137538 mirror command mirrors nothing if one part missing
diff --git a/update/org.eclipse.update.core/src/org/eclipse/update/internal/core/FeaturePackagedContentProvider.java b/update/org.eclipse.update.core/src/org/eclipse/update/internal/core/FeaturePackagedContentProvider.java
index 49bde17..abf3e7e 100644
--- a/update/org.eclipse.update.core/src/org/eclipse/update/internal/core/FeaturePackagedContentProvider.java
+++ b/update/org.eclipse.update.core/src/org/eclipse/update/internal/core/FeaturePackagedContentProvider.java
@@ -9,10 +9,13 @@
  *     IBM Corporation - initial API and implementation
  *******************************************************************************/
 package org.eclipse.update.internal.core;
+
 import java.io.*;
 import java.net.URL;
 import java.util.*;
 import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
 import org.eclipse.osgi.internal.provisional.verifier.CertificateVerifierFactory;
 import org.eclipse.osgi.util.NLS;
 import org.eclipse.update.core.*;
@@ -30,6 +33,7 @@
 	private ContentReference[] localFeatureFiles = new ContentReference[0];
 	private IVerifier jarVerifier = null;
 	private ExtendedSite siteModel = null;
+	private boolean continueOnError;
 	/*
 	 * filter for file with .jar
 	 */
@@ -40,7 +44,7 @@
 	};
 
 	/*
-	 * Constructor 
+	 * Constructor
 	 */
 	public FeaturePackagedContentProvider(URL url, ISite site) {
 		super(url);
@@ -48,7 +52,7 @@
 			this.siteModel = (ExtendedSite) site;
 		}
 	}
-
+	
 	/*
 	 * Returns a new verifier for each top-level install
 	 * (if the verifier has a parent, return the verifier
@@ -86,7 +90,7 @@
 			// we need to unpack archive locally for UI browser references to be resolved correctly
 			localFeatureFiles = featureJarReference.unpack(getWorkingDirectory(), null, monitor);
 		} catch (IOException e) {
-			throw errorRetrieving(Feature.FEATURE_XML, featureJarReference, e); // 
+			throw errorRetrieving(Feature.FEATURE_XML, featureJarReference, e); 
 		}
 
 		// find the manifest in the unpacked feature files
@@ -153,8 +157,8 @@
 			currentReference = asLocalReference(currentReference, monitor);
 			references[0] = currentReference;
 		} catch (IOException e) {
-			throw errorRetrieving(archiveID, currentReference, e); 
-		}
+			references[0] = continueOnErrorOrRethrow(archiveID, e); 
+		}		
 		return references;
 	}
 
@@ -174,7 +178,7 @@
 		try {
 			references[0] = retrieveLocalJar(new JarContentReference(archiveID, url), monitor);
 		} catch (IOException e) {
-			throw errorRetrieving(archiveID, references[0], e);
+			references[0] = continueOnErrorOrRethrow(archiveID, e);
 		}
 		return references;
 	}
@@ -182,7 +186,20 @@
 	private ContentReference retrieveLocalJar(JarContentReference reference, InstallMonitor monitor) throws IOException, CoreException {
 		//If the site does not support pack200, just get the jar as normal
 		if(siteModel == null || !siteModel.supportsPack200() || !JarProcessor.canPerformUnpack()) {
-			return asLocalReference(reference, monitor);
+			ContentReference contentReference = null;
+			try {
+				contentReference = asLocalReference(reference, monitor);
+			}
+			catch (FileNotFoundException e) {
+				contentReference = continueOnErrorOrRethrow(reference.getIdentifier(), e);
+			}
+			catch (IOException e) {
+				contentReference = continueOnErrorOrRethrow(reference.getIdentifier(), e);
+			}
+			catch (CoreException e) {
+				contentReference = continueOnErrorOrRethrow(reference.getIdentifier(), e);
+			}
+			return contentReference;
 		}
 		
 		ContentReference packedRef = null;
@@ -247,6 +264,7 @@
 		}
 		return packedRef;
 	}
+
 	/*
 	 * @see IFeatureContentProvider#getNonPluginEntryArchiveReferences(INonPluginEntry)
 	 */
@@ -269,12 +287,13 @@
 			references[0] = currentReference;
 
 		} catch (IOException e) {
-			throw errorRetrieving(archiveID, currentReference, e);
+			references[0] = continueOnErrorOrRethrow(archiveID, e);
 		}
 
 		return references;
 	}
 
+
 	/*
 	 * @see IFeatureContentProvider#getFeatureEntryContentReferences()
 	 */
@@ -349,4 +368,58 @@
 
 	}
 
+
+	public void setContinueOnError(boolean continueOnError) {
+		this.continueOnError = continueOnError;
+	}
+	
+	/** 
+	 * This method is used for when a core exception is detected, so, if its decided to rethrow, then 
+	 * a core exception odes not have to be recreated. 
+	 * 
+	 * @param archiveID id of the archive file
+	 * @param CoreException 
+	 * @return NullReference if its decided not to continue
+	 * @throws CoreException
+	 */
+	private ContentReference continueOrErrorOrRethrow(String archiveID, CoreException coreException) throws CoreException {
+		ContentReference reference = null;
+
+		if (continueOnError) {
+			// this ContentReference without a file or URL is purely a
+			// "missing jar" reference.
+			reference = new NullContentReference(archiveID);
+
+			String msg = "    ContinueOnError: The following ID was not found, so was skipped, and is not on miror site: " + archiveID; //$NON-NLS-1$
+			String id = UpdateCore.getPlugin().getBundle().getSymbolicName();
+			IStatus status = new Status(IStatus.WARNING, id , 0, msg, null);
+			UpdateCore.log(status);
+			
+		}
+		else {
+			throw coreException;
+		}
+		return reference;
+	}
+	
+	private ContentReference continueOnErrorOrRethrow(String archiveID, Exception e) throws CoreException {
+		ContentReference reference = null;
+
+		if (continueOnError) { 
+			// this ContentReference without a file or URL is purely a
+			// "missing jar" reference.
+			reference = new NullContentReference(archiveID);
+			
+			String msg = "    ContinueOnError: The following ID was not found, so was skipped, and is not on miror site: " + archiveID; //$NON-NLS-1$
+			String id = UpdateCore.getPlugin().getBundle().getSymbolicName();
+			IStatus status = new Status(IStatus.WARNING, id , 0, msg, null);
+			UpdateCore.log(status);	
+			
+		}
+		else {
+			throw errorRetrieving(archiveID, reference, e);
+		}
+		return reference;
+	}
+
 }
diff --git a/update/org.eclipse.update.core/src/org/eclipse/update/internal/core/NullContentReference.java b/update/org.eclipse.update.core/src/org/eclipse/update/internal/core/NullContentReference.java
new file mode 100644
index 0000000..963fa1d
--- /dev/null
+++ b/update/org.eclipse.update.core/src/org/eclipse/update/internal/core/NullContentReference.java
@@ -0,0 +1,90 @@
+/*******************************************************************************
+ * Copyright (c) 2006 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.update.internal.core;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+import org.eclipse.update.core.ContentReference;
+
+/**
+ * NullContentReference implements a general access wrapper 
+ * to feature and site content -- but, for which, there is no 
+ * content actually found. This way, it can "keep" the ID that was 
+ * requested, and still hold a place in lists and arrays, without a 
+ * change to other program logic. It does, how ever require the internal
+ * algorithms to be more careful about assumptions made ... for example, 
+ * just because asFile is null, it does not follow that asURL will not be null. 
+ * <p>
+ * This class may not be instantiated or subclassed by clients. 
+ * </p>
+ * @see org.eclipse.update.core.ContentReference
+ * @see org.eclipse.update.core.JarContentReference
+ * @see org.eclipse.update.core.JarEntryContentReference
+ */
+public class NullContentReference extends ContentReference {
+
+
+	/**
+	 * Contructor for the "missing jar" case. 
+	 * 
+	 * @param id
+	 */
+	public NullContentReference(String id) {
+		super(id, (File) null);
+	}
+	/**
+	 * A factory method to create a content reference of
+	 * the same type.
+	 * 
+	 * @param id "symbolic" path identifier
+	 */
+	public ContentReference createContentReference(String id, File file) {
+		return new NullContentReference(id);
+	}
+
+	/**
+	 * Overrides super class implementation to avoid throwing a FileNotFound exception.
+	 * 
+	 * @return null
+	 */
+	public InputStream getInputStream() throws IOException {
+		return null;
+	}
+	/**
+	 * Overrides super class implementation to avoid throwing a FileNotFound exception.
+	 * 
+	 * @return null
+	 */
+	public File asFile() throws IOException {
+		return null; 
+	}
+
+	/**
+	 * Overrides super class implementation to avoid throwing URL exceptions.
+	 * 
+	 * @return null
+	 */
+	public URL asURL() throws IOException {
+		return null;
+	}
+
+	/**
+	 * Return string representation of this reference.
+	 * 
+	 * @return string representation
+	 */
+	public String toString() {
+			return "Missing archive file: " + '(' + getIdentifier() + ')'; //$NON-NLS-1$
+	}
+}
diff --git a/update/org.eclipse.update.core/src/org/eclipse/update/internal/mirror/MirrorSite.java b/update/org.eclipse.update.core/src/org/eclipse/update/internal/mirror/MirrorSite.java
index e9c440b..b5fec0c 100644
--- a/update/org.eclipse.update.core/src/org/eclipse/update/internal/mirror/MirrorSite.java
+++ b/update/org.eclipse.update.core/src/org/eclipse/update/internal/mirror/MirrorSite.java
@@ -46,6 +46,7 @@
 import org.eclipse.update.core.model.URLEntryModel;
 import org.eclipse.update.internal.core.CoreExceptionWithRootCause;
 import org.eclipse.update.internal.core.FatalIOException;
+import org.eclipse.update.internal.core.FeaturePackagedContentProvider;
 import org.eclipse.update.internal.core.ISiteContentConsumer;
 import org.eclipse.update.internal.core.UpdateCore;
 import org.eclipse.update.internal.core.UpdateManagerUtils;
@@ -190,6 +191,13 @@
 
 		final IFeatureContentProvider provider =
 			sourceFeature.getFeatureContentProvider();
+		
+		// TODO: passing command options could be made more general in future, so this 
+		// cast is not needed. 
+		if (provider instanceof FeaturePackagedContentProvider) {
+			((FeaturePackagedContentProvider) provider).setContinueOnError(ignoreNonPresentPlugins);
+		}
+		
 		System.out.println(
 			tab
 				+ "Getting plugin entries for " //$NON-NLS-1$
@@ -452,7 +460,10 @@
 			URL newURL = new URL(getURL(), contentReference.getIdentifier());
 			pluginPath = newURL.getFile();
 			inStream = contentReference.getInputStream();
-			UpdateManagerUtils.copyToLocal(inStream, pluginPath, null);
+			// added null check here,  since contentReference can, in theory, return null for input stream. 
+			if (inStream != null) {
+				UpdateManagerUtils.copyToLocal(inStream, pluginPath, null);
+			}
 		} catch (IOException e) {
 			throw Utilities.newCoreException(
 			"Error occurred while creating "+ pluginPath+" file.", //$NON-NLS-1$ //$NON-NLS-2$
@@ -704,7 +715,13 @@
 			URLEntryModel newUrlEntryModel = new URLEntryModel();
 			URL url = urlEntry.getURL();
 			newUrlEntryModel.setAnnotation(urlEntry.getAnnotation());
-			newUrlEntryModel.setURLString(url.toExternalForm());
+			// https://bugs.eclipse.org/bugs/show_bug.cgi?id=136249
+			// URL is not required, so might be null
+			// The null case is (already) handled correctly in
+			// writeDescription
+			if (url != null) {
+				newUrlEntryModel.setURLString(url.toExternalForm());
+			}
 			this.setDescriptionModel(newUrlEntryModel);
 		}
 	}