   /**
    * This class scans a single url for modifications.  It supports
    * missing url's, and will deploy them when they appear.
    */
   private class Scanner
   {
      /** the original url to scan */
      protected URL url;

      /** the url to watch for modification */
      private URL watchUrl;

      /** this holds the lastModified time of watchUrl */
      private long lastModified;

      /** this is a flag to indicate if this url is deployed */
      private boolean deployed;

      /**
       * Construct with the url to deploy / watch
       */
      public Scanner(URL url)
      {
         this.url = url;
      }

      /**
       * Check the url for modification, and deploy / redeploy / undeploy
       * appropriately.
       */
      public void scan()
      {
         if (getLog().isTraceEnabled()) {
            getLog().trace("Scanning url: " + url);
         }
         // check time stamps
         boolean exists = true ;
         long newLastModified = 0 ;
         try
         {
         	newLastModified = getLastModified();
         }
         catch (final FileNotFoundException fnfe)
         {
         	exists = false ;
         }
         catch (final IOException ioe)
         {
         	getLog().debug("Communication failure checking url: " + url) ;
         	return ;
         }

         if (exists)
         {
         	if (newLastModified == 0)
         	{
	          	getLog().debug("Failed to retrieve lastModified for url: " + url);
         	}
         	else if (lastModified != newLastModified)
         	{
               // url needs deploy / redeploy
               try
               {
                  getLog().debug("Deploying Modified (or new) url: " + url);
                  deploy();
               } catch (DeploymentException e)
               {
                  getLog().error("Failed to (re)deploy url: " + url, e);
               }
         	}
         }
         else
         {
           // url does not exist... undeploy
           try
           {
              getLog().debug("Undeploying url: " + url);
              undeploy();
           } catch (DeploymentException e)
           {
              getLog().error("Failed to undeploy url: " + url, e);
           }
        }
      }
      /**
       * return the modification date of watchUrl
       */
      private long getLastModified()
      	throws IOException
      {
        final URL lmUrl = watchUrl == null ? url : watchUrl;
        return lmUrl.openConnection().getLastModified();
      }
      /**
       * (Re)deploy the url.  This will undeploy the url first, if it is
       * already deployed.  It also fetches
       */
      private void deploy() throws DeploymentException
      {
         if (deployed)
         {
            // already deployed... undeploy first
            getDeployerObj().undeploy(url);
         }
         getDeployerObj().deploy(url);

         // reset the watch url
         try
         {
            Object o = getServer().invoke(getDeployer(), "getWatchUrl",
            new Object[]
            { url },
            new String[]
            { URL.class.getName() });
            watchUrl = o == null ? url : (URL)o;

            getLog().debug("Watch URL for: " + url + " -> " + watchUrl);
         } catch (Exception e)
         {
            watchUrl = url;
            getLog().debug("Unable to obtain watchUrl from deployer.  " +
            "Use url: " + url, e);
         }

         // the watchurl may have changed... get a new lastModified
         try
         {
	         lastModified = getLastModified();
         }
         catch (final IOException ioe)
         {
         	lastModified = 0L ;
         }

         // set the deployed flag
         deployed = true;
      }
      /**
       * Undeploy the url (if deployed).
       */
      private void undeploy() throws DeploymentException
      {
         if (!deployed)
         {
            return;
         }
         getDeployerObj().undeploy(url);
         // reset the other fields
         deployed = false;
         lastModified = 0L;
         watchUrl = null;
      }
   }
