[243319] [validation] The XML Validator repeatedly tries to access inexistent remote resources
diff --git a/bundles/org.eclipse.wst.xml.core/META-INF/MANIFEST.MF b/bundles/org.eclipse.wst.xml.core/META-INF/MANIFEST.MF
index af632b8..ead58c4 100644
--- a/bundles/org.eclipse.wst.xml.core/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.wst.xml.core/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %pluginName
 Bundle-SymbolicName: org.eclipse.wst.xml.core; singleton:=true
-Bundle-Version: 1.1.301.qualifier
+Bundle-Version: 1.1.302.qualifier
 Bundle-Activator: org.eclipse.wst.xml.core.internal.XMLCorePlugin
 Bundle-Vendor: %providerName
 Bundle-Localization: plugin
diff --git a/bundles/org.eclipse.wst.xml.core/src-validation/org/eclipse/wst/xml/core/internal/validation/XMLNestedValidatorContext.java b/bundles/org.eclipse.wst.xml.core/src-validation/org/eclipse/wst/xml/core/internal/validation/XMLNestedValidatorContext.java
new file mode 100644
index 0000000..29c5e7e
--- /dev/null
+++ b/bundles/org.eclipse.wst.xml.core/src-validation/org/eclipse/wst/xml/core/internal/validation/XMLNestedValidatorContext.java
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * 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.wst.xml.core.internal.validation;
+
+
+import java.util.HashSet;
+
+import org.eclipse.wst.xml.core.internal.validation.core.NestedValidatorContext;
+
+
+/**
+ * XMLNestedValidatorContext is used to store state data needed during an XML
+ * validation session.
+ */
+public class XMLNestedValidatorContext extends NestedValidatorContext
+{
+  /**
+   * A set of inaccessible locations URIs (String).
+   */
+  private HashSet inaccessibleLocationURIs = new HashSet();
+
+  /**
+   * Determines if a location URI was marked as inaccessible.
+   * 
+   * @param locationURI
+   *          the location URI to test. Must not be null.
+   * @return true if a location URI was marked as inaccessible, false otherwise.
+   */
+  public boolean isURIMarkedInaccessible(String locationURI)
+  {
+    return locationURI != null && inaccessibleLocationURIs.contains(locationURI);
+  }
+
+  /**
+   * Marks the given location URI as inaccessible.
+   * 
+   * @param locationURI
+   *          the location URI to mark as inaccessible. Must not be null.
+   */
+  public void markURIInaccessible(String locationURI)
+  {
+    if (locationURI != null)
+    {
+      inaccessibleLocationURIs.add(locationURI);
+    }
+  }
+}
diff --git a/bundles/org.eclipse.wst.xml.core/src-validation/org/eclipse/wst/xml/core/internal/validation/XMLValidator.java b/bundles/org.eclipse.wst.xml.core/src-validation/org/eclipse/wst/xml/core/internal/validation/XMLValidator.java
index 1a44faf..3b1f8dc 100644
--- a/bundles/org.eclipse.wst.xml.core/src-validation/org/eclipse/wst/xml/core/internal/validation/XMLValidator.java
+++ b/bundles/org.eclipse.wst.xml.core/src-validation/org/eclipse/wst/xml/core/internal/validation/XMLValidator.java
@@ -51,6 +51,7 @@
 import org.eclipse.wst.validation.ValidationResult;
 import org.eclipse.wst.xml.core.internal.Logger;
 import org.eclipse.wst.xml.core.internal.validation.core.LazyURLInputStream;
+import org.eclipse.wst.xml.core.internal.validation.core.NestedValidatorContext;
 import org.xml.sax.Attributes;
 import org.xml.sax.InputSource;
 import org.xml.sax.SAXException;
@@ -275,6 +276,27 @@
    */
   public XMLValidationReport validate(String uri, InputStream inputStream, XMLValidationConfiguration configuration, ValidationResult result)
   {
+	  return validate(uri, inputStream, configuration, null, null);
+  }
+  
+  /**
+   * Validate the inputStream
+   * 
+   * @param uri 
+   *    The URI of the file to validate.
+   * @param inputstream
+   *    The inputStream of the file to validate
+   * @param configuration
+   *    A configuration for this validation session.
+   * @param result
+   *    The validation result
+   * @param context
+   *    The validation context   
+   * @return 
+   *    Returns an XML validation report.
+   */
+  public XMLValidationReport validate(String uri, InputStream inputStream, XMLValidationConfiguration configuration, ValidationResult result, NestedValidatorContext context)
+  {
     String grammarFile = "";
     Reader reader1 = null; // Used for the preparse.
     Reader reader2 = null; // Used for validation parse.
@@ -287,7 +309,7 @@
     } 
         
     XMLValidationInfo valinfo = new XMLValidationInfo(uri);
-    MyEntityResolver entityResolver = new MyEntityResolver(uriResolver); 
+    MyEntityResolver entityResolver = new MyEntityResolver(uriResolver, context); 
     ValidatorHelper helper = new ValidatorHelper(); 
     try
     {  
@@ -419,15 +441,18 @@
   {
     private URIResolver uriResolver;
     private String resolvedDTDLocation;
+    private NestedValidatorContext context;
    
     /**
      * Constructor.
      * 
      * @param uriResolver The URI resolver to use with this entity resolver.
+     * @param context The XML validator context.
      */
-    public MyEntityResolver(URIResolver uriResolver)
+    public MyEntityResolver(URIResolver uriResolver, NestedValidatorContext context)
     {
       this.uriResolver = uriResolver;
+      this.context = context;
     }
     
     /* (non-Javadoc)
@@ -435,20 +460,12 @@
      */
     public XMLInputSource resolveEntity(XMLResourceIdentifier rid) throws XNIException, IOException
     {
-      try
-      {
-        XMLInputSource inputSource = _internalResolveEntity(uriResolver, rid);
+        XMLInputSource inputSource = _internalResolveEntity(uriResolver, rid, context);
         if (inputSource != null)
         {
           resolvedDTDLocation = inputSource.getSystemId();
         }
         return inputSource;
-      }
-      catch(IOException e)
-      {
-        //e.printStackTrace();   
-      }      
-      return null;
     }
    
     public String getLocation()
@@ -462,6 +479,11 @@
   // identical code.  In any case we should strive to ensure that the validators perform resolution consistently. 
   public static XMLInputSource _internalResolveEntity(URIResolver uriResolver, XMLResourceIdentifier rid) throws  IOException
   {
+    return _internalResolveEntity(uriResolver, rid, null);
+  }
+  
+  public static XMLInputSource _internalResolveEntity(URIResolver uriResolver, XMLResourceIdentifier rid, NestedValidatorContext context) throws  IOException
+  {
     XMLInputSource is = null;
     
     if (uriResolver != null)
@@ -481,6 +503,18 @@
       if (location != null)
       {                     
         String physical = uriResolver.resolvePhysicalLocation(rid.getBaseSystemId(), id, location);
+
+        // if physical is already a known bad uri, just go ahead and throw an exception
+        if (context instanceof XMLNestedValidatorContext)
+        {
+          XMLNestedValidatorContext xmlContext = ((XMLNestedValidatorContext)context);
+
+          if (xmlContext.isURIMarkedInaccessible(physical))
+          {
+        	 throw new FileNotFoundException(physical);
+          }
+        }
+        
         is = new XMLInputSource(rid.getPublicId(), location, location);
         
         // This block checks that the file exists. If it doesn't we need to throw
@@ -492,6 +526,16 @@
         {
           isTemp = new URL(physical).openStream();
         }
+        catch (IOException e)
+        {
+          // physical was a bad url, so cache it so we know next time
+          if (context instanceof XMLNestedValidatorContext)
+          {
+            XMLNestedValidatorContext xmlContext = ((XMLNestedValidatorContext)context);
+            xmlContext.markURIInaccessible(physical);
+          }
+          throw e;
+        }
         finally
         {
           if(isTemp != null)
diff --git a/bundles/org.eclipse.wst.xml.core/src-validation/org/eclipse/wst/xml/core/internal/validation/core/AbstractNestedValidator.java b/bundles/org.eclipse.wst.xml.core/src-validation/org/eclipse/wst/xml/core/internal/validation/core/AbstractNestedValidator.java
index 9a48579..5e2dfa4 100644
--- a/bundles/org.eclipse.wst.xml.core/src-validation/org/eclipse/wst/xml/core/internal/validation/core/AbstractNestedValidator.java
+++ b/bundles/org.eclipse.wst.xml.core/src-validation/org/eclipse/wst/xml/core/internal/validation/core/AbstractNestedValidator.java
@@ -64,12 +64,25 @@
   public ValidationResult validate(IResource resource, int kind, ValidationState state, IProgressMonitor monitor){
 	  ValidationResult result = new ValidationResult();  
 	  IReporter reporter = result.getReporter(monitor);
-	  NestedValidatorContext nestedcontext = new NestedValidatorContext();
-		setupValidation(nestedcontext);
 		IFile file = null;
 		if (resource instanceof IFile)file = (IFile)resource;
-		if (file != null)validate(file, null, result, reporter, nestedcontext);
-		teardownValidation(nestedcontext);
+		if (file != null)
+		{
+		  NestedValidatorContext nestedcontext = getNestedContext(state, false);
+	      boolean teardownRequired = false;
+	      if (nestedcontext == null)
+	      {
+	        // validationstart was not called, so manually setup and tear down
+	        nestedcontext = getNestedContext(state, true);
+	        setupValidation(nestedcontext);
+	        teardownRequired = true;
+	      }
+
+		  validate(file, null, result, reporter, nestedcontext);
+
+	      if (teardownRequired)
+	        teardownValidation(nestedcontext);
+		}
 	    return result;
   }
  
@@ -421,9 +434,28 @@
    */
   protected void addInfoToMessage (ValidationMessage validationmessage, IMessage message)
   { 
-	// This method may be overidden by subclasses
+	// This method may be overridden by subclasses
   }
-	  
+
+  /**
+   * Get the nested validation context.
+   * 
+   * @param state
+   *          the validation state.
+   * @param create
+   *          when true, a new context will be created if one is not found
+   * @return the nested validation context.
+   */
+  protected NestedValidatorContext getNestedContext(ValidationState state, boolean create)
+  {
+    NestedValidatorContext context = null;
+    if (create)
+    {
+      context = new NestedValidatorContext();
+    }
+    return context;
+  }
+
   /**
    * A localized message is a specialized type of IMessage that allows setting
    * and using a localized message string for a message.
diff --git a/bundles/org.eclipse.wst.xml.core/src-validation/org/eclipse/wst/xml/core/internal/validation/eclipse/Validator.java b/bundles/org.eclipse.wst.xml.core/src-validation/org/eclipse/wst/xml/core/internal/validation/eclipse/Validator.java
index 79acb37..4deafa1 100644
--- a/bundles/org.eclipse.wst.xml.core/src-validation/org/eclipse/wst/xml/core/internal/validation/eclipse/Validator.java
+++ b/bundles/org.eclipse.wst.xml.core/src-validation/org/eclipse/wst/xml/core/internal/validation/eclipse/Validator.java
@@ -13,10 +13,14 @@
 
 import java.io.InputStream;
 
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.wst.validation.ValidationResult;
+import org.eclipse.wst.validation.ValidationState;
 import org.eclipse.wst.validation.internal.provisional.core.IMessage;
 import org.eclipse.wst.xml.core.internal.XMLCorePlugin;
 import org.eclipse.wst.xml.core.internal.preferences.XMLCorePreferenceNames;
+import org.eclipse.wst.xml.core.internal.validation.XMLNestedValidatorContext;
 import org.eclipse.wst.xml.core.internal.validation.XMLValidationConfiguration;
 import org.eclipse.wst.xml.core.internal.validation.XMLValidationReport;
 import org.eclipse.wst.xml.core.internal.validation.core.AbstractNestedValidator;
@@ -26,6 +30,7 @@
 
 public class Validator extends AbstractNestedValidator
 {
+  private static final String XML_VALIDATOR_CONTEXT = "org.eclipse.wst.xml.core.validatorContext"; //$NON-NLS-1$
   protected int indicateNoGrammar = 0;
   
   /**
@@ -64,11 +69,11 @@
     XMLValidationReport valreport = null;
     if (inputstream != null)
     {
-      valreport = validator.validate(uri, inputstream, configuration, result);
+      valreport = validator.validate(uri, inputstream, configuration, result, context);
     }
     else
     {
-      valreport = validator.validate(uri, null, configuration, result);
+      valreport = validator.validate(uri, null, configuration, result, context);
     }
               
     return valreport;
@@ -96,4 +101,55 @@
       message.setAttribute(SQUIGGLE_NAME_OR_VALUE_ATTRIBUTE, messageInfo[1]);
 	}
   }
+  
+  /**
+   * Get the nested validation context.
+   * 
+   * @param state
+   *          the validation state.
+   * @param create
+   *          when true, a new context will be created if one is not found
+   * @return the nested validation context.
+   */
+  protected NestedValidatorContext getNestedContext(ValidationState state, boolean create)
+  {
+    NestedValidatorContext context = null;
+    Object o = state.get(XML_VALIDATOR_CONTEXT);
+    if (o instanceof XMLNestedValidatorContext)
+      context = (XMLNestedValidatorContext)o;
+    else if (create)
+    {
+      context = new XMLNestedValidatorContext();
+    }
+    return context;
+  }
+  
+  public void validationStarting(IProject project, ValidationState state, IProgressMonitor monitor)
+  {
+    if (project != null)
+    {
+      NestedValidatorContext context = getNestedContext(state, false);
+      if (context == null)
+      {
+        context = getNestedContext(state, true);
+        setupValidation(context);
+        state.put(XML_VALIDATOR_CONTEXT, context);
+      }
+      super.validationStarting(project, state, monitor);
+    }
+  }
+  
+  public void validationFinishing(IProject project, ValidationState state, IProgressMonitor monitor)
+  {
+    if (project != null)
+    {
+      super.validationFinishing(project, state, monitor);
+      NestedValidatorContext context = getNestedContext(state, false);
+      if (context != null)
+      {
+        teardownValidation(context);
+        state.put(XML_VALIDATOR_CONTEXT, null);
+      }
+    }
+  }
 }