KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jboss > deployment > scanner > URLDeploymentScanner


1 /*
2  * JBoss, Home of Professional Open Source
3  * Copyright 2005, JBoss Inc., and individual contributors as indicated
4  * by the @authors tag. See the copyright.txt in the distribution for a
5  * full listing of individual contributors.
6  *
7  * This is free software; you can redistribute it and/or modify it
8  * under the terms of the GNU Lesser General Public License as
9  * published by the Free Software Foundation; either version 2.1 of
10  * the License, or (at your option) any later version.
11  *
12  * This software is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this software; if not, write to the Free
19  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
21  */

22 package org.jboss.deployment.scanner;
23
24 import java.io.File JavaDoc;
25 import java.io.IOException JavaDoc;
26 import java.net.MalformedURLException JavaDoc;
27 import java.net.URL JavaDoc;
28 import java.net.URLConnection JavaDoc;
29 import java.util.ArrayList JavaDoc;
30 import java.util.Collections JavaDoc;
31 import java.util.Comparator JavaDoc;
32 import java.util.HashSet JavaDoc;
33 import java.util.Iterator JavaDoc;
34 import java.util.LinkedList JavaDoc;
35 import java.util.List JavaDoc;
36 import java.util.Set JavaDoc;
37 import java.util.StringTokenizer JavaDoc;
38
39 import javax.management.MBeanServer JavaDoc;
40 import javax.management.ObjectName JavaDoc;
41
42 import org.jboss.deployment.DefaultDeploymentSorter;
43 import org.jboss.deployment.IncompleteDeploymentException;
44 import org.jboss.mx.util.JMXExceptionDecoder;
45 import org.jboss.net.protocol.URLLister;
46 import org.jboss.net.protocol.URLListerFactory;
47 import org.jboss.net.protocol.URLLister.URLFilter;
48 import org.jboss.system.server.ServerConfig;
49 import org.jboss.system.server.ServerConfigLocator;
50 import org.jboss.util.NullArgumentException;
51 import org.jboss.util.StringPropertyReplacer;
52
53 /**
54  * A URL-based deployment scanner. Supports local directory
55  * scanning for file-based urls.
56  *
57  * @jmx:mbean extends="org.jboss.deployment.scanner.DeploymentScannerMBean"
58  *
59  * @version <tt>$Revision: 57108 $</tt>
60  * @author <a HREF="mailto:jason@planet57.com">Jason Dillon</a>
61  * @author <a HREF="mailto:dimitris@jboss.org">Dimitris Andreadis</a>
62  */

63 public class URLDeploymentScanner extends AbstractDeploymentScanner
64    implements DeploymentScanner, URLDeploymentScannerMBean
65 {
66    /** A set of deployment URLs to skip **/
67    protected Set JavaDoc skipSet = Collections.synchronizedSet(new HashSet JavaDoc());
68    
69    /** The list of URLs to scan. */
70    protected List JavaDoc urlList = Collections.synchronizedList(new ArrayList JavaDoc());
71
72    /** A set of scanned urls which have been deployed. */
73    protected Set JavaDoc deployedSet = Collections.synchronizedSet(new HashSet JavaDoc());
74
75    /** Helper for listing local/remote directory URLs */
76    protected URLListerFactory listerFactory = new URLListerFactory();
77    
78    /** The server's home directory, for relative paths. */
79    protected File JavaDoc serverHome;
80
81    protected URL JavaDoc serverHomeURL;
82
83    /** A sorter urls from a scaned directory to allow for coarse dependency
84     ordering based on file type
85     */

86    protected Comparator JavaDoc sorter;
87
88    /** Allow a filter for scanned directories */
89    protected URLFilter filter;
90    
91    protected IncompleteDeploymentException lastIncompleteDeploymentException;
92    
93    /** Whether to search inside directories whose names containing no dots */
94    protected boolean doRecursiveSearch = true;
95    
96    /**
97     * @jmx:managed-attribute
98     */

99    public void setRecursiveSearch (boolean recurse)
100    {
101       doRecursiveSearch = recurse;
102    }
103    
104    /**
105     * @jmx:managed-attribute
106     */

107    public boolean getRecursiveSearch ()
108    {
109       return doRecursiveSearch;
110    }
111    
112    /**
113     * @jmx:managed-attribute
114     */

115    public void setURLList(final List JavaDoc list)
116    {
117       if (list == null)
118          throw new NullArgumentException("list");
119
120       // start out with a fresh list
121
urlList.clear();
122
123       Iterator JavaDoc iter = list.iterator();
124       while (iter.hasNext())
125       {
126          URL JavaDoc url = (URL JavaDoc)iter.next();
127          if (url == null)
128             throw new NullArgumentException("list element");
129
130          addURL(url);
131       }
132       
133       log.debug("URL list: " + urlList);
134    }
135
136    /**
137     * @jmx:managed-attribute
138     *
139     * @param classname The name of a Comparator class.
140     */

141    public void setURLComparator(String JavaDoc classname)
142       throws ClassNotFoundException JavaDoc, IllegalAccessException JavaDoc,
143       InstantiationException JavaDoc
144    {
145       sorter = (Comparator JavaDoc)Thread.currentThread().getContextClassLoader().loadClass(classname).newInstance();
146    }
147
148    /**
149     * @jmx:managed-attribute
150     */

151    public String JavaDoc getURLComparator()
152    {
153       if (sorter == null)
154          return null;
155       return sorter.getClass().getName();
156    }
157
158    /**
159     * @jmx:managed-attribute
160     *
161     * @param classname The name of a FileFilter class.
162     */

163    public void setFilter(String JavaDoc classname)
164    throws ClassNotFoundException JavaDoc, IllegalAccessException JavaDoc, InstantiationException JavaDoc
165    {
166       Class JavaDoc filterClass = Thread.currentThread().getContextClassLoader().loadClass(classname);
167       filter = (URLFilter) filterClass.newInstance();
168    }
169
170    /**
171     * @jmx:managed-attribute
172     */

173    public String JavaDoc getFilter()
174    {
175       if (filter == null)
176          return null;
177       return filter.getClass().getName();
178    }
179
180    
181    /**
182     * @jmx:managed-attribute
183     *
184     * @param filter The URLFilter instance
185     */

186    public void setFilterInstance(URLFilter filter)
187    {
188       this.filter = filter;
189    }
190
191    /**
192     * @jmx:managed-attribute
193     */

194    public URLFilter getFilterInstance()
195    {
196       return filter;
197    }
198
199    /**
200     * @jmx:managed-attribute
201     */

202    public List JavaDoc getURLList()
203    {
204       // too bad, List isn't a cloneable
205
return new ArrayList JavaDoc(urlList);
206    }
207
208    /**
209     * @jmx:managed-operation
210     */

211    public void addURL(final URL JavaDoc url)
212    {
213       if (url == null)
214          throw new NullArgumentException("url");
215       
216       try
217       {
218          // check if this is a valid url
219
url.openConnection().connect();
220       }
221       catch (IOException JavaDoc e)
222       {
223          // either a bad configuration (non-existent url) or a transient i/o error
224
log.warn("addURL(), caught " + e.getClass().getName() + ": " + e.getMessage());
225       }
226       urlList.add(url);
227       
228       log.debug("Added url: " + url);
229    }
230
231    /**
232     * @jmx:managed-operation
233     */

234    public void removeURL(final URL JavaDoc url)
235    {
236       if (url == null)
237          throw new NullArgumentException("url");
238
239       boolean success = urlList.remove(url);
240       if (success)
241       {
242          log.debug("Removed url: " + url);
243       }
244    }
245
246    /**
247     * @jmx:managed-operation
248     */

249    public boolean hasURL(final URL JavaDoc url)
250    {
251       if (url == null)
252          throw new NullArgumentException("url");
253
254       return urlList.contains(url);
255    }
256
257    /**
258     * Temporarily ignore changes (addition, updates, removal) to a particular
259     * deployment, identified by its deployment URL. The deployment URL is different
260     * from the 'base' URLs that are scanned by the scanner (e.g. the full path to
261     * deploy/jmx-console.war vs. deploy/). This can be used to avoid an attempt
262     * by the scanner to deploy/redeploy/undeploy a URL that is being modified.
263     *
264     * To re-enable scanning of changes for a URL, use resumeDeployment(URL, boolean).
265     *
266     * @jmx:managed-operation
267     */

268    public void suspendDeployment(URL JavaDoc url)
269    {
270       if (url == null)
271          throw new NullArgumentException("url");
272       
273       if (skipSet.add(url))
274          log.debug("Deployment URL added to skipSet: " + url);
275       else
276          throw new IllegalStateException JavaDoc("Deployment URL already suspended: " + url);
277    }
278
279    /**
280     * Re-enables scanning of a particular deployment URL, previously suspended
281     * using suspendDeployment(URL). If the markUpToDate flag is true then the
282     * deployment module will be considered up-to-date during the next scan.
283     * If the flag is false, at the next scan the scanner will check the
284     * modification date to decide if the module needs deploy/redeploy/undeploy.
285     *
286     * @jmx:managed-operation
287     */

288    public void resumeDeployment(URL JavaDoc url, boolean markUpToDate)
289    {
290       if (url == null)
291          throw new NullArgumentException("url");
292       
293       if (skipSet.contains(url))
294       {
295          if (markUpToDate)
296          {
297             // look for the deployment and mark it as uptodate
298
for (Iterator JavaDoc i = deployedSet.iterator(); i.hasNext(); )
299             {
300                DeployedURL deployedURL = (DeployedURL)i.next();
301                if (deployedURL.url.equals(url))
302                {
303                   // the module could have been removed..
304
log.debug("Marking up-to-date: " + url);
305                   deployedURL.deployed();
306                   break;
307                }
308             }
309          }
310          // don't skip this url anymore
311
skipSet.remove(url);
312          log.debug("Deployment URL removed from skipSet: " + url);
313       }
314       else
315       {
316          throw new IllegalStateException JavaDoc("Deployment URL not suspended: " + url);
317       }
318    }
319
320    /**
321     * Lists all urls deployed by the scanner, each URL on a new line.
322     *
323     * @jmx:managed-operation
324     */

325    public String JavaDoc listDeployedURLs()
326    {
327       StringBuffer JavaDoc sbuf = new StringBuffer JavaDoc();
328       for (Iterator JavaDoc i = deployedSet.iterator(); i.hasNext(); )
329       {
330          URL JavaDoc url = ((DeployedURL)i.next()).url;
331          if (sbuf.length() > 0)
332          {
333             sbuf.append("\n").append(url);
334          }
335          else
336          {
337             sbuf.append(url);
338          }
339       }
340       return sbuf.toString();
341    }
342
343    /////////////////////////////////////////////////////////////////////////
344
// Management/Configuration Helpers //
345
/////////////////////////////////////////////////////////////////////////
346

347    /**
348     * @jmx:managed-attribute
349     */

350    public void setURLs(final String JavaDoc listspec) throws MalformedURLException JavaDoc
351    {
352       if (listspec == null)
353          throw new NullArgumentException("listspec");
354
355       List JavaDoc list = new LinkedList JavaDoc();
356
357       StringTokenizer JavaDoc stok = new StringTokenizer JavaDoc(listspec, ",");
358       while (stok.hasMoreTokens())
359       {
360          String JavaDoc urlspec = stok.nextToken().trim();
361          log.debug("Adding URL from spec: " + urlspec);
362
363          URL JavaDoc url = makeURL(urlspec);
364          log.debug("URL: " + url);
365          
366          list.add(url);
367       }
368
369       setURLList(list);
370    }
371
372    /**
373     * A helper to make a URL from a full url, or a filespec.
374     */

375    protected URL JavaDoc makeURL(String JavaDoc urlspec) throws MalformedURLException JavaDoc
376    {
377       // First replace URL with appropriate properties
378
//
379
urlspec = StringPropertyReplacer.replaceProperties (urlspec);
380       return new URL JavaDoc(serverHomeURL, urlspec);
381    }
382
383    /**
384     * @jmx:managed-operation
385     */

386    public void addURL(final String JavaDoc urlspec) throws MalformedURLException JavaDoc
387    {
388       addURL(makeURL(urlspec));
389    }
390
391    /**
392     * @jmx:managed-operation
393     */

394    public void removeURL(final String JavaDoc urlspec) throws MalformedURLException JavaDoc
395    {
396       removeURL(makeURL(urlspec));
397    }
398
399    /**
400     * @jmx:managed-operation
401     */

402    public boolean hasURL(final String JavaDoc urlspec) throws MalformedURLException JavaDoc
403    {
404       return hasURL(makeURL(urlspec));
405    }
406
407    /**
408     * A helper to deploy the given URL with the deployer.
409     */

410    protected void deploy(final DeployedURL du)
411    {
412       // If the deployer is null simply ignore the request
413
if (deployer == null)
414          return;
415       
416       try
417       {
418          if (log.isTraceEnabled())
419             log.trace("Deploying: " + du);
420
421          deployer.deploy(du.url);
422       }
423       catch (IncompleteDeploymentException e)
424       {
425          lastIncompleteDeploymentException = e;
426       }
427       catch (Exception JavaDoc e)
428       {
429          log.debug("Failed to deploy: " + du, e);
430       }
431
432       du.deployed();
433
434       if (!deployedSet.contains(du))
435       {
436          deployedSet.add(du);
437       }
438    }
439
440    /**
441     * A helper to undeploy the given URL from the deployer.
442     */

443    protected void undeploy(final DeployedURL du)
444    {
445       try
446       {
447          if (log.isTraceEnabled())
448             log.trace("Undeploying: " + du);
449
450          deployer.undeploy(du.url);
451          deployedSet.remove(du);
452       }
453       catch (Exception JavaDoc e)
454       {
455          log.error("Failed to undeploy: " + du, e);
456       }
457    }
458
459    /**
460     * Checks if the url is in the deployed set.
461     */

462    protected boolean isDeployed(final URL JavaDoc url)
463    {
464       DeployedURL du = new DeployedURL(url);
465       return deployedSet.contains(du);
466    }
467
468    public synchronized void scan() throws Exception JavaDoc
469    {
470       lastIncompleteDeploymentException = null;
471       if (urlList == null)
472          throw new IllegalStateException JavaDoc("not initialized");
473
474       updateSorter();
475
476       boolean trace = log.isTraceEnabled();
477       List JavaDoc urlsToDeploy = new LinkedList JavaDoc();
478
479       // Scan for deployments
480
if (trace)
481       {
482          log.trace("Scanning for new deployments");
483       }
484       synchronized (urlList)
485       {
486          for (Iterator JavaDoc i = urlList.iterator(); i.hasNext();)
487          {
488             URL JavaDoc url = (URL JavaDoc) i.next();
489             try
490             {
491                if (url.toString().endsWith("/"))
492                {
493                   // treat URL as a collection
494
URLLister lister = listerFactory.createURLLister(url);
495                   
496                   // listMembers() will throw an IOException if collection url does not exist
497
urlsToDeploy.addAll(lister.listMembers(url, filter, doRecursiveSearch));
498                }
499                else
500                {
501                   // treat URL as a deployable unit
502

503                   // throws IOException if this URL does not exist
504
url.openConnection().connect();
505                   urlsToDeploy.add(url);
506                }
507             }
508             catch (IOException JavaDoc e)
509             {
510                // Either one of the configured URLs is bad, i.e. points to a non-existent
511
// location, or it ends with a '/' but it is not a directory (so it
512
// is really user's fault), OR some other hopefully transient I/O error
513
// happened (e.g. out of file descriptors?) so log a warning.
514
log.warn("Scan URL, caught " + e.getClass().getName() + ": " + e.getMessage());
515                
516                // We need to return because at least one of the listed URLs will
517
// return no results, and so all deployments starting from that point
518
// (e.g. deploy/) will get undeployed, see JBAS-3107.
519
// On the other hand, in case of a bad configuration nothing will get
520
// deployed. If really want independence of e.g. 2 deploy urls, more
521
// than one URLDeploymentScanners can be setup.
522
return;
523             }
524          }
525       }
526
527       if (trace)
528       {
529          log.trace("Updating existing deployments");
530       }
531       LinkedList JavaDoc urlsToRemove = new LinkedList JavaDoc();
532       LinkedList JavaDoc urlsToCheckForUpdate = new LinkedList JavaDoc();
533       synchronized (deployedSet)
534       {
535          // remove previously deployed URLs no longer needed
536
for (Iterator JavaDoc i = deployedSet.iterator(); i.hasNext();)
537          {
538             DeployedURL deployedURL = (DeployedURL) i.next();
539             
540             if (skipSet.contains(deployedURL.url))
541             {
542                if (trace)
543                   log.trace("Skipping update/removal check for: " + deployedURL.url);
544             }
545             else
546             {
547                if (urlsToDeploy.contains(deployedURL.url))
548                {
549                   urlsToCheckForUpdate.add(deployedURL);
550                }
551                else
552                {
553                   urlsToRemove.add(deployedURL);
554                }
555             }
556          }
557       }
558
559       // ********
560
// Undeploy
561
// ********
562

563       for (Iterator JavaDoc i = urlsToRemove.iterator(); i.hasNext();)
564       {
565          DeployedURL deployedURL = (DeployedURL) i.next();
566          if (trace)
567          {
568             log.trace("Removing " + deployedURL.url);
569          }
570          undeploy(deployedURL);
571       }
572
573       // ********
574
// Redeploy
575
// ********
576

577       // compute the DeployedURL list to update
578
ArrayList JavaDoc urlsToUpdate = new ArrayList JavaDoc(urlsToCheckForUpdate.size());
579       for (Iterator JavaDoc i = urlsToCheckForUpdate.iterator(); i.hasNext();)
580       {
581          DeployedURL deployedURL = (DeployedURL) i.next();
582          if (deployedURL.isModified())
583          {
584             if (trace)
585             {
586                log.trace("Re-deploying " + deployedURL.url);
587             }
588             urlsToUpdate.add(deployedURL);
589          }
590       }
591
592       // sort to update list
593
Collections.sort(urlsToUpdate, new Comparator JavaDoc()
594       {
595          public int compare(Object JavaDoc o1, Object JavaDoc o2)
596          {
597             return sorter.compare(((DeployedURL) o1).url, ((DeployedURL) o2).url);
598          }
599       });
600
601       // Undeploy in order
602
for (int i = urlsToUpdate.size() - 1; i >= 0;i--)
603       {
604          undeploy((DeployedURL) urlsToUpdate.get(i));
605       }
606
607       // Deploy in order
608
for (int i = 0; i < urlsToUpdate.size();i++)
609       {
610          deploy((DeployedURL) urlsToUpdate.get(i));
611       }
612
613       // ******
614
// Deploy
615
// ******
616

617       Collections.sort(urlsToDeploy, sorter);
618       for (Iterator JavaDoc i = urlsToDeploy.iterator(); i.hasNext();)
619       {
620          URL JavaDoc url = (URL JavaDoc) i.next();
621          DeployedURL deployedURL = new DeployedURL(url);
622          if (deployedSet.contains(deployedURL) == false)
623          {
624             if (skipSet.contains(url))
625             {
626                if (trace)
627                   log.trace("Skipping deployment of: " + url);
628             }
629             else
630             {
631                if (trace)
632                   log.trace("Deploying " + deployedURL.url);
633                
634                deploy(deployedURL);
635             }
636          }
637          i.remove();
638          // Check to see if mainDeployer suffix list has changed.
639
// if so, then resort
640
if (i.hasNext() && updateSorter())
641          {
642             Collections.sort(urlsToDeploy, sorter);
643             i = urlsToDeploy.iterator();
644          }
645       }
646
647       // Validate that there are still incomplete deployments
648
if (lastIncompleteDeploymentException != null)
649       {
650          try
651          {
652             Object JavaDoc[] args = {};
653             String JavaDoc[] sig = {};
654             getServer().invoke(getDeployer(),
655                                "checkIncompleteDeployments", args, sig);
656          }
657          catch (Exception JavaDoc e)
658          {
659             Throwable JavaDoc t = JMXExceptionDecoder.decode(e);
660             log.error(t);
661          }
662       }
663    }
664
665    protected boolean updateSorter()
666    {
667       // Check to see if mainDeployer suffix list has changed.
668
if (sorter instanceof DefaultDeploymentSorter)
669       {
670          DefaultDeploymentSorter defaultSorter = (DefaultDeploymentSorter)sorter;
671          if (defaultSorter.getSuffixOrder() != mainDeployer.getSuffixOrder())
672          {
673             defaultSorter.setSuffixOrder(mainDeployer.getSuffixOrder());
674             return true;
675          }
676       }
677       return false;
678    }
679
680    /////////////////////////////////////////////////////////////////////////
681
// Service/ServiceMBeanSupport //
682
/////////////////////////////////////////////////////////////////////////
683

684    public ObjectName JavaDoc preRegister(MBeanServer JavaDoc server, ObjectName JavaDoc name)
685       throws Exception JavaDoc
686    {
687       // get server's home for relative paths, need this for setting
688
// attribute final values, so we need to do it here
689
ServerConfig serverConfig = ServerConfigLocator.locate();
690       serverHome = serverConfig.getServerHomeDir();
691       serverHomeURL = serverConfig.getServerHomeURL();
692
693       return super.preRegister(server, name);
694    }
695
696    protected void createService() throws Exception JavaDoc
697    {
698       // Perform a couple of sanity checks
699
if (this.filter == null)
700       {
701          throw new IllegalStateException JavaDoc("'FilterInstance' attribute not configured");
702       }
703       if (this.sorter == null)
704       {
705          throw new IllegalStateException JavaDoc("'URLComparator' attribute not configured");
706       }
707       // ok, proceed with normal createService()
708
super.createService();
709    }
710    
711    /////////////////////////////////////////////////////////////////////////
712
// DeployedURL //
713
/////////////////////////////////////////////////////////////////////////
714

715    /**
716     * A container and help class for a deployed URL.
717     * should be static at this point, with the explicit scanner ref, but I'm (David) lazy.
718     */

719    protected class DeployedURL
720    {
721       public URL JavaDoc url;
722       /** The url to check to decide if we need to redeploy */
723       public URL JavaDoc watchUrl;
724       
725       public long deployedLastModified;
726
727       public DeployedURL(final URL JavaDoc url)
728       {
729          this.url = url;
730       }
731
732       public void deployed()
733       {
734          deployedLastModified = getLastModified();
735       }
736       public boolean isFile()
737       {
738          return url.getProtocol().equals("file");
739       }
740
741       public File JavaDoc getFile()
742       {
743          return new File JavaDoc(url.getFile());
744       }
745
746       public boolean isRemoved()
747       {
748          if (isFile())
749          {
750             File JavaDoc file = getFile();
751             return !file.exists();
752          }
753          return false;
754       }
755
756       public long getLastModified()
757       {
758          if (watchUrl == null)
759          {
760             try
761             {
762                Object JavaDoc o = getServer().invoke(
763                      getDeployer(),
764                      "getWatchUrl",
765                      new Object JavaDoc[] { url },
766                      new String JavaDoc[] { URL JavaDoc.class.getName() }
767                      );
768                watchUrl = o == null ? url : (URL JavaDoc)o;
769                getLog().debug("Watch URL for: " + url + " -> " + watchUrl);
770             }
771             catch (Exception JavaDoc e)
772             {
773                watchUrl = url;
774                getLog().debug("Unable to obtain watchUrl from deployer. Use url: " + url, e);
775             }
776          }
777
778          try
779          {
780             URLConnection JavaDoc connection;
781             if (watchUrl != null)
782             {
783                connection = watchUrl.openConnection();
784             }
785             else
786             {
787                connection = url.openConnection();
788             }
789             // no need to do special checks for files...
790
// org.jboss.net.protocol.file.FileURLConnection correctly
791
// implements the getLastModified method.
792
long lastModified = connection.getLastModified();
793
794             return lastModified;
795          }
796          catch (java.io.IOException JavaDoc e)
797          {
798             log.warn("Failed to check modification of deployed url: " + url, e);
799          }
800          return -1;
801       }
802
803       public boolean isModified()
804       {
805          long lastModified = getLastModified();
806          if (lastModified == -1)
807          {
808             // ignore errors fetching the timestamp - see bug 598335
809
return false;
810          }
811          return deployedLastModified != lastModified;
812       }
813
814       public int hashCode()
815       {
816          return url.hashCode();
817       }
818
819       public boolean equals(final Object JavaDoc other)
820       {
821          if (other instanceof DeployedURL)
822          {
823             return ((DeployedURL)other).url.equals(this.url);
824          }
825          return false;
826       }
827
828       public String JavaDoc toString()
829       {
830          return super.toString() +
831          "{ url=" + url +
832          ", deployedLastModified=" + deployedLastModified +
833          " }";
834       }
835    }
836 }
837
Popular Tags